mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-18 09:35:39 -05:00
Add datavirtualization extension (#21594)
* initial * cleanup * Add typings ref * fix compile * remove unused * add missing * another unused * Use newer vscodetestcover * newer dataprotocol * format * cleanup ignores * fix out path * fix entry point * more cleanup * Move into src folder * Handle service client log messages * remove unused
This commit is contained in:
@@ -0,0 +1,312 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
import { IWizardPageWrapper } from '../wizardPageWrapper';
|
||||
import { VirtualizeDataModel } from './virtualizeDataModel';
|
||||
import { VirtualizeDataInput } from '../../services/contracts';
|
||||
import { getDropdownValue } from '../../utils';
|
||||
import { AppContext } from '../../appContext';
|
||||
import { VDIManager } from './virtualizeDataInputManager';
|
||||
import { dataSourcePrefixMapping, connectionPageInfoMapping } from '../../constants';
|
||||
|
||||
export class ConnectionDetailsPage implements IWizardPageWrapper {
|
||||
|
||||
private _page: azdata.window.WizardPage;
|
||||
private _modelBuilder: azdata.ModelBuilder;
|
||||
private _mainContainer: azdata.FlexContainer;
|
||||
|
||||
private _dataSourceNameForm: azdata.FormComponent;
|
||||
private _sourceServerInfoComponentsFormGroup: azdata.FormComponentGroup;
|
||||
private _credentialComponentsFormGroup: azdata.FormComponentGroup;
|
||||
|
||||
private _dataSourceNameDropDown: azdata.DropDownComponent;
|
||||
private _serverNameInput: azdata.InputBoxComponent;
|
||||
private _databaseNameInput: azdata.InputBoxComponent;
|
||||
private _existingCredDropdown: azdata.DropDownComponent;
|
||||
private _credentialNameInput: azdata.InputBoxComponent;
|
||||
private _usernameInput: azdata.InputBoxComponent;
|
||||
private _passwordInput: azdata.InputBoxComponent;
|
||||
|
||||
private readonly _createCredLabel = localize('newCredOption', '-- Create New Credential --');
|
||||
private readonly _parentLayout: azdata.FormItemLayout = { horizontal: true, componentWidth: '600px' };
|
||||
private readonly _dataSourceNameInputBoxLayout: azdata.FormItemLayout =
|
||||
Object.assign({ info: localize('dataSourceHelpText', 'The name for your External Data Source.') }, this._parentLayout);
|
||||
private readonly _existingCredDropdownLayout: azdata.FormItemLayout =
|
||||
Object.assign({
|
||||
info: localize('credNameHelpText',
|
||||
'The name of the Database Scoped Credential used to securely store the login information for the External Data Source you are creating.')
|
||||
}, this._parentLayout);
|
||||
|
||||
private _currentDataSourceType: string;
|
||||
private _currentDestDbName: string;
|
||||
|
||||
constructor(private _dataModel: VirtualizeDataModel, private _vdiManager: VDIManager, private _appContext: AppContext) {
|
||||
this._page = this._appContext.apiWrapper.createWizardPage(localize('connectionDetailsTitle', 'Create a connection to your Data Source'));
|
||||
this._page.registerContent(async (modelView) => {
|
||||
this._modelBuilder = modelView.modelBuilder;
|
||||
this._mainContainer = this._modelBuilder.flexContainer().component();
|
||||
await modelView.initializeModel(this._mainContainer);
|
||||
});
|
||||
}
|
||||
|
||||
public async buildMainContainer(): Promise<void> {
|
||||
// Create data source fields first, since it preloads the database metadata
|
||||
await this.buildDataSourceNameForm();
|
||||
await this.buildSourceServerInfoComponentsFormGroup();
|
||||
await this.buildCredentialComponentsFormGroup();
|
||||
const serverAndCredentialComponents: (azdata.FormComponent | azdata.FormComponentGroup)[] = [];
|
||||
serverAndCredentialComponents.push(this._sourceServerInfoComponentsFormGroup);
|
||||
serverAndCredentialComponents.push(this._credentialComponentsFormGroup);
|
||||
|
||||
const mainFormBuilder: azdata.FormBuilder = this._modelBuilder.formContainer();
|
||||
mainFormBuilder.addFormItem(this._dataSourceNameForm, this._dataSourceNameInputBoxLayout);
|
||||
mainFormBuilder.addFormItems(serverAndCredentialComponents, this._parentLayout);
|
||||
this._mainContainer.clearItems();
|
||||
this._mainContainer.addItem(mainFormBuilder.component());
|
||||
}
|
||||
|
||||
public async buildDataSourceNameForm(): Promise<void> {
|
||||
let destinationDB = this._vdiManager.destinationDatabaseName;
|
||||
let dbInfo = await this._dataModel.loadDatabaseInfo(destinationDB);
|
||||
let existingDataSources = dbInfo ? dbInfo.externalDataSources : [];
|
||||
const locationPrefix = dataSourcePrefixMapping.get(this._currentDataSourceType) ?? '';
|
||||
existingDataSources = existingDataSources.filter(ds => ds.location.startsWith(locationPrefix));
|
||||
|
||||
let dataSourceInfo = existingDataSources.map(e => {
|
||||
return { name: e.name, location: e.location, credName: e.credentialName };
|
||||
});
|
||||
|
||||
this._dataSourceNameDropDown = this._modelBuilder.dropDown().component();
|
||||
await this._dataSourceNameDropDown.updateProperties({
|
||||
values: [''].concat(dataSourceInfo.map(e => `${e.name} (${e.location}, ${e.credName})`)),
|
||||
value: undefined,
|
||||
editable: true,
|
||||
height: undefined,
|
||||
enabled: true,
|
||||
fireOnTextChange: true
|
||||
});
|
||||
|
||||
this._dataSourceNameDropDown.onValueChanged(async () => {
|
||||
let dataSourceName = getDropdownValue(this._dataSourceNameDropDown.value);
|
||||
let dsInfo = dataSourceInfo.find(e => dataSourceName === `${e.name} (${e.location}, ${e.credName})`);
|
||||
if (dsInfo) {
|
||||
await this._dataSourceNameDropDown.updateProperties({ value: dsInfo.name });
|
||||
return;
|
||||
}
|
||||
if (dataSourceName === '') {
|
||||
await this._dataSourceNameDropDown.updateProperties({ value: undefined });
|
||||
await this.toggleServerCredInputs(true, '', this._createCredLabel, '', '', '');
|
||||
return;
|
||||
}
|
||||
let selectedDataSource = existingDataSources.find(ds => ds.name === this._dataSourceNameDropDown.value);
|
||||
if (selectedDataSource) {
|
||||
let serverName: string = selectedDataSource.location.substring(locationPrefix.length);
|
||||
await this.toggleServerCredInputs(false, serverName, selectedDataSource.credentialName,
|
||||
selectedDataSource.credentialName, selectedDataSource.username, '');
|
||||
return;
|
||||
}
|
||||
if (!this._serverNameInput.enabled) {
|
||||
await this.toggleServerCredInputs(true, '', this._createCredLabel, '', '', '');
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
this._dataSourceNameForm = <azdata.FormComponent>{
|
||||
component: this._dataSourceNameDropDown,
|
||||
title: localize('sourceNameInput', 'External Data Source Name'),
|
||||
required: true
|
||||
};
|
||||
}
|
||||
|
||||
public async toggleServerCredInputs(
|
||||
enable: boolean,
|
||||
serverNameValue: string,
|
||||
credDropDownValue: string,
|
||||
credNameValue: string,
|
||||
usernameValue: string,
|
||||
passwordValue: string
|
||||
): Promise<void> {
|
||||
// There is a bug in recognizing required field.
|
||||
// As workaround, it intentionally updates 'enabled' property first and then update 'value'
|
||||
await this._serverNameInput.updateProperties({ enabled: enable });
|
||||
await this._existingCredDropdown.updateProperties({ enabled: enable });
|
||||
await this._credentialNameInput.updateProperties({ enabled: enable });
|
||||
await this._usernameInput.updateProperties({ enabled: enable });
|
||||
await this._passwordInput.updateProperties({ enabled: enable });
|
||||
|
||||
await this._serverNameInput.updateProperties({ value: serverNameValue });
|
||||
await this._existingCredDropdown.updateProperties({ value: credDropDownValue });
|
||||
await this._credentialNameInput.updateProperties({ value: credNameValue });
|
||||
await this._usernameInput.updateProperties({ value: usernameValue });
|
||||
await this._passwordInput.updateProperties({ value: passwordValue });
|
||||
}
|
||||
|
||||
// Server-specific fields
|
||||
public async buildSourceServerInfoComponentsFormGroup(): Promise<void> {
|
||||
let serverNameValue: string = '';
|
||||
let dbNameValue: string = '';
|
||||
|
||||
const connectionPageInfo = connectionPageInfoMapping.get(this._currentDataSourceType);
|
||||
|
||||
let sourceServerInfoComponents: azdata.FormComponent[] = [];
|
||||
|
||||
this._serverNameInput = this._modelBuilder.inputBox().withProps({
|
||||
value: serverNameValue
|
||||
}).component();
|
||||
sourceServerInfoComponents.push({
|
||||
component: this._serverNameInput,
|
||||
title: connectionPageInfo.serverNameTitle,
|
||||
required: true
|
||||
});
|
||||
|
||||
this._databaseNameInput = this._modelBuilder.inputBox().withProps({
|
||||
value: dbNameValue
|
||||
}).component();
|
||||
sourceServerInfoComponents.push({
|
||||
component: this._databaseNameInput,
|
||||
title: connectionPageInfo.databaseNameTitle,
|
||||
required: connectionPageInfo.isDbRequired
|
||||
});
|
||||
|
||||
this._sourceServerInfoComponentsFormGroup = {
|
||||
components: sourceServerInfoComponents,
|
||||
title: localize('serverFields', 'Server Connection')
|
||||
};
|
||||
}
|
||||
|
||||
// Credential fields
|
||||
public async buildCredentialComponentsFormGroup(): Promise<void> {
|
||||
let credentialNames = this._dataModel.existingCredentials ?
|
||||
this._dataModel.existingCredentials.map(cred => cred.credentialName) : [];
|
||||
credentialNames.unshift(this._createCredLabel);
|
||||
|
||||
let credDropDownValues: string[] = credentialNames;
|
||||
let credDropDownValue: string = this._createCredLabel;
|
||||
let credDropDownRequired: boolean = true;
|
||||
let credNameValue: string = '';
|
||||
let credNameRequired: boolean = true;
|
||||
let usernameValue: string = '';
|
||||
let usernameRequired: boolean = true;
|
||||
let passwordValue: string = '';
|
||||
let passwordRequired: boolean = true;
|
||||
|
||||
let credentialComponents: (azdata.FormComponent & { layout?: azdata.FormItemLayout })[] = [];
|
||||
|
||||
this._existingCredDropdown = this._modelBuilder.dropDown().withProps({
|
||||
values: credDropDownValues,
|
||||
value: credDropDownValue,
|
||||
}).component();
|
||||
this._existingCredDropdown.onValueChanged(async (selection) => {
|
||||
if (selection.selected === this._createCredLabel) {
|
||||
await this.toggleCredentialInputs(true);
|
||||
} else {
|
||||
await this.toggleCredentialInputs(false);
|
||||
await this._credentialNameInput.updateProperties({ value: '' });
|
||||
let credential = this._dataModel.existingCredentials.find(cred => cred.credentialName === selection.selected);
|
||||
await this._usernameInput.updateProperties({ value: credential ? credential.username : '' });
|
||||
await this._passwordInput.updateProperties({ value: '' });
|
||||
}
|
||||
});
|
||||
|
||||
credentialComponents.push({
|
||||
component: this._existingCredDropdown,
|
||||
title: localize('credentialNameDropdown', 'Choose Credential'),
|
||||
required: credDropDownRequired,
|
||||
layout: this._existingCredDropdownLayout
|
||||
});
|
||||
|
||||
this._credentialNameInput = this._modelBuilder.inputBox().withProps({
|
||||
value: credNameValue,
|
||||
}).component();
|
||||
|
||||
credentialComponents.push({
|
||||
component: this._credentialNameInput,
|
||||
title: localize('credentialNameInput', 'New Credential Name'),
|
||||
required: credNameRequired
|
||||
});
|
||||
|
||||
this._usernameInput = this._modelBuilder.inputBox().withProps({
|
||||
value: usernameValue,
|
||||
}).component();
|
||||
|
||||
credentialComponents.push({
|
||||
component: this._usernameInput,
|
||||
title: localize('usernameInput', 'Username'),
|
||||
required: usernameRequired
|
||||
});
|
||||
|
||||
this._passwordInput = this._modelBuilder.inputBox().withProps({
|
||||
value: passwordValue,
|
||||
inputType: 'password'
|
||||
}).component();
|
||||
|
||||
credentialComponents.push({
|
||||
component: this._passwordInput,
|
||||
title: localize('passwordInput', 'Password'),
|
||||
required: passwordRequired
|
||||
});
|
||||
|
||||
this._credentialComponentsFormGroup = {
|
||||
components: credentialComponents,
|
||||
title: localize('credentialFields', 'Configure Credential')
|
||||
};
|
||||
}
|
||||
|
||||
public async validate(): Promise<boolean> {
|
||||
let inputValues = this._vdiManager.getVirtualizeDataInput(this);
|
||||
return this._dataModel.validateInput(inputValues);
|
||||
}
|
||||
|
||||
public getPage(): azdata.window.WizardPage {
|
||||
return this._page;
|
||||
}
|
||||
|
||||
public async updatePage(): Promise<void> {
|
||||
let newDataSourceType = this._vdiManager.sourceServerType;
|
||||
let newDestDbName = this._vdiManager.destinationDatabaseName;
|
||||
if ((newDataSourceType && this._currentDataSourceType !== newDataSourceType)
|
||||
|| (newDestDbName && this._currentDestDbName !== newDestDbName)) {
|
||||
this._currentDataSourceType = newDataSourceType;
|
||||
this._currentDestDbName = newDestDbName;
|
||||
await this.buildMainContainer();
|
||||
}
|
||||
}
|
||||
|
||||
private async toggleCredentialInputs(enable: boolean): Promise<void> {
|
||||
await this._credentialNameInput.updateProperties({ enabled: enable });
|
||||
await this._usernameInput.updateProperties({ enabled: enable });
|
||||
await this._passwordInput.updateProperties({ enabled: enable });
|
||||
}
|
||||
|
||||
public getInputValues(existingInput: VirtualizeDataInput): void {
|
||||
if (!this._dataSourceNameDropDown) { return; }
|
||||
|
||||
let isNewDataSource: boolean = this._serverNameInput ? this._serverNameInput.enabled : undefined;
|
||||
let dataSourceName: string = this._dataSourceNameDropDown ? getDropdownValue(this._dataSourceNameDropDown.value) : undefined;
|
||||
if (isNewDataSource) {
|
||||
existingInput.newDataSourceName = dataSourceName;
|
||||
let isNewCredential: boolean = this._existingCredDropdown ?
|
||||
this._existingCredDropdown.value === this._createCredLabel : undefined;
|
||||
if (isNewCredential) {
|
||||
existingInput.newCredentialName = this._credentialNameInput ? this._credentialNameInput.value : undefined;
|
||||
existingInput.sourceUsername = this._usernameInput ? this._usernameInput.value : undefined;
|
||||
existingInput.sourcePassword = this._passwordInput ? this._passwordInput.value : undefined;
|
||||
} else {
|
||||
existingInput.existingCredentialName = this._existingCredDropdown ?
|
||||
getDropdownValue(this._existingCredDropdown.value) : undefined;
|
||||
}
|
||||
} else {
|
||||
existingInput.existingDataSourceName = dataSourceName;
|
||||
existingInput.existingCredentialName = this._existingCredDropdown ?
|
||||
getDropdownValue(this._existingCredDropdown.value) : undefined;
|
||||
}
|
||||
existingInput.sourceServerName = this._serverNameInput ? this._serverNameInput.value : undefined;
|
||||
existingInput.sourceDatabaseName = this._databaseNameInput ? this._databaseNameInput.value : undefined;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
import { IWizardPageWrapper } from '../wizardPageWrapper';
|
||||
import { VirtualizeDataModel } from './virtualizeDataModel';
|
||||
import { VirtualizeDataInput } from '../../services/contracts';
|
||||
import { VDIManager } from './virtualizeDataInputManager';
|
||||
import { AppContext } from '../../appContext';
|
||||
|
||||
export class MasterKeyUiElements {
|
||||
public masterKeyPasswordInput: azdata.InputBoxComponent;
|
||||
public masterKeyPasswordConfirmInput: azdata.InputBoxComponent;
|
||||
}
|
||||
export class CreateMasterKeyPage implements IWizardPageWrapper {
|
||||
private _page: azdata.window.WizardPage;
|
||||
private _uiElements: MasterKeyUiElements;
|
||||
|
||||
private readonly _masterKeyExistsMsg = localize('masterKeyExistsMsg', 'A Master Key already exists for the selected database. No action is required on this page.');
|
||||
|
||||
public constructor(private _dataModel: VirtualizeDataModel, private _vdiManager: VDIManager, private _appContext: AppContext) {
|
||||
this.buildPage();
|
||||
}
|
||||
|
||||
public setUi(ui: MasterKeyUiElements): void {
|
||||
this._uiElements = ui;
|
||||
}
|
||||
|
||||
private buildPage(): void {
|
||||
this._page = this._appContext.apiWrapper.createWizardPage(localize('createMasterKeyTitle', 'Create Database Master Key'));
|
||||
this._page.description = localize(
|
||||
'createMasterKeyDescription',
|
||||
'A master key is required. This secures the credentials used by an External Data Source. Note that you should back up the master key by using BACKUP MASTER KEY and store the backup in a secure, off-site location.');
|
||||
|
||||
this._page.registerContent(async (modelView) => {
|
||||
let ui = new MasterKeyUiElements();
|
||||
let builder = modelView.modelBuilder;
|
||||
let allComponents: (azdata.FormComponent | azdata.FormComponentGroup)[] = [];
|
||||
|
||||
// Master key fields
|
||||
ui.masterKeyPasswordInput = builder.inputBox().withProperties({
|
||||
inputType: 'password'
|
||||
}).component();
|
||||
ui.masterKeyPasswordConfirmInput = builder.inputBox().withProperties({
|
||||
inputType: 'password'
|
||||
}).component();
|
||||
allComponents.push({
|
||||
components:
|
||||
[
|
||||
{
|
||||
component: ui.masterKeyPasswordInput,
|
||||
title: localize('masterKeyPasswordInput', 'Password'),
|
||||
required: true
|
||||
},
|
||||
{
|
||||
component: ui.masterKeyPasswordConfirmInput,
|
||||
title: localize('masterKeyPasswordConfirmInput', 'Confirm Password'),
|
||||
required: true
|
||||
}
|
||||
],
|
||||
title: localize('masterKeyPasswordLabel', 'Set the Master Key password.')
|
||||
});
|
||||
|
||||
let formContainer = builder.formContainer()
|
||||
.withFormItems(allComponents,
|
||||
{
|
||||
horizontal: true,
|
||||
componentWidth: '600px'
|
||||
}).component();
|
||||
|
||||
let pwdReminderText = builder.text().withProperties({
|
||||
value: localize('pwdReminderText', 'Strong passwords use a combination of alphanumeric, upper, lower, and special characters.')
|
||||
}).component();
|
||||
|
||||
let flexContainer = builder.flexContainer().withLayout({
|
||||
flexFlow: 'column',
|
||||
alignItems: 'stretch',
|
||||
height: '100%',
|
||||
width: '100%'
|
||||
}).component();
|
||||
|
||||
flexContainer.addItem(formContainer, { CSSStyles: { 'padding': '0px' } });
|
||||
flexContainer.addItem(pwdReminderText, { CSSStyles: { 'padding': '10px 0 0 30px' } });
|
||||
|
||||
this.setUi(ui);
|
||||
await modelView.initializeModel(flexContainer);
|
||||
});
|
||||
}
|
||||
|
||||
public async validate(): Promise<boolean> {
|
||||
if (this._uiElements.masterKeyPasswordInput.value === this._uiElements.masterKeyPasswordConfirmInput.value) {
|
||||
let inputValues = this._vdiManager.getVirtualizeDataInput(this);
|
||||
return this._dataModel.validateInput(inputValues);
|
||||
} else {
|
||||
this._dataModel.showWizardError(localize('passwordMismatchWithConfirmError', 'Password values do not match.'));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public getPage(): azdata.window.WizardPage {
|
||||
return this._page;
|
||||
}
|
||||
|
||||
public async updatePage(): Promise<void> {
|
||||
let hasMasterKey: boolean = await this._dataModel.hasMasterKey();
|
||||
this._uiElements.masterKeyPasswordInput.updateProperties({ enabled: !hasMasterKey, required: !hasMasterKey });
|
||||
this._uiElements.masterKeyPasswordConfirmInput.updateProperties({ enabled: !hasMasterKey, required: !hasMasterKey });
|
||||
|
||||
if (hasMasterKey) {
|
||||
this._dataModel.showWizardInfo(this._masterKeyExistsMsg);
|
||||
}
|
||||
}
|
||||
|
||||
public getInputValues(existingInput: VirtualizeDataInput): void {
|
||||
existingInput.destDbMasterKeyPwd = (this._uiElements && this._uiElements.masterKeyPasswordInput) ?
|
||||
this._uiElements.masterKeyPasswordInput.value : undefined;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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 * as azdata from 'azdata';
|
||||
import * as nls from 'vscode-nls';
|
||||
import * as localizedConstants from '../../localizedConstants';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
import { IWizardPageWrapper } from '../wizardPageWrapper';
|
||||
import { VirtualizeDataModel } from './virtualizeDataModel';
|
||||
import { VirtualizeDataInput } from '../../services/contracts';
|
||||
import { getDropdownValue } from '../../utils';
|
||||
import { AppContext } from '../../appContext';
|
||||
import { VDIManager } from './virtualizeDataInputManager';
|
||||
import { VirtualizeDataWizard } from './virtualizeDataWizard';
|
||||
import { CreateMasterKeyPage } from './createMasterKeyPage';
|
||||
|
||||
export class SelectDataSourcePage implements IWizardPageWrapper {
|
||||
private readonly SqlServerType = localizedConstants.SqlServerName;
|
||||
private readonly DefaultType = localize('defaultSourceType', 'Default');
|
||||
private readonly IconsConfig: {} = {};
|
||||
|
||||
private _dataModel: VirtualizeDataModel;
|
||||
private _vdiManager: VDIManager;
|
||||
private _appContext: AppContext;
|
||||
|
||||
private _page: azdata.window.WizardPage;
|
||||
|
||||
private _modelBuilder: azdata.ModelBuilder;
|
||||
private _formContainer: azdata.FormBuilder;
|
||||
|
||||
private _loadingSpinner: azdata.LoadingComponent;
|
||||
private _destDBDropDown: azdata.DropDownComponent;
|
||||
private _selectedSourceType: string;
|
||||
|
||||
private _componentsAreSetup: boolean;
|
||||
private _modelInitialized: boolean;
|
||||
|
||||
constructor(private _virtualizeDataWizard: VirtualizeDataWizard) {
|
||||
if (this._virtualizeDataWizard) {
|
||||
this._dataModel = _virtualizeDataWizard.dataModel;
|
||||
this._vdiManager = _virtualizeDataWizard.vdiManager;
|
||||
this._appContext = _virtualizeDataWizard.appContext;
|
||||
}
|
||||
|
||||
this._componentsAreSetup = false;
|
||||
this._modelInitialized = false;
|
||||
|
||||
this.IconsConfig[this.SqlServerType] = {
|
||||
light: 'resources/light/server.svg',
|
||||
dark: 'resources/dark/server_inverse.svg'
|
||||
};
|
||||
this.IconsConfig[this.DefaultType] = {
|
||||
light: 'resources/light/database.svg',
|
||||
dark: 'resources/dark/database_inverse.svg'
|
||||
};
|
||||
|
||||
this._page = azdata.window.createWizardPage(localize('selectDataSrcTitle', 'Select a Data Source'));
|
||||
|
||||
this._page.registerContent(async (modelView) => {
|
||||
this._modelBuilder = modelView.modelBuilder;
|
||||
this._formContainer = this._modelBuilder.formContainer();
|
||||
|
||||
let parentLayout: azdata.FormItemLayout = {
|
||||
horizontal: false
|
||||
};
|
||||
|
||||
this._destDBDropDown = this._modelBuilder.dropDown().withProps({
|
||||
values: [],
|
||||
value: '',
|
||||
height: undefined,
|
||||
width: undefined
|
||||
}).component();
|
||||
|
||||
this._loadingSpinner = this._modelBuilder.loadingComponent()
|
||||
.withItem(this._destDBDropDown)
|
||||
.withProps({ loading: true })
|
||||
.component();
|
||||
|
||||
this._formContainer.addFormItem({
|
||||
component: this._loadingSpinner,
|
||||
title: localize('destDBLabel', 'Select the destination database for your external table')
|
||||
},
|
||||
Object.assign({ info: localize('destDBHelpText', 'The database in which to create your External Data Source.') },
|
||||
parentLayout)
|
||||
);
|
||||
|
||||
await modelView.initializeModel(this._formContainer.component());
|
||||
|
||||
this._modelInitialized = true;
|
||||
await this.setupPageComponents();
|
||||
});
|
||||
}
|
||||
|
||||
public async setupPageComponents(): Promise<void> {
|
||||
if (!this._componentsAreSetup && this._modelInitialized && this._dataModel.configInfoResponse) {
|
||||
this._componentsAreSetup = true;
|
||||
|
||||
let parentLayout: azdata.FormItemLayout = {
|
||||
horizontal: false
|
||||
};
|
||||
|
||||
// Destination DB
|
||||
let databaseList: string[] = this._dataModel.destDatabaseList.map(db => db.name).sort((a, b) => a.localeCompare(b));
|
||||
let connectedDatabase = this._dataModel.connection.databaseName;
|
||||
let selectedDatabase: string;
|
||||
if (connectedDatabase && databaseList.some(name => name === connectedDatabase)) {
|
||||
selectedDatabase = connectedDatabase;
|
||||
} else {
|
||||
selectedDatabase = databaseList.length > 0 ? databaseList[0] : '';
|
||||
}
|
||||
|
||||
await this._destDBDropDown.updateProperties({
|
||||
values: databaseList,
|
||||
value: selectedDatabase
|
||||
});
|
||||
await this.toggleCreateMasterKeyPage(getDropdownValue(this._destDBDropDown.value));
|
||||
this._destDBDropDown.onValueChanged(async (selection) => {
|
||||
await this.toggleCreateMasterKeyPage(selection.selected);
|
||||
});
|
||||
|
||||
await this._loadingSpinner.updateProperties({
|
||||
loading: false
|
||||
});
|
||||
|
||||
// Source Type
|
||||
let components: azdata.FormComponent[] = [];
|
||||
let info = this._dataModel.configInfoResponse;
|
||||
const cards: azdata.RadioCard[] = [];
|
||||
info.supportedSourceTypes.forEach(sourceType => {
|
||||
let typeName = sourceType.typeName;
|
||||
let iconTypeName: string;
|
||||
if (this.IconsConfig[typeName]) {
|
||||
iconTypeName = typeName;
|
||||
} else {
|
||||
iconTypeName = this.DefaultType;
|
||||
}
|
||||
|
||||
let iconPath = this._appContext ?
|
||||
{
|
||||
light: this._appContext.extensionContext.asAbsolutePath(this.IconsConfig[iconTypeName].light),
|
||||
dark: this._appContext.extensionContext.asAbsolutePath(this.IconsConfig[iconTypeName].dark)
|
||||
} : undefined;
|
||||
|
||||
cards.push({
|
||||
id: typeName,
|
||||
descriptions: [{ textValue: typeName }],
|
||||
icon: iconPath
|
||||
});
|
||||
});
|
||||
|
||||
const cardGroup = this._modelBuilder.radioCardGroup().withProps({
|
||||
cards: cards,
|
||||
cardWidth: '150px',
|
||||
cardHeight: '160px',
|
||||
iconWidth: '50px',
|
||||
iconHeight: '50px'
|
||||
}).component();
|
||||
|
||||
cardGroup.onSelectionChanged((e: azdata.RadioCardSelectionChangedEvent) => {
|
||||
this._selectedSourceType = e.cardId;
|
||||
});
|
||||
|
||||
if (cards.length > 0) {
|
||||
cardGroup.selectedCardId = cards[0].id;
|
||||
}
|
||||
|
||||
components.push({
|
||||
component: cardGroup,
|
||||
title: localize('sourceCardsLabel', 'Select your data source type')
|
||||
});
|
||||
this._formContainer.addFormItems(components, parentLayout);
|
||||
|
||||
this._dataModel.wizard.nextButton.enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
public async validate(): Promise<boolean> {
|
||||
let inputValues = this._vdiManager.getVirtualizeDataInput(this);
|
||||
if (!inputValues.sourceServerType) {
|
||||
this._dataModel.showWizardError(localize('noServerTypeError', 'A data source type must be selected.'));
|
||||
return false;
|
||||
}
|
||||
if (!inputValues.destDatabaseName) {
|
||||
this._dataModel.showWizardError(localize('noDestDatabaseError', 'A destination database must be selected.'));
|
||||
return false;
|
||||
}
|
||||
|
||||
return await this._dataModel.validateInput(inputValues);
|
||||
}
|
||||
|
||||
private async toggleCreateMasterKeyPage(dbSelected: string): Promise<void> {
|
||||
if (!dbSelected || !this._virtualizeDataWizard || !this._virtualizeDataWizard.wizard
|
||||
|| !this._virtualizeDataWizard.wizard.pages) { return; }
|
||||
let databaseListWithMasterKey: string[] = this._dataModel.destDatabaseList.filter(db => db.hasMasterKey).map(db => db.name) || [];
|
||||
let currentPages = this._virtualizeDataWizard.wizard.pages;
|
||||
let currentWrappers = currentPages.map(p => p['owner']);
|
||||
if (databaseListWithMasterKey.find(e => e === dbSelected)) {
|
||||
let indexToRemove = currentWrappers.findIndex(w => w instanceof CreateMasterKeyPage);
|
||||
if (indexToRemove >= 0) {
|
||||
await this._virtualizeDataWizard.wizard.removePage(indexToRemove);
|
||||
}
|
||||
} else if (!currentWrappers.find(w => w instanceof CreateMasterKeyPage)) {
|
||||
let thisWrapperIndex = currentWrappers.findIndex(w => Object.is(w, this));
|
||||
let createMasterKeyPageWrapper = this._virtualizeDataWizard.wizardPageWrappers.find(w => w instanceof CreateMasterKeyPage);
|
||||
await this._virtualizeDataWizard.wizard.addPage(createMasterKeyPageWrapper.getPage(), thisWrapperIndex + 1);
|
||||
}
|
||||
}
|
||||
|
||||
public getPage(): azdata.window.WizardPage {
|
||||
return this._page;
|
||||
}
|
||||
|
||||
public async updatePage(): Promise<void> {
|
||||
return;
|
||||
}
|
||||
|
||||
public getInputValues(existingInput: VirtualizeDataInput): void {
|
||||
existingInput.destDatabaseName = (this._destDBDropDown && this._destDBDropDown.value) ?
|
||||
getDropdownValue(this._destDBDropDown.value) : undefined;
|
||||
existingInput.sourceServerType = this._selectedSourceType;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
import { IWizardPageWrapper } from '../wizardPageWrapper';
|
||||
import { VirtualizeDataModel } from './virtualizeDataModel';
|
||||
import { VirtualizeDataInput } from '../../services/contracts';
|
||||
import { VDIManager } from './virtualizeDataInputManager';
|
||||
import { AppContext } from '../../appContext';
|
||||
|
||||
export class SummaryUiElements {
|
||||
public destDBLabel: azdata.TextComponent;
|
||||
public summaryTable: azdata.DeclarativeTableComponent;
|
||||
}
|
||||
|
||||
export class SummaryPage implements IWizardPageWrapper {
|
||||
private _page: azdata.window.WizardPage;
|
||||
|
||||
private _uiElements: SummaryUiElements;
|
||||
|
||||
private readonly _taskLabel = localize('virtualizeTaskLabel', 'Virtualize Data');
|
||||
|
||||
constructor(private _dataModel: VirtualizeDataModel, private _vdiManager: VDIManager, private _appContext: AppContext) {
|
||||
this._page = this._appContext.apiWrapper.createWizardPage(localize('summaryPageTitle', 'Summary'));
|
||||
this._page.registerContent(async (modelView) => {
|
||||
let ui = new SummaryUiElements();
|
||||
let builder = modelView.modelBuilder;
|
||||
let components: azdata.FormComponent[] = [];
|
||||
|
||||
ui.destDBLabel = builder.text().withProperties({
|
||||
value: ''
|
||||
}).component();
|
||||
components.push({
|
||||
component: ui.destDBLabel,
|
||||
title: localize('summaryDestDb', 'Destination Database:')
|
||||
});
|
||||
|
||||
let tableData = [['', '']];
|
||||
ui.summaryTable = builder.declarativeTable()
|
||||
.withProperties({
|
||||
columns: [{
|
||||
displayName: localize('summaryObjTypeLabel', 'Object type'),
|
||||
valueType: azdata.DeclarativeDataType.string,
|
||||
width: '300px',
|
||||
isReadOnly: true
|
||||
}, {
|
||||
displayName: localize('summaryObjNameLabel', 'Name'),
|
||||
valueType: azdata.DeclarativeDataType.string,
|
||||
width: '300px',
|
||||
isReadOnly: true
|
||||
}
|
||||
],
|
||||
data: tableData
|
||||
}).component();
|
||||
components.push({
|
||||
component: ui.summaryTable,
|
||||
title: localize('summaryTitle', 'The following objects will be created in the destination database:')
|
||||
});
|
||||
|
||||
let form = builder.formContainer()
|
||||
.withFormItems(components, {
|
||||
horizontal: false
|
||||
})
|
||||
.withLayout({
|
||||
width: '800px'
|
||||
}).component();
|
||||
|
||||
this.setUi(ui);
|
||||
await modelView.initializeModel(form);
|
||||
});
|
||||
}
|
||||
|
||||
public setUi(ui: SummaryUiElements): void {
|
||||
this._uiElements = ui;
|
||||
}
|
||||
|
||||
public async validate(): Promise<boolean> {
|
||||
this._dataModel.wizard.registerOperation({
|
||||
connection: undefined,
|
||||
displayName: this._taskLabel,
|
||||
description: this._taskLabel,
|
||||
isCancelable: false,
|
||||
operation: op => {
|
||||
op.updateStatus(azdata.TaskStatus.InProgress, localize('virtualizeTaskStart', 'Executing script...'));
|
||||
|
||||
let inputValues = this._vdiManager.getVirtualizeDataInput();
|
||||
this._dataModel.processInput(inputValues).then(response => {
|
||||
if (!response.isSuccess) {
|
||||
op.updateStatus(azdata.TaskStatus.Failed, localize('createSourceError', 'External Table creation failed'));
|
||||
if (response.errorMessages) {
|
||||
this._appContext.apiWrapper.showErrorMessage(response.errorMessages.join('\n'));
|
||||
}
|
||||
} else {
|
||||
op.updateStatus(azdata.TaskStatus.Succeeded, localize('createSourceInfo', 'External Table creation completed successfully'));
|
||||
let serverName = this._dataModel.connection.serverName;
|
||||
let databaseName = inputValues.destDatabaseName;
|
||||
let nodePath = `${serverName}/Databases/${databaseName}/Tables`;
|
||||
let username = this._dataModel.connection.userName;
|
||||
SummaryPage.refreshExplorerNode(nodePath, '/', username);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Always return true, so that wizard closes.
|
||||
return true;
|
||||
}
|
||||
|
||||
private static async refreshExplorerNode(nodePath: string, delimiter: string, username?: string): Promise<boolean> {
|
||||
if (!nodePath || !delimiter) { return false; }
|
||||
let refreshNodePath = nodePath.split(delimiter);
|
||||
if (!refreshNodePath || refreshNodePath.length === 0) { return false; }
|
||||
|
||||
let isSuccess: boolean = false;
|
||||
try {
|
||||
let targetNodes: azdata.objectexplorer.ObjectExplorerNode[] = undefined;
|
||||
let nodes = await azdata.objectexplorer.getActiveConnectionNodes();
|
||||
if (nodes && username) {
|
||||
nodes = nodes.filter(n => n.label.endsWith(` - ${username})`));
|
||||
}
|
||||
let currentNodePath: string = undefined;
|
||||
for (let i = 0; i < refreshNodePath.length; ++i) {
|
||||
if (nodes && nodes.length > 0) {
|
||||
currentNodePath = currentNodePath ? `${currentNodePath}/${refreshNodePath[i]}` : refreshNodePath[i];
|
||||
let currentNodes = nodes.filter(node => node.nodePath === currentNodePath);
|
||||
if (currentNodes && currentNodes.length > 0) {
|
||||
targetNodes = currentNodes;
|
||||
let newNodes = [];
|
||||
for (let n of targetNodes) { newNodes = newNodes.concat(await n.getChildren()); }
|
||||
nodes = newNodes;
|
||||
} else {
|
||||
nodes = undefined;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (targetNodes && targetNodes.length > 0) {
|
||||
for (let n of targetNodes) { await n.refresh(); }
|
||||
isSuccess = true;
|
||||
}
|
||||
} catch { }
|
||||
return isSuccess;
|
||||
}
|
||||
|
||||
public getPage(): azdata.window.WizardPage {
|
||||
return this._page;
|
||||
}
|
||||
|
||||
public async updatePage(): Promise<void> {
|
||||
let summary = this._vdiManager.getVirtualizeDataInput();
|
||||
if (summary) {
|
||||
await this._uiElements.destDBLabel.updateProperties({
|
||||
value: summary.destDatabaseName
|
||||
});
|
||||
|
||||
let tableData = this.getTableData(summary);
|
||||
await this._uiElements.summaryTable.updateProperties({
|
||||
data: tableData
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private getTableData(summary: VirtualizeDataInput): string[][] {
|
||||
let data = [];
|
||||
if (summary.destDbMasterKeyPwd) {
|
||||
let mdash = '\u2014';
|
||||
data.push([localize('summaryMasterKeyLabel', 'Database Master Key'), mdash]);
|
||||
}
|
||||
if (summary.newCredentialName) {
|
||||
data.push([localize('summaryCredLabel', 'Database Scoped Credential'), summary.newCredentialName]);
|
||||
}
|
||||
if (summary.newDataSourceName) {
|
||||
data.push([localize('summaryDataSrcLabel', 'External Data Source'), summary.newDataSourceName]);
|
||||
}
|
||||
if (summary.newSchemas) {
|
||||
for (let schemaName of summary.newSchemas) {
|
||||
data.push([localize('summaryNewSchemaLabel', 'Schema'), schemaName]);
|
||||
}
|
||||
}
|
||||
if (summary.externalTableInfoList) {
|
||||
let labelText: string = localize('summaryExternalTableLabel', 'External Table');
|
||||
for (let tableInfo of summary.externalTableInfoList) {
|
||||
data.push([labelText, tableInfo.externalTableName.join('.')]);
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
public getInputValues(existingInput: VirtualizeDataInput): void {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ConnectionDetailsPage } from "./connectionDetailsPage";
|
||||
import { CreateMasterKeyPage } from "./createMasterKeyPage";
|
||||
import { IWizardPageWrapper } from "../wizardPageWrapper";
|
||||
import { ObjectMappingPage } from "./objectMappingPage";
|
||||
import { SelectDataSourcePage } from "./selectDataSourcePage";
|
||||
import { VirtualizeDataModel } from "./virtualizeDataModel";
|
||||
import { VirtualizeDataInput } from "../../services/contracts";
|
||||
|
||||
export class VDIManager {
|
||||
private _selectDataSourcePage: IWizardPageWrapper;
|
||||
private _createMasterKeyPage: IWizardPageWrapper;
|
||||
private _connectionDetailsPage: IWizardPageWrapper;
|
||||
private _objectMappingPage: IWizardPageWrapper;
|
||||
private _pages: IWizardPageWrapper[];
|
||||
|
||||
private _virtualizeDataModel: VirtualizeDataModel;
|
||||
private _propertyLookUp: Map<string, IWizardPageWrapper> = new Map<string, IWizardPageWrapper>();
|
||||
|
||||
public setInputPages(inputPages: IWizardPageWrapper[]): void {
|
||||
if (inputPages && inputPages.length > 0) {
|
||||
this._pages = inputPages;
|
||||
this.setInputPagesInOrder();
|
||||
this.setPropertyLookUp();
|
||||
}
|
||||
}
|
||||
|
||||
private setInputPagesInOrder(): void {
|
||||
this._selectDataSourcePage = this.getSelectDataSourcePage();
|
||||
this._createMasterKeyPage = this.getCreateMasterKeyPage();
|
||||
this._connectionDetailsPage = this.getConnectionDetailsPage();
|
||||
this._objectMappingPage = this.getObjectMappingPage();
|
||||
let inputPages: IWizardPageWrapper[] = [];
|
||||
[
|
||||
this._selectDataSourcePage,
|
||||
this._createMasterKeyPage,
|
||||
this._connectionDetailsPage,
|
||||
this._objectMappingPage
|
||||
].forEach(e => {
|
||||
if (e) { inputPages.push(e); }
|
||||
});
|
||||
this._pages = inputPages;
|
||||
}
|
||||
|
||||
private setPropertyLookUp(): void {
|
||||
if (this._pages && this._pages.length > 0) {
|
||||
this._pages.forEach(page => {
|
||||
if (page instanceof SelectDataSourcePage) {
|
||||
this._propertyLookUp.set('destDatabaseName', page);
|
||||
this._propertyLookUp.set('sourceServerType', page);
|
||||
} else if (page instanceof CreateMasterKeyPage) {
|
||||
this._propertyLookUp.set('destDbMasterKeyPwd', page);
|
||||
} else if (page instanceof ConnectionDetailsPage) {
|
||||
this._propertyLookUp.set('existingCredentialName', page);
|
||||
this._propertyLookUp.set('newCredentialName', page);
|
||||
this._propertyLookUp.set('sourceUsername', page);
|
||||
this._propertyLookUp.set('sourcePassword', page);
|
||||
this._propertyLookUp.set('existingDataSourceName', page);
|
||||
this._propertyLookUp.set('newDataSourceName', page);
|
||||
this._propertyLookUp.set('sourceServerName', page);
|
||||
this._propertyLookUp.set('sourceDatabaseName', page);
|
||||
} else if (page instanceof ObjectMappingPage) {
|
||||
this._propertyLookUp.set('externalTableInfoList', page);
|
||||
}
|
||||
// No inputs set from SummaryPage
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public setVirtualizeDataModel(virtualizeDataModel: VirtualizeDataModel): void {
|
||||
this._virtualizeDataModel = virtualizeDataModel;
|
||||
}
|
||||
|
||||
public getVirtualizeDataInput(upToPage?: IWizardPageWrapper): VirtualizeDataInput {
|
||||
let virtualizeDataInput: VirtualizeDataInput = VDIManager.getEmptyInputInstance();
|
||||
if (this._virtualizeDataModel && this._virtualizeDataModel.configInfoResponse) {
|
||||
virtualizeDataInput.sessionId = this._virtualizeDataModel.configInfoResponse.sessionId;
|
||||
}
|
||||
for (let page of this._pages) {
|
||||
if (page) {
|
||||
page.getInputValues(virtualizeDataInput);
|
||||
if (upToPage && page === upToPage) { break; }
|
||||
}
|
||||
}
|
||||
return virtualizeDataInput;
|
||||
}
|
||||
|
||||
public get virtualizeDataInput(): VirtualizeDataInput {
|
||||
return this.getVirtualizeDataInput();
|
||||
}
|
||||
|
||||
public getPropertyValue(property: string): any {
|
||||
let propertyValue: any = undefined;
|
||||
if (property && this._propertyLookUp.has(property)) {
|
||||
let pageInput = VDIManager.getEmptyInputInstance();
|
||||
this._propertyLookUp.get(property).getInputValues(pageInput);
|
||||
if (pageInput) {
|
||||
propertyValue = pageInput[property];
|
||||
}
|
||||
}
|
||||
return propertyValue;
|
||||
}
|
||||
|
||||
public get dataSourceName(): string {
|
||||
return this.existingDataSourceName || this.newDataSourceName;
|
||||
}
|
||||
|
||||
public get existingDataSourceName(): string {
|
||||
return this.getPropertyValue('existingDataSourceName');
|
||||
}
|
||||
|
||||
public get newDataSourceName(): string {
|
||||
return this.getPropertyValue('newDataSourceName');
|
||||
}
|
||||
|
||||
public get sourceServerName(): string {
|
||||
return this.getPropertyValue('sourceServerName');
|
||||
}
|
||||
|
||||
public get sourceDatabaseName(): string {
|
||||
return this.getPropertyValue('sourceDatabaseName');
|
||||
}
|
||||
|
||||
public get destinationDatabaseName(): string {
|
||||
return this.getPropertyValue('destDatabaseName');
|
||||
}
|
||||
|
||||
public get sourceServerType(): string {
|
||||
return this.getPropertyValue('sourceServerType');
|
||||
}
|
||||
|
||||
public get externalTableInfoList(): string {
|
||||
return this.getPropertyValue('externalTableInfoList');
|
||||
}
|
||||
|
||||
public get destDbMasterKeyPwd(): string {
|
||||
return this.getPropertyValue('destDbMasterKeyPwd');
|
||||
}
|
||||
|
||||
public get inputUptoConnectionDetailsPage(): VirtualizeDataInput {
|
||||
let inputValues: VirtualizeDataInput = undefined;
|
||||
if (this._connectionDetailsPage) {
|
||||
inputValues = this.getVirtualizeDataInput(this._connectionDetailsPage);
|
||||
}
|
||||
return inputValues;
|
||||
}
|
||||
|
||||
private getSelectDataSourcePage(): IWizardPageWrapper {
|
||||
return this._pages.find(page => page instanceof SelectDataSourcePage);
|
||||
}
|
||||
|
||||
private getCreateMasterKeyPage(): IWizardPageWrapper {
|
||||
return this._pages.find(page => page instanceof CreateMasterKeyPage);
|
||||
}
|
||||
|
||||
private getConnectionDetailsPage(): IWizardPageWrapper {
|
||||
return this._pages.find(page => page instanceof ConnectionDetailsPage);
|
||||
}
|
||||
|
||||
private getObjectMappingPage(): IWizardPageWrapper {
|
||||
return this._pages.find(page => page instanceof ObjectMappingPage);
|
||||
}
|
||||
|
||||
public static getEmptyInputInstance(): VirtualizeDataInput {
|
||||
return <VirtualizeDataInput>{};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import * as utils from '../../utils';
|
||||
|
||||
import {
|
||||
DataSourceWizardConfigInfoResponse, DataSourceWizardService, VirtualizeDataInput,
|
||||
ProcessVirtualizeDataInputResponse,
|
||||
GenerateScriptResponse,
|
||||
GetDatabaseInfoResponse,
|
||||
DatabaseInfo,
|
||||
CredentialInfo,
|
||||
GetSourceDatabasesResponse,
|
||||
GetSourceTablesRequestParams,
|
||||
GetSourceTablesResponse,
|
||||
GetSourceColumnDefinitionsRequestParams,
|
||||
ColumnDefinition,
|
||||
ExecutionResult,
|
||||
DataSourceBrowsingParams,
|
||||
SchemaViews,
|
||||
DatabaseOverview
|
||||
} from '../../services/contracts';
|
||||
import { VDIManager } from './virtualizeDataInputManager';
|
||||
|
||||
// Stores important state and service methods used by the Virtualize Data wizard.
|
||||
export class VirtualizeDataModel {
|
||||
|
||||
private _configInfoResponse: DataSourceWizardConfigInfoResponse;
|
||||
private _databaseInfo: { [databaseName: string]: DatabaseInfo };
|
||||
|
||||
constructor(
|
||||
private readonly _connection: azdata.connection.ConnectionProfile,
|
||||
private readonly _wizardService: DataSourceWizardService,
|
||||
private readonly _wizard: azdata.window.Wizard,
|
||||
private readonly _vdiManager: VDIManager) {
|
||||
this._databaseInfo = {};
|
||||
}
|
||||
|
||||
public get connection(): azdata.connection.ConnectionProfile {
|
||||
return this._connection;
|
||||
}
|
||||
|
||||
public get wizardService(): DataSourceWizardService {
|
||||
return this._wizardService;
|
||||
}
|
||||
|
||||
public get wizard(): azdata.window.Wizard {
|
||||
return this._wizard;
|
||||
}
|
||||
|
||||
public get configInfoResponse(): DataSourceWizardConfigInfoResponse {
|
||||
return this._configInfoResponse;
|
||||
}
|
||||
|
||||
public get destDatabaseList(): DatabaseOverview[] {
|
||||
return this._configInfoResponse ? (this._configInfoResponse.databaseList || []) : [];
|
||||
}
|
||||
|
||||
public get sessionId(): string {
|
||||
return this._configInfoResponse ? this._configInfoResponse.sessionId : undefined;
|
||||
}
|
||||
|
||||
public get existingCredentials(): CredentialInfo[] {
|
||||
let currentDbInfo = this._databaseInfo[this.selectedDestDatabaseName];
|
||||
return currentDbInfo ? currentDbInfo.existingCredentials : undefined;
|
||||
}
|
||||
|
||||
private get selectedDestDatabaseName(): string {
|
||||
return this._vdiManager.destinationDatabaseName;
|
||||
}
|
||||
|
||||
public get defaultSchema(): string {
|
||||
let currentDbInfo = this._databaseInfo[this.selectedDestDatabaseName];
|
||||
return currentDbInfo ? currentDbInfo.defaultSchema : undefined;
|
||||
}
|
||||
|
||||
public get schemaList(): string[] {
|
||||
let currentDbInfo = this._databaseInfo[this.selectedDestDatabaseName];
|
||||
return currentDbInfo ? currentDbInfo.schemaList : [];
|
||||
}
|
||||
|
||||
public async hasMasterKey(): Promise<boolean> {
|
||||
let dbInfo = this._databaseInfo[this.selectedDestDatabaseName];
|
||||
if (!dbInfo) {
|
||||
await this.loadDatabaseInfo();
|
||||
dbInfo = this._databaseInfo[this.selectedDestDatabaseName];
|
||||
}
|
||||
return dbInfo.hasMasterKey;
|
||||
}
|
||||
|
||||
public showWizardError(title: string, description?: string): void {
|
||||
this.showWizardMessage(title, description, azdata.window.MessageLevel.Error);
|
||||
}
|
||||
|
||||
public showWizardInfo(title: string, description?: string): void {
|
||||
this.showWizardMessage(title, description, azdata.window.MessageLevel.Information);
|
||||
}
|
||||
|
||||
public showWizardWarning(title: string, description?: string): void {
|
||||
this.showWizardMessage(title, description, azdata.window.MessageLevel.Warning);
|
||||
}
|
||||
|
||||
public showWizardMessage(title: string, description: string, msgLevel: number): void {
|
||||
this._wizard.message = {
|
||||
text: title,
|
||||
level: msgLevel,
|
||||
description: description
|
||||
};
|
||||
}
|
||||
|
||||
public async createSession(): Promise<void> {
|
||||
if (!this._configInfoResponse) {
|
||||
try {
|
||||
let credentials = await azdata.connection.getCredentials(this.connection.connectionId);
|
||||
if (credentials) {
|
||||
Object.assign(this.connection, credentials);
|
||||
}
|
||||
} catch (error) {
|
||||
// swallow this as either it was integrated auth or we will fail later with login failed,
|
||||
// which is a good error that makes sense to the user
|
||||
}
|
||||
|
||||
try {
|
||||
const timeout = vscode.workspace.getConfiguration('mssql').get('query.executionTimeout');
|
||||
this.connection.options['QueryTimeout'] = timeout;
|
||||
this._configInfoResponse = await this.wizardService.createDataSourceWizardSession(this.connection);
|
||||
} catch (error) {
|
||||
this.showWizardError(utils.getErrorMessage(error));
|
||||
this._configInfoResponse = {
|
||||
sessionId: undefined,
|
||||
supportedSourceTypes: [],
|
||||
databaseList: [],
|
||||
serverMajorVersion: -1,
|
||||
productLevel: undefined
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async validateInput(virtualizeDataInput: VirtualizeDataInput): Promise<boolean> {
|
||||
try {
|
||||
let response = await this._wizardService.validateVirtualizeDataInput(virtualizeDataInput);
|
||||
if (!response.isValid) {
|
||||
this.showWizardError(response.errorMessages.join('\n'));
|
||||
}
|
||||
return response.isValid;
|
||||
} catch (error) {
|
||||
this.showWizardError(utils.getErrorMessage(error));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async getDatabaseInfo(databaseName: string): Promise<GetDatabaseInfoResponse> {
|
||||
try {
|
||||
let response = await this._wizardService.getDatabaseInfo({ sessionId: this.sessionId, databaseName: databaseName });
|
||||
if (!response.isSuccess) {
|
||||
this.showWizardError(response.errorMessages.join('\n'));
|
||||
}
|
||||
return response;
|
||||
} catch (error) {
|
||||
let eMessage = utils.getErrorMessage(error);
|
||||
return { isSuccess: false, errorMessages: [eMessage], databaseInfo: undefined };
|
||||
}
|
||||
}
|
||||
|
||||
public async loadDatabaseInfo(databaseName?: string): Promise<DatabaseInfo> {
|
||||
if (!databaseName) {
|
||||
databaseName = this.selectedDestDatabaseName;
|
||||
}
|
||||
let databaseInfo: DatabaseInfo = this._databaseInfo[databaseName];
|
||||
if (databaseInfo === undefined) {
|
||||
let response = await this.getDatabaseInfo(databaseName);
|
||||
if (response.isSuccess) {
|
||||
databaseInfo = response.databaseInfo;
|
||||
this._databaseInfo[databaseName] = databaseInfo;
|
||||
} else {
|
||||
this.showWizardError(response.errorMessages.join('\n'));
|
||||
}
|
||||
}
|
||||
return databaseInfo;
|
||||
}
|
||||
|
||||
public async generateScript(virtualizeDataInput: VirtualizeDataInput): Promise<GenerateScriptResponse> {
|
||||
try {
|
||||
let response = await this._wizardService.generateScript(virtualizeDataInput);
|
||||
if (!response.isSuccess) {
|
||||
this.showWizardError(response.errorMessages.join('\n'));
|
||||
}
|
||||
return response;
|
||||
} catch (error) {
|
||||
let eMessage = utils.getErrorMessage(error);
|
||||
return { isSuccess: false, errorMessages: [eMessage], script: undefined };
|
||||
}
|
||||
}
|
||||
|
||||
public async processInput(virtualizeDataInput: VirtualizeDataInput): Promise<ProcessVirtualizeDataInputResponse> {
|
||||
try {
|
||||
let response = await this._wizardService.processVirtualizeDataInput(virtualizeDataInput);
|
||||
if (!response.isSuccess) {
|
||||
this.showWizardError(response.errorMessages.join('\n'));
|
||||
}
|
||||
return response;
|
||||
} catch (error) {
|
||||
let eMessage = utils.getErrorMessage(error);
|
||||
return { isSuccess: false, errorMessages: [eMessage] };
|
||||
}
|
||||
}
|
||||
|
||||
public async getSourceDatabases(virtualizeDataInput: VirtualizeDataInput): Promise<GetSourceDatabasesResponse> {
|
||||
try {
|
||||
let response = await this._wizardService.getSourceDatabases(virtualizeDataInput);
|
||||
if (!response.isSuccess) {
|
||||
this.showWizardError(response.errorMessages.join('\n'));
|
||||
}
|
||||
return response;
|
||||
} catch (error) {
|
||||
let eMessage = utils.getErrorMessage(error);
|
||||
this.showWizardError(eMessage);
|
||||
return { isSuccess: false, errorMessages: [eMessage], databaseNames: undefined };
|
||||
}
|
||||
}
|
||||
|
||||
public async getSourceTables(requestParams: GetSourceTablesRequestParams): Promise<GetSourceTablesResponse> {
|
||||
try {
|
||||
let response = await this._wizardService.getSourceTables(requestParams);
|
||||
if (!response.isSuccess) {
|
||||
this.showWizardError(response.errorMessages.join('\n'));
|
||||
}
|
||||
return response;
|
||||
} catch (error) {
|
||||
let eMessage = utils.getErrorMessage(error);
|
||||
this.showWizardError(eMessage);
|
||||
return { isSuccess: false, errorMessages: [eMessage], schemaTablesList: undefined };
|
||||
}
|
||||
}
|
||||
|
||||
public async getSourceViewList(requestParams: DataSourceBrowsingParams<string>): Promise<ExecutionResult<SchemaViews[]>> {
|
||||
let result: ExecutionResult<SchemaViews[]> = undefined;
|
||||
try {
|
||||
result = await this._wizardService.getSourceViewList(requestParams);
|
||||
if (!result.isSuccess) {
|
||||
this.showWizardError(result.errorMessages.join('\n'));
|
||||
}
|
||||
} catch (error) {
|
||||
let eMessage = utils.getErrorMessage(error);
|
||||
this.showWizardError(eMessage);
|
||||
result = { isSuccess: false, errorMessages: [eMessage], returnValue: undefined };
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public async getSourceColumnDefinitions(requestParams: GetSourceColumnDefinitionsRequestParams): Promise<ExecutionResult<ColumnDefinition[]>> {
|
||||
let result: ExecutionResult<ColumnDefinition[]> = undefined;
|
||||
try {
|
||||
let response = await this._wizardService.getSourceColumnDefinitions(requestParams);
|
||||
if (response && response.isSuccess) {
|
||||
result = { isSuccess: true, errorMessages: undefined, returnValue: response.columnDefinitions };
|
||||
} else {
|
||||
result = { isSuccess: false, errorMessages: response.errorMessages, returnValue: undefined };
|
||||
}
|
||||
} catch (error) {
|
||||
let eMessage = utils.getErrorMessage(error);
|
||||
result = { isSuccess: false, errorMessages: [eMessage], returnValue: undefined };
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,364 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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';
|
||||
import { Uri, ThemeIcon } from 'vscode';
|
||||
|
||||
export enum TreeCheckboxState {
|
||||
Intermediate = 0,
|
||||
Checked = 1,
|
||||
Unchecked = 2
|
||||
}
|
||||
|
||||
export interface CheckboxTreeNodeArg {
|
||||
treeId?: string;
|
||||
nodeId?: string;
|
||||
isRoot?: boolean;
|
||||
label?: string;
|
||||
maxLabelLength?: number;
|
||||
isLeaf?: boolean;
|
||||
isChecked?: boolean;
|
||||
isEnabled?: boolean;
|
||||
}
|
||||
|
||||
export abstract class CheckboxTreeNode implements azdata.TreeComponentItem {
|
||||
|
||||
protected _onNodeChange = new vscode.EventEmitter<void>();
|
||||
protected _onTreeChange = new vscode.EventEmitter<CheckboxTreeNode>();
|
||||
public readonly onNodeChange: vscode.Event<void> = this._onNodeChange.event;
|
||||
public readonly onTreeChange: vscode.Event<CheckboxTreeNode> = this._onTreeChange.event;
|
||||
|
||||
private _nodeId: string;
|
||||
public label: string;
|
||||
private _isRoot: boolean;
|
||||
private _isLeaf: boolean;
|
||||
private _isChecked: boolean;
|
||||
private _isEnabled: boolean;
|
||||
private _treeId: string;
|
||||
private _maxLabelLength: number;
|
||||
|
||||
private _rootNode: CheckboxTreeNode;
|
||||
private _parent?: CheckboxTreeNode;
|
||||
private _children: CheckboxTreeNode[];
|
||||
|
||||
private static _nodeRegistry: { [treeId: string]: Map<string, CheckboxTreeNode> } = {};
|
||||
|
||||
constructor(treeArg?: CheckboxTreeNodeArg) {
|
||||
this._isRoot = false;
|
||||
this._isLeaf = false;
|
||||
this._isChecked = false;
|
||||
this._isEnabled = true;
|
||||
this.setArgs(treeArg);
|
||||
}
|
||||
|
||||
public setArgs(treeArg: CheckboxTreeNodeArg): void {
|
||||
if (treeArg) {
|
||||
this._isRoot = treeArg.isRoot !== undefined ? treeArg.isRoot : this._isRoot;
|
||||
this._treeId = treeArg.treeId || this._treeId;
|
||||
this._nodeId = this._isRoot ? 'root' : (treeArg.nodeId || this._nodeId);
|
||||
this.label = this._isRoot ? 'root' : (treeArg.label || this.label);
|
||||
this._isLeaf = treeArg.isLeaf !== undefined ? treeArg.isLeaf : this._isLeaf;
|
||||
this._isChecked = treeArg.isChecked !== undefined ? treeArg.isChecked : this._isChecked;
|
||||
this._isEnabled = treeArg.isEnabled !== undefined ? treeArg.isEnabled : this._isEnabled;
|
||||
this._maxLabelLength = treeArg.maxLabelLength || this._maxLabelLength;
|
||||
}
|
||||
CheckboxTreeNode.AddToNodeRegistry(this);
|
||||
}
|
||||
|
||||
public static clearNodeRegistry(): void {
|
||||
CheckboxTreeNode._nodeRegistry = {};
|
||||
}
|
||||
|
||||
private static AddToNodeRegistry(node: CheckboxTreeNode): void {
|
||||
if (node._treeId && node._nodeId) {
|
||||
if (!CheckboxTreeNode._nodeRegistry[node._treeId]) {
|
||||
CheckboxTreeNode._nodeRegistry[node._treeId] = new Map<string, CheckboxTreeNode>();
|
||||
}
|
||||
let registry = CheckboxTreeNode._nodeRegistry[node._treeId];
|
||||
if (!registry.has(node._nodeId)) {
|
||||
registry.set(node._nodeId, node);
|
||||
} else {
|
||||
throw new Error(`tree node with id: '${node._nodeId}' already exists`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static findNode(treeId: string, nodeId: string): CheckboxTreeNode {
|
||||
let wantedNode: CheckboxTreeNode = undefined;
|
||||
if (treeId && nodeId && CheckboxTreeNode._nodeRegistry[treeId] && CheckboxTreeNode._nodeRegistry[treeId].has(nodeId)) {
|
||||
wantedNode = CheckboxTreeNode._nodeRegistry[treeId].get(nodeId);
|
||||
}
|
||||
return wantedNode;
|
||||
}
|
||||
|
||||
public get id(): string {
|
||||
return this._nodeId;
|
||||
}
|
||||
|
||||
public get parent(): CheckboxTreeNode {
|
||||
return this._parent;
|
||||
}
|
||||
|
||||
public get children(): CheckboxTreeNode[] {
|
||||
return this._children;
|
||||
}
|
||||
|
||||
public set children(children: CheckboxTreeNode[]) {
|
||||
if (children) {
|
||||
this._children = children;
|
||||
}
|
||||
}
|
||||
|
||||
public get isRoot(): boolean {
|
||||
return this._isRoot;
|
||||
}
|
||||
|
||||
public get isLeaf(): boolean {
|
||||
return this._isLeaf;
|
||||
}
|
||||
|
||||
public set isLeaf(isLeaf: boolean) {
|
||||
if (isLeaf !== undefined) {
|
||||
this._isLeaf = isLeaf;
|
||||
}
|
||||
}
|
||||
|
||||
public get treeId(): string {
|
||||
return this._treeId;
|
||||
}
|
||||
|
||||
public set treeId(treeId: string) {
|
||||
if (treeId) {
|
||||
this._treeId = treeId;
|
||||
}
|
||||
}
|
||||
|
||||
public get checked(): boolean {
|
||||
return this._isChecked;
|
||||
}
|
||||
|
||||
public get enabled(): boolean {
|
||||
return this._isEnabled;
|
||||
}
|
||||
|
||||
public get hasChildren(): boolean {
|
||||
return this._children !== undefined && this._children.length > 0;
|
||||
}
|
||||
|
||||
protected get rootNode(): CheckboxTreeNode {
|
||||
if (!this._rootNode && this._treeId) {
|
||||
this._rootNode = CheckboxTreeNode._nodeRegistry[this._treeId].get('root');
|
||||
}
|
||||
return this._rootNode;
|
||||
}
|
||||
|
||||
public get collapsibleState(): vscode.TreeItemCollapsibleState {
|
||||
if (!this._isLeaf) {
|
||||
return vscode.TreeItemCollapsibleState.Expanded;
|
||||
} else {
|
||||
vscode.TreeItemCollapsibleState.None;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract get iconPath(): string | Uri | { light: string | Uri; dark: string | Uri } | ThemeIcon;
|
||||
|
||||
public get nodePath(): string {
|
||||
return `${this.parent ? this.parent.nodePath + '-' : ''}${this.id}`;
|
||||
}
|
||||
|
||||
public async setCheckedState(isChecked: boolean): Promise<void> {
|
||||
let nodesToCheck: CheckboxTreeNode[] = [this];
|
||||
while (nodesToCheck && nodesToCheck.length > 0) {
|
||||
let node = nodesToCheck.shift();
|
||||
if (node._isEnabled) {
|
||||
node._isChecked = isChecked;
|
||||
node.notifyStateChanged();
|
||||
if (node.hasChildren) {
|
||||
nodesToCheck = node._children.concat(nodesToCheck);
|
||||
}
|
||||
if (node.parent) {
|
||||
await node.parent.refreshCheckedState();
|
||||
}
|
||||
}
|
||||
}
|
||||
this.notifyStateChanged();
|
||||
}
|
||||
|
||||
public async refreshCheckedState(): Promise<void> {
|
||||
let nodeToRefresh: CheckboxTreeNode = this;
|
||||
while (nodeToRefresh && nodeToRefresh.hasChildren) {
|
||||
if (nodeToRefresh._children.every(c => c.checked)) {
|
||||
if (!nodeToRefresh._isChecked) {
|
||||
nodeToRefresh._isChecked = true;
|
||||
nodeToRefresh.notifyStateChanged();
|
||||
nodeToRefresh = nodeToRefresh.parent;
|
||||
} else {
|
||||
nodeToRefresh = undefined;
|
||||
}
|
||||
} else if (nodeToRefresh._children.every(c => c.checked === false)) {
|
||||
if (nodeToRefresh._isChecked !== false) {
|
||||
nodeToRefresh._isChecked = false;
|
||||
nodeToRefresh.notifyStateChanged();
|
||||
nodeToRefresh = nodeToRefresh.parent;
|
||||
} else {
|
||||
nodeToRefresh = undefined;
|
||||
}
|
||||
} else {
|
||||
if (nodeToRefresh._isChecked !== undefined) {
|
||||
nodeToRefresh._isChecked = undefined;
|
||||
nodeToRefresh.notifyStateChanged();
|
||||
nodeToRefresh = nodeToRefresh.parent;
|
||||
} else {
|
||||
nodeToRefresh = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.notifyStateChanged();
|
||||
}
|
||||
|
||||
public async setEnable(isEnabled: boolean): Promise<void> {
|
||||
if (isEnabled === undefined) {
|
||||
isEnabled = true;
|
||||
}
|
||||
|
||||
let nodesToSet: CheckboxTreeNode[] = [this];
|
||||
while (nodesToSet && nodesToSet.length > 0) {
|
||||
let node = nodesToSet.shift();
|
||||
node._isEnabled = isEnabled;
|
||||
node.notifyStateChanged();
|
||||
if (node.hasChildren) {
|
||||
nodesToSet = node._children.concat(nodesToSet);
|
||||
}
|
||||
if (node.parent) {
|
||||
await node.parent.refreshEnableState();
|
||||
}
|
||||
}
|
||||
this.notifyStateChanged();
|
||||
}
|
||||
|
||||
public async refreshEnableState(): Promise<void> {
|
||||
let nodeToRefresh: CheckboxTreeNode = this;
|
||||
while (nodeToRefresh && nodeToRefresh.hasChildren) {
|
||||
if (nodeToRefresh._children.every(c => c._isEnabled === false)) {
|
||||
if (nodeToRefresh._isEnabled !== false) {
|
||||
nodeToRefresh._isEnabled = false;
|
||||
nodeToRefresh.notifyStateChanged();
|
||||
nodeToRefresh = nodeToRefresh.parent;
|
||||
} else {
|
||||
nodeToRefresh = undefined;
|
||||
}
|
||||
} else {
|
||||
if (!nodeToRefresh._isEnabled) {
|
||||
nodeToRefresh._isEnabled = true;
|
||||
nodeToRefresh.notifyStateChanged();
|
||||
nodeToRefresh = nodeToRefresh.parent;
|
||||
} else {
|
||||
nodeToRefresh = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.notifyStateChanged();
|
||||
}
|
||||
|
||||
public notifyStateChanged(): void {
|
||||
this._onNodeChange.fire();
|
||||
let rootNode = this.rootNode;
|
||||
if (rootNode) {
|
||||
rootNode._onTreeChange.fire(this);
|
||||
}
|
||||
}
|
||||
|
||||
public get checkboxState(): TreeCheckboxState {
|
||||
if (this.checked === undefined) {
|
||||
return TreeCheckboxState.Intermediate;
|
||||
} else {
|
||||
return this.checked ? TreeCheckboxState.Checked : TreeCheckboxState.Unchecked;
|
||||
}
|
||||
}
|
||||
|
||||
public findNode(nodeId: string): CheckboxTreeNode {
|
||||
let wantedNode: CheckboxTreeNode = undefined;
|
||||
if (this.id === nodeId) {
|
||||
wantedNode = this;
|
||||
} else {
|
||||
wantedNode = CheckboxTreeNode.findNode(this._treeId, nodeId);
|
||||
}
|
||||
return wantedNode;
|
||||
}
|
||||
|
||||
public abstract getChildren(): Promise<CheckboxTreeNode[]>;
|
||||
|
||||
public clearChildren(): void {
|
||||
if (this.children) {
|
||||
this.children.forEach(child => {
|
||||
child.clearChildren();
|
||||
});
|
||||
this._children = undefined;
|
||||
this.notifyStateChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public addChildNode(node: CheckboxTreeNode): void {
|
||||
if (node) {
|
||||
if (!this._children) {
|
||||
this._children = [];
|
||||
}
|
||||
node._parent = this;
|
||||
this._children.push(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class CheckboxTreeDataProvider implements azdata.TreeComponentDataProvider<CheckboxTreeNode> {
|
||||
private _onDidChangeTreeData = new vscode.EventEmitter<CheckboxTreeNode>();
|
||||
constructor(private _root: CheckboxTreeNode) {
|
||||
if (this._root) {
|
||||
this._root.onTreeChange(node => {
|
||||
this._onDidChangeTreeData.fire(node);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onDidChangeTreeData?: vscode.Event<CheckboxTreeNode | undefined | null> = this._onDidChangeTreeData.event;
|
||||
|
||||
/**
|
||||
* Get [TreeItem](#TreeItem) representation of the `element`
|
||||
*
|
||||
* @param element The element for which [TreeItem](#TreeItem) representation is asked for.
|
||||
* @return [TreeItem](#TreeItem) representation of the element
|
||||
*/
|
||||
getTreeItem(element: CheckboxTreeNode): azdata.TreeComponentItem | Thenable<azdata.TreeComponentItem> {
|
||||
let item: azdata.TreeComponentItem = {};
|
||||
item.label = element.label;
|
||||
item.checked = element.checked;
|
||||
item.collapsibleState = element.isLeaf ? vscode.TreeItemCollapsibleState.None : vscode.TreeItemCollapsibleState.Collapsed;
|
||||
item.iconPath = element.iconPath;
|
||||
item.enabled = element.enabled;
|
||||
return item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the children of `element` or root if no element is passed.
|
||||
*
|
||||
* @param element The element from which the provider gets children. Can be `undefined`.
|
||||
* @return Children of `element` or root if no element is passed.
|
||||
*/
|
||||
getChildren(element?: CheckboxTreeNode): vscode.ProviderResult<CheckboxTreeNode[]> {
|
||||
if (element) {
|
||||
return element.getChildren();
|
||||
} else {
|
||||
return Promise.resolve(this._root.getChildren());
|
||||
}
|
||||
}
|
||||
|
||||
getParent(element?: CheckboxTreeNode): vscode.ProviderResult<CheckboxTreeNode> {
|
||||
if (element) {
|
||||
return Promise.resolve(element.parent);
|
||||
} else {
|
||||
return Promise.resolve(this._root);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
import { ApiWrapper } from '../../apiWrapper';
|
||||
import { DataSourceWizardService } from '../../services/contracts';
|
||||
import { SelectDataSourcePage } from './selectDataSourcePage';
|
||||
import { ConnectionDetailsPage } from './connectionDetailsPage';
|
||||
import { SummaryPage } from './summaryPage';
|
||||
import { ObjectMappingPage } from './objectMappingPage';
|
||||
import { IWizardPageWrapper } from '../wizardPageWrapper';
|
||||
import { VirtualizeDataModel } from './virtualizeDataModel';
|
||||
import { sqlFileExtension } from '../../constants';
|
||||
import { AppContext } from '../../appContext';
|
||||
import { CreateMasterKeyPage } from './createMasterKeyPage';
|
||||
import { getErrorMessage } from '../../utils';
|
||||
import { VDIManager } from './virtualizeDataInputManager';
|
||||
|
||||
export class VirtualizeDataWizard {
|
||||
private _wizard: azdata.window.Wizard;
|
||||
private _wizardPageWrappers: IWizardPageWrapper[];
|
||||
private _dataModel: VirtualizeDataModel;
|
||||
private _vdiManager: VDIManager;
|
||||
|
||||
constructor(
|
||||
private _connection: azdata.connection.ConnectionProfile,
|
||||
private _wizardService: DataSourceWizardService,
|
||||
private _appContext: AppContext) {
|
||||
}
|
||||
|
||||
public async openWizard(): Promise<void> {
|
||||
await this.initialize();
|
||||
await this._wizard.open();
|
||||
}
|
||||
|
||||
private async initialize(): Promise<void> {
|
||||
this._wizard = azdata.window.createWizard(localize('getExtDataTitle', 'Virtualize Data'));
|
||||
this._wizard.nextButton.enabled = false;
|
||||
|
||||
// TODO: Add placeholder loading page or spinner here
|
||||
this._vdiManager = new VDIManager();
|
||||
this._dataModel = new VirtualizeDataModel(this._connection, this._wizardService, this._wizard, this._vdiManager);
|
||||
await this._dataModel.createSession();
|
||||
|
||||
this._wizardPageWrappers = [
|
||||
new SelectDataSourcePage(this),
|
||||
new CreateMasterKeyPage(this._dataModel, this._vdiManager, this.appContext),
|
||||
new ConnectionDetailsPage(this._dataModel, this._vdiManager, this._appContext),
|
||||
new ObjectMappingPage(this._dataModel, this._vdiManager, this._appContext),
|
||||
new SummaryPage(this._dataModel, this._vdiManager, this._appContext)
|
||||
];
|
||||
|
||||
this._wizardPageWrappers.forEach(w => {
|
||||
let page = w.getPage();
|
||||
if (page) { page['owner'] = w; }
|
||||
});
|
||||
|
||||
this._vdiManager.setInputPages(this._wizardPageWrappers);
|
||||
this._vdiManager.setVirtualizeDataModel(this._dataModel);
|
||||
|
||||
this._wizard.pages = this._wizardPageWrappers.map(wrapper => wrapper.getPage());
|
||||
this._wizard.displayPageTitles = true;
|
||||
|
||||
this._wizard.cancelButton.onClick(() => this.actionClose());
|
||||
|
||||
this._wizard.doneButton.label = localize('doneButtonLabel', 'Create');
|
||||
this._wizard.doneButton.hidden = true;
|
||||
|
||||
this._wizard.generateScriptButton.onClick(async () => await this.actionGenerateScript());
|
||||
this._wizard.generateScriptButton.hidden = true;
|
||||
this._wizard.generateScriptButton.enabled = false;
|
||||
|
||||
this._wizard.registerNavigationValidator(async (info) => await this.actionValidateInputAndUpdateNextPage(info));
|
||||
|
||||
this._wizard.onPageChanged(info => this.actionChangePage(info));
|
||||
}
|
||||
|
||||
private async actionClose(): Promise<void> {
|
||||
try {
|
||||
let sessionId = this._dataModel.sessionId;
|
||||
if (sessionId) {
|
||||
await this._wizardService.disposeWizardSession(sessionId);
|
||||
}
|
||||
} catch (error) {
|
||||
this.apiWrapper.showErrorMessage(error.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private async actionGenerateScript(): Promise<void> {
|
||||
try {
|
||||
// Disable the button while generating the script to prevent an issue where multiple quick
|
||||
// button presses would duplicate the script. (There's no good reason to allow multiple
|
||||
// scripts to be generated anyways)
|
||||
this._wizard.generateScriptButton.enabled = false;
|
||||
let virtualizeDataInput = this._vdiManager.virtualizeDataInput;
|
||||
let response = await this._dataModel.generateScript(virtualizeDataInput);
|
||||
if (response.isSuccess) {
|
||||
let sqlScript: string = response.script;
|
||||
let doc = await this.apiWrapper.openTextDocument({ language: sqlFileExtension, content: sqlScript });
|
||||
await this.apiWrapper.showDocument(doc);
|
||||
await azdata.queryeditor.connect(doc.uri.toString(), this._dataModel.connection.connectionId);
|
||||
|
||||
this._dataModel.showWizardInfo(
|
||||
localize('openScriptMsg',
|
||||
'The script has opened in a document window. You can view it once the wizard is closed.'));
|
||||
} else {
|
||||
let eMessage = response.errorMessages.join('\n');
|
||||
this._dataModel.showWizardError(eMessage);
|
||||
}
|
||||
} catch (error) {
|
||||
this._dataModel.showWizardError(error.toString());
|
||||
// re-enable button if an error occurred since we didn't actually generate a script
|
||||
this._wizard.generateScriptButton.enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private actionChangePage(info: azdata.window.WizardPageChangeInfo): void {
|
||||
this.toggleLastPageButtons(info.newPage === (this._wizard.pages.length - 1));
|
||||
}
|
||||
|
||||
private toggleLastPageButtons(isLastPage: boolean): void {
|
||||
this._wizard.doneButton.hidden = !isLastPage;
|
||||
this._wizard.generateScriptButton.hidden = !isLastPage;
|
||||
this._wizard.generateScriptButton.enabled = isLastPage;
|
||||
}
|
||||
|
||||
private async actionValidateInputAndUpdateNextPage(info: azdata.window.WizardPageChangeInfo): Promise<boolean> {
|
||||
this._wizard.message = undefined;
|
||||
|
||||
// Skip validation for moving to a previous page
|
||||
if (info.newPage < info.lastPage) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
let currentPageWrapper: IWizardPageWrapper = this.GetWizardPageWrapper(info.lastPage);
|
||||
if (!currentPageWrapper || !(await currentPageWrapper.validate())) { return false; }
|
||||
|
||||
if (!info.newPage) { return true; }
|
||||
let newPageWrapper: IWizardPageWrapper = this.GetWizardPageWrapper(info.newPage);
|
||||
if (!newPageWrapper) { return false; }
|
||||
|
||||
await newPageWrapper.updatePage();
|
||||
return true;
|
||||
} catch (error) {
|
||||
this._dataModel.showWizardError(getErrorMessage(error));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private GetWizardPageWrapper(pageIndex: number): IWizardPageWrapper {
|
||||
if (!this._wizard || !this._wizard.pages || this._wizard.pages.length === 0
|
||||
|| pageIndex < 0 || pageIndex >= this._wizard.pages.length) { return undefined; }
|
||||
let wizardPage = this._wizard.pages[pageIndex];
|
||||
return wizardPage && wizardPage['owner'];
|
||||
}
|
||||
|
||||
private get apiWrapper(): ApiWrapper {
|
||||
return this._appContext.apiWrapper;
|
||||
}
|
||||
|
||||
public get appContext(): AppContext {
|
||||
return this._appContext;
|
||||
}
|
||||
|
||||
public get dataModel(): VirtualizeDataModel {
|
||||
return this._dataModel;
|
||||
}
|
||||
|
||||
public get vdiManager(): VDIManager {
|
||||
return this._vdiManager;
|
||||
}
|
||||
|
||||
public get wizard(): azdata.window.Wizard {
|
||||
return this._wizard;
|
||||
}
|
||||
|
||||
public get wizardPageWrappers(): IWizardPageWrapper[] {
|
||||
return this._wizardPageWrappers;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user