Move DacFx wizard into separate extension (#4115)

* Moved dacfx wizard into separate extension

* updating to use azdata

* one more azdata change

* bump import extension version

* renaming extension to dacpac
This commit is contained in:
kisantia
2019-03-06 17:45:30 -08:00
committed by GitHub
parent b8f454b8ac
commit 5f003b0dd7
28 changed files with 473 additions and 66 deletions

View File

@@ -1,8 +1,7 @@
# Microsoft SQL Server Import for Azure Data Studio
Microsoft SQL Server Import for Azure Data Studio includes two wizards:
Microsoft SQL Server Import for Azure Data Studio includes the wizard:
- [Import Flat File Wizard](#import-flat-file-wizard-preview)
- [Data-tier Application Wizard.](#data-tier-application-wizard-preview)
## Import Flat File Wizard *(preview)*
**The Import Flat File Wizard** is a simple way to copy data from a flat file (.csv, .txt, .json) to a SQL Server table. Checkout below the reasons for using the Import Flat File wizard, how to find this wizard, and a simple example.
@@ -24,25 +23,6 @@ This wizard was created to improve the current import experience leveraging an i
PROSE analyzes data patterns in your input file to infer column names, types, delimiters, and more. This framework learns the structure of the file and does all of the hard work so users don't have to.
Please note that the PROSE binary components used by this extension are licensed under the [MICROSOFT SQL TOOLS IMPORT FLAT FILE EULA](https://raw.githubusercontent.com/Microsoft/azuredatastudio/master/extensions/import/Microsoft_SQL_Server_Import_Extension_and_Tools_Import_Flat_File_Preview.docx).
## Data-tier Application Wizard *(preview)*
**The Data-tier Application Wizard** provides an easy to use experience to deploy and extract .dacpac files and import and export .bacpac files.
This experience is currently in its initial preview. Please report issues and feature requests [here.](https://github.com/microsoft/azuredatastudio/issues)
<img src="https://user-images.githubusercontent.com/30873802/49676289-f2df6880-fa2d-11e8-8bfa-6213b7734075.png" width="800px" />
### Requirements
* This wizard requires an active connection to a SQL Server instance to start.
### How do I start the Data-tier Application wizard?
* The main entry point for the wizard is to right click a database in the Object Explorer, and click **Data-tier Application wizard**.
* If a user is connected to a SQL Server instance, the user can also start the wizard from the command palette (Ctrl+Shift+P) by searching for **Data-tier Application wizard.**
### Why would I use the Data-tier Application wizard?
This wizard was created to add the ability to extract and deploy .dacpac files and import and export .bacpac files in Azure Data Studio.
To learn more about Data-Tier Applications and working with dacpac and bacpac files, [you can read more here.](https://docs.microsoft.com/en-us/sql/relational-databases/data-tier-applications/data-tier-applications?view=sql-server-2017)
## License

View File

@@ -2,7 +2,7 @@
"name": "import",
"displayName": "SQL Server Import",
"description": "SQL Server Import for Azure Data Studio supports importing CSV or JSON files into SQL Server.",
"version": "0.6.1",
"version": "0.7.0",
"publisher": "Microsoft",
"preview": true,
"engines": {
@@ -33,15 +33,6 @@
"light": "./images/light_icon.svg",
"dark": "./images/dark_icon.svg"
}
},
{
"command": "dacFx.start",
"title": "Data-tier Application Wizard",
"category": "Data-tier Application",
"icon": {
"light": "./images/light_icon.svg",
"dark": "./images/dark_icon.svg"
}
}
],
"keybindings": [
@@ -57,11 +48,6 @@
"command": "flatFileImport.start",
"when": "connectionProvider == MSSQL && nodeType && nodeType == Database",
"group": "import"
},
{
"command": "dacFx.start",
"when": "connectionProvider == MSSQL && nodeType && nodeType == Database",
"group": "export"
}
]
}

View File

@@ -13,7 +13,6 @@ import { FlatFileWizard } from '../wizard/flatFileWizard';
import { ServiceClient } from '../services/serviceClient';
import { ApiType, managerInstance } from '../services/serviceApiManager';
import { FlatFileProvider } from '../services/contracts';
import { DataTierApplicationWizard } from '../wizard/dataTierApplicationWizard';
/**
* The main controller class that initializes the extension
@@ -36,15 +35,10 @@ export default class MainController extends ControllerBase {
this.initializeFlatFileProvider(provider);
});
this.initializeDacFxWizard();
return Promise.resolve(true);
}
private initializeFlatFileProvider(provider: FlatFileProvider) {
azdata.tasks.registerTask('flatFileImport.start', (profile: azdata.IConnectionProfile, ...args: any[]) => new FlatFileWizard(provider).start(profile, args));
}
private initializeDacFxWizard() {
azdata.tasks.registerTask('dacFx.start', (profile: azdata.IConnectionProfile, ...args: any[]) => new DataTierApplicationWizard().start(profile, args));
}
}

View File

@@ -5,12 +5,12 @@
'use strict';
import * as azdata from 'azdata';
import { BaseDataModel } from './models';
import { ImportDataModel } from './models';
export abstract class BasePage {
protected readonly wizardPage: azdata.window.WizardPage;
protected readonly model: BaseDataModel;
protected readonly model: ImportDataModel;
protected readonly view: azdata.ModelView;
/**

View File

@@ -1,158 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as azdata from 'azdata';
import * as nls from 'vscode-nls';
import * as os from 'os';
import * as path from 'path';
import { DataTierApplicationWizard } from '../dataTierApplicationWizard';
import { DacFxDataModel } from './models';
import { BasePage } from './basePage';
const localize = nls.loadMessageBundle();
export abstract class DacFxConfigPage extends BasePage {
protected readonly wizardPage: azdata.window.WizardPage;
protected readonly instance: DataTierApplicationWizard;
protected readonly model: DacFxDataModel;
protected readonly view: azdata.ModelView;
protected serverDropdown: azdata.DropDownComponent;
protected databaseTextBox: azdata.InputBoxComponent;
protected databaseDropdown: azdata.DropDownComponent;
protected databaseLoader: azdata.LoadingComponent;
protected fileTextBox: azdata.InputBoxComponent;
protected fileButton: azdata.ButtonComponent;
protected fileExtension: string;
protected constructor(instance: DataTierApplicationWizard, wizardPage: azdata.window.WizardPage, model: DacFxDataModel, view: azdata.ModelView) {
super();
this.instance = instance;
this.wizardPage = wizardPage;
this.model = model;
this.view = view;
}
public setupNavigationValidator() {
this.instance.registerNavigationValidator(() => {
return true;
});
}
protected async createServerDropdown(isTargetServer: boolean): Promise<azdata.FormComponent> {
this.serverDropdown = this.view.modelBuilder.dropDown().withProperties({
required: true
}).component();
// Handle server changes
this.serverDropdown.onValueChanged(async () => {
this.model.server = (this.serverDropdown.value as ConnectionDropdownValue).connection;
this.model.serverName = (this.serverDropdown.value as ConnectionDropdownValue).displayName;
await this.populateDatabaseDropdown();
});
let targetServerTitle = localize('dacFx.targetServerDropdownTitle', 'Target Server');
let sourceServerTitle = localize('dacFx.sourceServerDropdownTitle', 'Source Server');
return {
component: this.serverDropdown,
title: isTargetServer ? targetServerTitle : sourceServerTitle
};
}
protected async populateServerDropdown(): Promise<boolean> {
let values = await this.getServerValues();
if (values === undefined) {
return false;
}
this.model.server = values[0].connection;
this.model.serverName = values[0].displayName;
this.serverDropdown.updateProperties({
values: values
});
return true;
}
protected async createDatabaseTextBox(): Promise<azdata.FormComponent> {
this.databaseTextBox = this.view.modelBuilder.inputBox().withProperties({
required: true
}).component();
this.databaseTextBox.onTextChanged(async () => {
this.model.database = this.databaseTextBox.value;
});
return {
component: this.databaseTextBox,
title: localize('dacFx.databaseNameTextBox', 'Target Database')
};
}
protected async createDatabaseDropdown(): Promise<azdata.FormComponent> {
this.databaseDropdown = this.view.modelBuilder.dropDown().withProperties({
required: true
}).component();
// Handle database changes
this.databaseDropdown.onValueChanged(async () => {
this.model.database = (<azdata.CategoryValue>this.databaseDropdown.value).name;
this.fileTextBox.value = this.generateFilePath();
this.model.filePath = this.fileTextBox.value;
});
this.databaseLoader = this.view.modelBuilder.loadingComponent().withItem(this.databaseDropdown).component();
return {
component: this.databaseLoader,
title: localize('dacFx.sourceDatabaseDropdownTitle', 'Source Database')
};
}
protected async populateDatabaseDropdown(): Promise<boolean> {
this.databaseLoader.loading = true;
this.databaseDropdown.updateProperties({ values: [] });
if (!this.model.server) {
this.databaseLoader.loading = false;
return false;
}
let values = await this.getDatabaseValues();
this.model.database = values[0].name;
this.model.filePath = this.generateFilePath();
this.fileTextBox.value = this.model.filePath;
this.databaseDropdown.updateProperties({
values: values
});
this.databaseLoader.loading = false;
return true;
}
protected async createFileBrowserParts() {
this.fileTextBox = this.view.modelBuilder.inputBox().withProperties({
required: true
}).component();
this.fileButton = this.view.modelBuilder.button().withProperties({
label: '•••',
}).component();
}
protected generateFilePath(): string {
let now = new Date();
let datetime = now.getFullYear() + '-' + (now.getMonth() + 1) + '-' + now.getDate() + '-' + now.getHours() + '-' + now.getMinutes();
return path.join(os.homedir(), this.model.database + '-' + datetime + this.fileExtension);
}
}
interface ConnectionDropdownValue extends azdata.CategoryValue {
connection: azdata.connection.Connection;
}

View File

@@ -6,16 +6,12 @@
import * as azdata from 'azdata';
export interface BaseDataModel {
server: azdata.connection.Connection;
serverId: string;
database: string;
}
/**
* The main data model that communicates between the pages.
*/
export interface ImportDataModel extends BaseDataModel {
export interface ImportDataModel {
server: azdata.connection.Connection;
serverId: string;
ownerUri: string;
proseColumns: ColumnMetadata[];
proseDataPreview: string[][];
@@ -35,16 +31,3 @@ export interface ColumnMetadata {
primaryKey: boolean;
nullable: boolean;
}
/**
* Data model to communicate between DacFx pages
*/
export interface DacFxDataModel extends BaseDataModel {
serverName: string;
serverId: string;
filePath: string;
version: string;
upgradeExisting: boolean;
scriptFilePath: string;
generateScriptAndDeploy: boolean;
}

View File

@@ -1,380 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
import * as azdata from 'azdata';
import { SelectOperationPage } from './pages/selectOperationpage';
import { DeployConfigPage } from './pages/deployConfigPage';
import { DeployPlanPage } from './pages/deployPlanPage';
import { DeployActionPage } from './pages/deployActionPage';
import { DacFxSummaryPage } from './pages/dacFxSummaryPage';
import { ExportConfigPage } from './pages/exportConfigPage';
import { ExtractConfigPage } from './pages/extractConfigPage';
import { ImportConfigPage } from './pages/importConfigPage';
import { DacFxDataModel } from './api/models';
import { BasePage } from './api/basePage';
const localize = nls.loadMessageBundle();
class Page {
wizardPage: azdata.window.WizardPage;
dacFxPage: BasePage;
constructor(wizardPage: azdata.window.WizardPage) {
this.wizardPage = wizardPage;
}
}
export enum Operation {
deploy,
extract,
import,
export,
generateDeployScript
}
export enum DeployOperationPath {
selectOperation,
deployOptions,
deployPlan,
deployAction,
summary
}
export enum DeployNewOperationPath {
selectOperation,
deployOptions,
summary
}
export enum ExtractOperationPath {
selectOperation,
options,
summary
}
export enum ImportOperationPath {
selectOperation,
options,
summary
}
export enum ExportOperationPath {
selectOperation,
options,
summary
}
export class DataTierApplicationWizard {
public wizard: azdata.window.Wizard;
private connection: azdata.connection.ConnectionProfile;
private model: DacFxDataModel;
public pages: Map<string, Page> = new Map<string, Page>();
public selectedOperation: Operation;
constructor() {
}
public async start(p: any, ...args: any[]) {
this.model = <DacFxDataModel>{};
let profile = p ? <azdata.IConnectionProfile>p.connectionProfile : undefined;
if (profile) {
this.model.serverId = profile.id;
this.model.database = profile.databaseName;
}
this.connection = await azdata.connection.getCurrentConnection();
if (!this.connection) {
// @TODO: remove cast once azdata update complete - karlb 3/1/2019
this.connection = <azdata.connection.ConnectionProfile><any>await azdata.connection.openConnectionDialog();
}
this.wizard = azdata.window.createWizard('Data-tier Application Wizard');
let selectOperationWizardPage = azdata.window.createWizardPage(localize('dacFx.selectOperationPageName', 'Select an Operation'));
let deployConfigWizardPage = azdata.window.createWizardPage(localize('dacFx.deployConfigPageName', 'Select Deploy Dacpac Settings'));
let deployPlanWizardPage = azdata.window.createWizardPage(localize('dacFx.deployPlanPage', 'Review the deploy plan'));
let deployActionWizardPage = azdata.window.createWizardPage(localize('dacFx.deployActionPageName', 'Select Action'));
let summaryWizardPage = azdata.window.createWizardPage(localize('dacFx.summaryPageName', 'Summary'));
let extractConfigWizardPage = azdata.window.createWizardPage(localize('dacFx.extractConfigPageName', 'Select Extract Dacpac Settings'));
let importConfigWizardPage = azdata.window.createWizardPage(localize('dacFx.importConfigPageName', 'Select Import Bacpac Settings'));
let exportConfigWizardPage = azdata.window.createWizardPage(localize('dacFx.exportConfigPageName', 'Select Export Bacpac Settings'));
this.pages.set('selectOperation', new Page(selectOperationWizardPage));
this.pages.set('deployConfig', new Page(deployConfigWizardPage));
this.pages.set('deployPlan', new Page(deployPlanWizardPage));
this.pages.set('deployAction', new Page(deployActionWizardPage));
this.pages.set('extractConfig', new Page(extractConfigWizardPage));
this.pages.set('importConfig', new Page(importConfigWizardPage));
this.pages.set('exportConfig', new Page(exportConfigWizardPage));
this.pages.set('summary', new Page(summaryWizardPage));
selectOperationWizardPage.registerContent(async (view) => {
let selectOperationDacFxPage = new SelectOperationPage(this, selectOperationWizardPage, this.model, view);
this.pages.get('selectOperation').dacFxPage = selectOperationDacFxPage;
await selectOperationDacFxPage.start().then(() => {
selectOperationDacFxPage.setupNavigationValidator();
selectOperationDacFxPage.onPageEnter();
});
});
deployConfigWizardPage.registerContent(async (view) => {
let deployConfigDacFxPage = new DeployConfigPage(this, deployConfigWizardPage, this.model, view);
this.pages.get('deployConfig').dacFxPage = deployConfigDacFxPage;
await deployConfigDacFxPage.start();
});
deployPlanWizardPage.registerContent(async (view) => {
let deployPlanDacFxPage = new DeployPlanPage(this, deployPlanWizardPage, this.model, view);
this.pages.get('deployPlan').dacFxPage = deployPlanDacFxPage;
await deployPlanDacFxPage.start();
});
deployActionWizardPage.registerContent(async (view) => {
let deployActionDacFxPage = new DeployActionPage(this, deployActionWizardPage, this.model, view);
this.pages.get('deployAction').dacFxPage = deployActionDacFxPage;
await deployActionDacFxPage.start();
});
extractConfigWizardPage.registerContent(async (view) => {
let extractConfigDacFxPage = new ExtractConfigPage(this, extractConfigWizardPage, this.model, view);
this.pages.get('extractConfig').dacFxPage = extractConfigDacFxPage;
await extractConfigDacFxPage.start();
});
importConfigWizardPage.registerContent(async (view) => {
let importConfigDacFxPage = new ImportConfigPage(this, importConfigWizardPage, this.model, view);
this.pages.get('importConfig').dacFxPage = importConfigDacFxPage;
await importConfigDacFxPage.start();
});
exportConfigWizardPage.registerContent(async (view) => {
let exportConfigDacFxPage = new ExportConfigPage(this, exportConfigWizardPage, this.model, view);
this.pages.get('exportConfig').dacFxPage = exportConfigDacFxPage;
await exportConfigDacFxPage.start();
});
summaryWizardPage.registerContent(async (view) => {
let summaryDacFxPage = new DacFxSummaryPage(this, summaryWizardPage, this.model, view);
this.pages.get('summary').dacFxPage = summaryDacFxPage;
await summaryDacFxPage.start();
});
this.wizard.onPageChanged(async (event) => {
let idx = event.newPage;
let page = this.getPage(idx);
if (page !== undefined) {
page.dacFxPage.setupNavigationValidator();
page.dacFxPage.onPageEnter();
}
//do onPageLeave for summary page so that GenerateScript button only shows up if upgrading database
let idxLast = event.lastPage;
if (this.isSummaryPage(idxLast)) {
let lastPage = this.pages.get('summary');
if (lastPage) {
lastPage.dacFxPage.onPageLeave();
}
}
});
this.wizard.pages = [selectOperationWizardPage, deployConfigWizardPage, deployPlanWizardPage, deployActionWizardPage, summaryWizardPage];
this.wizard.generateScriptButton.hidden = true;
this.wizard.generateScriptButton.onClick(async () => await this.generateDeployScript());
this.wizard.doneButton.onClick(async () => await this.executeOperation());
this.wizard.open();
}
public registerNavigationValidator(validator: (pageChangeInfo: azdata.window.WizardPageChangeInfo) => boolean) {
this.wizard.registerNavigationValidator(validator);
}
public setDoneButton(operation: Operation): void {
switch (operation) {
case Operation.deploy: {
this.wizard.doneButton.label = localize('dacFx.deployButton', 'Deploy');
this.selectedOperation = Operation.deploy;
break;
}
case Operation.extract: {
this.wizard.doneButton.label = localize('dacFx.extractButton', 'Extract');
this.selectedOperation = Operation.extract;
break;
}
case Operation.import: {
this.wizard.doneButton.label = localize('dacFx.importButton', 'Import');
this.selectedOperation = Operation.import;
break;
}
case Operation.export: {
this.wizard.doneButton.label = localize('dacFx.exportButton', 'Export');
this.selectedOperation = Operation.export;
break;
}
case Operation.generateDeployScript: {
this.wizard.doneButton.label = localize('dacFx.generateScriptButton', 'Generate Script');
this.selectedOperation = Operation.generateDeployScript;
break;
}
}
if (operation !== Operation.deploy && operation !== Operation.generateDeployScript) {
this.model.upgradeExisting = false;
}
}
private async executeOperation() {
switch (this.selectedOperation) {
case Operation.deploy: {
await this.deploy();
break;
}
case Operation.extract: {
await this.extract();
break;
}
case Operation.import: {
await this.import();
break;
}
case Operation.export: {
await this.export();
break;
}
case Operation.generateDeployScript: {
await this.generateDeployScript();
break;
}
}
}
private async deploy() {
let service = await DataTierApplicationWizard.getService(this.model.server.providerName);
let ownerUri = await azdata.connection.getUriForConnection(this.model.server.connectionId);
let result = await service.deployDacpac(this.model.filePath, this.model.database, this.model.upgradeExisting, ownerUri, azdata.TaskExecutionMode.execute);
if (!result || !result.success) {
vscode.window.showErrorMessage(
localize('alertData.deployErrorMessage', "Deploy failed '{0}'", result.errorMessage ? result.errorMessage : 'Unknown'));
}
}
private async extract() {
let service = await DataTierApplicationWizard.getService(this.model.server.providerName);
let ownerUri = await azdata.connection.getUriForConnection(this.model.server.connectionId);
let result = await service.extractDacpac(this.model.database, this.model.filePath, this.model.database, this.model.version, ownerUri, azdata.TaskExecutionMode.execute);
if (!result || !result.success) {
vscode.window.showErrorMessage(
localize('alertData.extractErrorMessage', "Extract failed '{0}'", result.errorMessage ? result.errorMessage : 'Unknown'));
}
}
private async export() {
let service = await DataTierApplicationWizard.getService(this.model.server.providerName);
let ownerUri = await azdata.connection.getUriForConnection(this.model.server.connectionId);
let result = await service.exportBacpac(this.model.database, this.model.filePath, ownerUri, azdata.TaskExecutionMode.execute);
if (!result || !result.success) {
vscode.window.showErrorMessage(
localize('alertData.exportErrorMessage', "Export failed '{0}'", result.errorMessage ? result.errorMessage : 'Unknown'));
}
}
private async import() {
let service = await DataTierApplicationWizard.getService(this.model.server.providerName);
let ownerUri = await azdata.connection.getUriForConnection(this.model.server.connectionId);
let result = await service.importBacpac(this.model.filePath, this.model.database, ownerUri, azdata.TaskExecutionMode.execute);
if (!result || !result.success) {
vscode.window.showErrorMessage(
localize('alertData.importErrorMessage', "Import failed '{0}'", result.errorMessage ? result.errorMessage : 'Unknown'));
}
}
private async generateDeployScript() {
if (!this.model.scriptFilePath) {
return;
}
let service = await DataTierApplicationWizard.getService(this.model.server.providerName);
let ownerUri = await azdata.connection.getUriForConnection(this.model.server.connectionId);
this.wizard.message = {
text: localize('dacfx.scriptGeneratingMessage', 'You can view the status of script generation in the Task History once the wizard is closed'),
level: azdata.window.MessageLevel.Information,
description: ''
};
let result = await service.generateDeployScript(this.model.filePath, this.model.database, this.model.scriptFilePath, ownerUri, azdata.TaskExecutionMode.execute);
if (!result || !result.success) {
vscode.window.showErrorMessage(
localize('alertData.deployErrorMessage', "Deploy failed '{0}'", result.errorMessage ? result.errorMessage : 'Unknown'));
}
}
private getPage(idx: number): Page {
let page: Page;
if (idx === 1) {
switch (this.selectedOperation) {
case Operation.deploy: {
page = this.pages.get('deployConfig');
break;
}
case Operation.extract: {
page = this.pages.get('extractConfig');
break;
}
case Operation.import: {
page = this.pages.get('importConfig');
break;
}
case Operation.export: {
page = this.pages.get('exportConfig');
break;
}
}
} else if (this.isSummaryPage(idx)) {
page = this.pages.get('summary');
} else if ((this.selectedOperation === Operation.deploy || this.selectedOperation === Operation.generateDeployScript) && idx === DeployOperationPath.deployPlan) {
page = this.pages.get('deployPlan');
} else if ((this.selectedOperation === Operation.deploy || this.selectedOperation === Operation.generateDeployScript) && idx === DeployOperationPath.deployAction) {
page = this.pages.get('deployAction');
}
return page;
}
private isSummaryPage(idx: number): boolean {
return this.selectedOperation === Operation.import && idx === ImportOperationPath.summary
|| this.selectedOperation === Operation.export && idx === ExportOperationPath.summary
|| this.selectedOperation === Operation.extract && idx === ExtractOperationPath.summary
|| this.selectedOperation === Operation.deploy && !this.model.upgradeExisting && idx === DeployNewOperationPath.summary
|| (this.selectedOperation === Operation.deploy || this.selectedOperation === Operation.generateDeployScript) && idx === DeployOperationPath.summary;
}
public async generateDeployPlan(): Promise<string> {
let service = await DataTierApplicationWizard.getService(this.model.server.providerName);
let ownerUri = await azdata.connection.getUriForConnection(this.model.server.connectionId);
let result = await service.generateDeployPlan(this.model.filePath, this.model.database, ownerUri, azdata.TaskExecutionMode.execute);
if (!result || !result.success) {
vscode.window.showErrorMessage(
localize('alertData.deployPlanErrorMessage', "Generating deploy plan failed '{0}'", result.errorMessage ? result.errorMessage : 'Unknown'));
}
return result.report;
}
private static async getService(providerName: string): Promise<azdata.DacFxServicesProvider> {
let service = azdata.dataprotocol.getProvider<azdata.DacFxServicesProvider>(providerName, azdata.DataProviderType.DacFxServicesProvider);
return service;
}
}

View File

@@ -1,148 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as azdata from 'azdata';
import * as nls from 'vscode-nls';
import { DacFxDataModel } from '../api/models';
import { DataTierApplicationWizard, Operation } from '../dataTierApplicationWizard';
import { BasePage } from '../api/basePage';
const localize = nls.loadMessageBundle();
export class DacFxSummaryPage extends BasePage {
protected readonly wizardPage: azdata.window.WizardPage;
protected readonly instance: DataTierApplicationWizard;
protected readonly model: DacFxDataModel;
protected readonly view: azdata.ModelView;
private form: azdata.FormContainer;
private table: azdata.TableComponent;
private loader: azdata.LoadingComponent;
public constructor(instance: DataTierApplicationWizard, wizardPage: azdata.window.WizardPage, model: DacFxDataModel, view: azdata.ModelView) {
super();
this.instance = instance;
this.wizardPage = wizardPage;
this.model = model;
this.view = view;
}
async start(): Promise<boolean> {
this.table = this.view.modelBuilder.table().component();
this.loader = this.view.modelBuilder.loadingComponent().withItem(this.table).component();
this.form = this.view.modelBuilder.formContainer().withFormItems(
[
{
component: this.table,
title: ''
}
]
).component();
await this.view.initializeModel(this.form);
return true;
}
async onPageEnter(): Promise<boolean> {
this.populateTable();
this.loader.loading = false;
if (this.model.upgradeExisting && this.model.generateScriptAndDeploy) {
this.instance.wizard.generateScriptButton.hidden = false;
}
return true;
}
async onPageLeave(): Promise<boolean> {
this.instance.wizard.generateScriptButton.hidden = true;
return true;
}
public setupNavigationValidator() {
this.instance.registerNavigationValidator(() => {
if (this.loader.loading) {
return false;
}
return true;
});
}
private populateTable() {
let data = [];
let targetServer = localize('dacfx.targetServerName', 'Target Server');
let targetDatabase = localize('dacfx.targetDatabaseName', 'Target Database');
let sourceServer = localize('dacfx.sourceServerName', 'Source Server');
let sourceDatabase = localize('dacfx.sourceDatabaseName', 'Source Database');
let fileLocation = localize('dacfx.fileLocation', 'File Location');
let scriptLocation = localize('dacfx.scriptLocation', 'Deployment Script Location');
let action = localize('dacfx.action', 'Action');
let deploy = localize('dacfx.deploy', 'Deploy');
let generateScript = localize('dacfx.generateScript', 'Generate Deployment Script');
switch (this.instance.selectedOperation) {
case Operation.deploy: {
data = [
[targetServer, this.model.serverName],
[fileLocation, this.model.filePath],
[targetDatabase, this.model.database]];
if (this.model.generateScriptAndDeploy) {
data[3] = [scriptLocation, this.model.scriptFilePath];
data[4] = [action, generateScript + ', ' + deploy];
}
else {
data[3] = [action, deploy];
}
break;
}
case Operation.extract: {
data = [
[sourceServer, this.model.serverName],
[sourceDatabase, this.model.database],
[localize('dacfxExtract.version', 'Version'), this.model.version],
[fileLocation, this.model.filePath]];
break;
}
case Operation.import: {
data = [
[targetServer, this.model.serverName],
[fileLocation, this.model.filePath],
[targetDatabase, this.model.database]];
break;
}
case Operation.export: {
data = [
[sourceServer, this.model.serverName],
[sourceDatabase, this.model.database],
[fileLocation, this.model.filePath]];
break;
}
case Operation.generateDeployScript: {
data = [
[targetServer, this.model.serverName],
[fileLocation, this.model.filePath],
[targetDatabase, this.model.database],
[scriptLocation, this.model.scriptFilePath],
[action, generateScript]];
break;
}
}
this.table.updateProperties({
data: data,
columns: [
{
value: localize('dacfx.settingColumn', 'Setting'),
cssClass: 'align-with-header'
},
{
value: localize('dacfx.valueColumn', 'Value'),
cssClass: 'align-with-header'
}],
width: 700,
height: 200
});
}
}

View File

@@ -1,181 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as azdata from 'azdata';
import * as nls from 'vscode-nls';
import * as vscode from 'vscode';
import * as path from 'path';
import * as os from 'os';
import { DacFxDataModel } from '../api/models';
import { DataTierApplicationWizard, Operation } from '../dataTierApplicationWizard';
import { DacFxConfigPage } from '../api/dacFxConfigPage';
const localize = nls.loadMessageBundle();
export class DeployActionPage extends DacFxConfigPage {
protected readonly wizardPage: azdata.window.WizardPage;
protected readonly instance: DataTierApplicationWizard;
protected readonly model: DacFxDataModel;
protected readonly view: azdata.ModelView;
private deployRadioButton: azdata.RadioButtonComponent;
private deployScriptRadioButton: azdata.RadioButtonComponent;
private scriptRadioButton: azdata.RadioButtonComponent;
private form: azdata.FormContainer;
public constructor(instance: DataTierApplicationWizard, wizardPage: azdata.window.WizardPage, model: DacFxDataModel, view: azdata.ModelView) {
super(instance, wizardPage, model, view);
}
async start(): Promise<boolean> {
let deployComponent = await this.createDeployRadioButton();
let deployScriptComponent = await this.createDeployScriptRadioButton();
let scriptComponent = await this.createScriptRadioButton();
let fileBrowserComponent = await this.createFileBrowser();
this.form = this.view.modelBuilder.formContainer()
.withFormItems(
[
deployComponent,
scriptComponent,
deployScriptComponent,
fileBrowserComponent
]).component();
await this.view.initializeModel(this.form);
//default have the first radio button checked
this.deployRadioButton.checked = true;
this.toggleFileBrowser(false);
return true;
}
async onPageEnter(): Promise<boolean> {
// generate script file path in case the database changed since last time the page was entered
this.setDefaultScriptFilePath();
return true;
}
private async createDeployRadioButton(): Promise<azdata.FormComponent> {
this.deployRadioButton = this.view.modelBuilder.radioButton()
.withProperties({
name: 'selectedDeployAction',
label: localize('dacFx.deployRadioButtonLabel', 'Deploy'),
}).component();
this.deployRadioButton.onDidClick(() => {
this.model.generateScriptAndDeploy = false;
this.instance.setDoneButton(Operation.deploy);
this.toggleFileBrowser(false);
});
return {
component: this.deployRadioButton,
title: ''
};
}
private async createDeployScriptRadioButton(): Promise<azdata.FormComponent> {
this.deployScriptRadioButton = this.view.modelBuilder.radioButton()
.withProperties({
name: 'selectedDeployAction',
label: localize('dacFx.deployScriptRadioButtonLabel', 'Generate Deployment Script and Deploy'),
}).component();
this.deployScriptRadioButton.onDidClick(() => {
this.model.generateScriptAndDeploy = true;
this.instance.setDoneButton(Operation.deploy);
this.toggleFileBrowser(true);
});
return {
component: this.deployScriptRadioButton,
title: ''
};
}
private async createScriptRadioButton(): Promise<azdata.FormComponent> {
this.scriptRadioButton = this.view.modelBuilder.radioButton()
.withProperties({
name: 'selectedDeployAction',
label: localize('dacFx.scriptRadioButtonLabel', 'Generate Deployment Script'),
}).component();
this.scriptRadioButton.onDidClick(() => {
this.model.generateScriptAndDeploy = false;
this.toggleFileBrowser(true);
//change button text and operation
this.instance.setDoneButton(Operation.generateDeployScript);
});
return {
component: this.scriptRadioButton,
title: ''
};
}
private async createFileBrowser(): Promise<azdata.FormComponentGroup> {
this.createFileBrowserParts();
//default filepath
this.setDefaultScriptFilePath();
this.fileButton.onDidClick(async (click) => {
let fileUri = await vscode.window.showSaveDialog(
{
defaultUri: vscode.Uri.file(this.fileTextBox.value),
saveLabel: localize('dacfxDeployScript.saveFile', 'Save'),
filters: {
'SQL Files': ['sql'],
}
}
);
if (!fileUri) {
return;
}
this.fileTextBox.value = fileUri.fsPath;
this.model.scriptFilePath = fileUri.fsPath;
});
this.fileTextBox.onTextChanged(async () => {
this.model.scriptFilePath = this.fileTextBox.value;
});
return {
title: '',
components: [
{
title: localize('dacfx.generatedScriptLocation', 'Deployment Script Location'),
component: this.fileTextBox,
layout: {
horizontal: true,
componentWidth: 400
},
actions: [this.fileButton]
},],
};
}
private toggleFileBrowser(enable: boolean): void {
this.fileTextBox.enabled = enable;
this.fileButton.enabled = enable;
}
private setDefaultScriptFilePath(): void {
let now = new Date();
let datetime = now.getFullYear() + '-' + (now.getMonth() + 1) + '-' + now.getDate() + '-' + now.getHours() + '-' + now.getMinutes();
this.fileTextBox.value = path.join(os.homedir(), this.model.database + '_UpgradeDACScript_' + datetime + '.sql');
this.model.scriptFilePath = this.fileTextBox.value;
}
public setupNavigationValidator() {
this.instance.registerNavigationValidator(() => {
return true;
});
}
}

View File

@@ -1,202 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as azdata from 'azdata';
import * as nls from 'vscode-nls';
import * as vscode from 'vscode';
import * as path from 'path';
import * as os from 'os';
import { DacFxDataModel } from '../api/models';
import { DataTierApplicationWizard, DeployOperationPath, Operation } from '../dataTierApplicationWizard';
import { DacFxConfigPage } from '../api/dacFxConfigPage';
const localize = nls.loadMessageBundle();
export class DeployConfigPage extends DacFxConfigPage {
protected readonly wizardPage: azdata.window.WizardPage;
protected readonly instance: DataTierApplicationWizard;
protected readonly model: DacFxDataModel;
protected readonly view: azdata.ModelView;
private databaseDropdownComponent: azdata.FormComponent;
private databaseComponent: azdata.FormComponent;
private formBuilder: azdata.FormBuilder;
private form: azdata.FormContainer;
public constructor(instance: DataTierApplicationWizard, wizardPage: azdata.window.WizardPage, model: DacFxDataModel, view: azdata.ModelView) {
super(instance, wizardPage, model, view);
this.fileExtension = '.bacpac';
}
async start(): Promise<boolean> {
let serverComponent = await this.createServerDropdown(true);
let fileBrowserComponent = await this.createFileBrowser();
this.databaseComponent = await this.createDatabaseTextBox();
this.databaseComponent.title = localize('dacFx.databaseNameTextBox', 'Database Name');
this.databaseDropdownComponent = await this.createDeployDatabaseDropdown();
this.databaseDropdownComponent.title = localize('dacFx.databaseNameDropdown', 'Database Name');
let radioButtons = await this.createRadiobuttons();
this.formBuilder = this.view.modelBuilder.formContainer()
.withFormItems(
[
fileBrowserComponent,
serverComponent,
radioButtons,
this.databaseDropdownComponent
], {
horizontal: true,
componentWidth: 400
});
this.form = this.formBuilder.component();
await this.view.initializeModel(this.form);
return true;
}
async onPageEnter(): Promise<boolean> {
let r1 = await this.populateServerDropdown();
let r2 = await this.populateDeployDatabaseDropdown();
return r1 && r2;
}
private async createFileBrowser(): Promise<azdata.FormComponent> {
this.createFileBrowserParts();
this.fileButton.onDidClick(async (click) => {
let fileUris = await vscode.window.showOpenDialog(
{
canSelectFiles: true,
canSelectFolders: false,
canSelectMany: false,
defaultUri: vscode.Uri.file(os.homedir()),
openLabel: localize('dacFxDeploy.openFile', 'Open'),
filters: {
'dacpac Files': ['dacpac'],
}
}
);
if (!fileUris || fileUris.length === 0) {
return;
}
let fileUri = fileUris[0];
this.fileTextBox.value = fileUri.fsPath;
this.model.filePath = fileUri.fsPath;
});
this.fileTextBox.onTextChanged(async () => {
this.model.filePath = this.fileTextBox.value;
this.databaseTextBox.value = this.generateDatabaseName(this.model.filePath);
if (!this.model.upgradeExisting) {
this.model.database = this.databaseTextBox.value;
}
});
return {
component: this.fileTextBox,
title: localize('dacFxDeploy.fileTextboxTitle', 'File Location'),
actions: [this.fileButton]
};
}
private async createRadiobuttons(): Promise<azdata.FormComponent> {
let upgradeRadioButton = this.view.modelBuilder.radioButton()
.withProperties({
name: 'updateExisting',
label: localize('dacFx.upgradeRadioButtonLabel', 'Upgrade Existing Database'),
}).component();
let newRadioButton = this.view.modelBuilder.radioButton()
.withProperties({
name: 'updateExisting',
label: localize('dacFx.newRadioButtonLabel', 'New Database'),
}).component();
upgradeRadioButton.onDidClick(() => {
this.model.upgradeExisting = true;
this.formBuilder.removeFormItem(this.databaseComponent);
this.formBuilder.addFormItem(this.databaseDropdownComponent, { horizontal: true, componentWidth: 400 });
this.model.database = (<azdata.CategoryValue>this.databaseDropdown.value).name;
// add deploy plan and generate script pages
let deployPlanPage = this.instance.pages.get('deployPlan');
this.instance.wizard.addPage(deployPlanPage.wizardPage, DeployOperationPath.deployPlan);
let deployActionPage = this.instance.pages.get('deployAction');
this.instance.wizard.addPage(deployActionPage.wizardPage, DeployOperationPath.deployAction);
});
newRadioButton.onDidClick(() => {
this.model.upgradeExisting = false;
this.formBuilder.removeFormItem(this.databaseDropdownComponent);
this.formBuilder.addFormItem(this.databaseComponent, { horizontal: true, componentWidth: 400 });
this.model.database = this.databaseTextBox.value;
this.instance.setDoneButton(Operation.deploy);
// remove deploy plan and generate script pages
this.instance.wizard.removePage(DeployOperationPath.deployAction);
this.instance.wizard.removePage(DeployOperationPath.deployPlan);
});
//Initialize with upgrade existing true
upgradeRadioButton.checked = true;
this.model.upgradeExisting = true;
let flexRadioButtonsModel = this.view.modelBuilder.flexContainer()
.withLayout({
flexFlow: 'row',
}).withItems([
upgradeRadioButton, newRadioButton]
).component();
return {
component: flexRadioButtonsModel,
title: localize('dacFx.targetDatabaseRadioButtonsTitle', 'Target Database')
};
}
protected async createDeployDatabaseDropdown(): Promise<azdata.FormComponent> {
this.databaseDropdown = this.view.modelBuilder.dropDown().withProperties({
required: true
}).component();
//Handle database changes
this.databaseDropdown.onValueChanged(async () => {
this.model.database = (<azdata.CategoryValue>this.databaseDropdown.value).name;
});
this.databaseLoader = this.view.modelBuilder.loadingComponent().withItem(this.databaseDropdown).component();
return {
component: this.databaseLoader,
title: localize('dacFx.targetDatabaseDropdownTitle', 'Database Name')
};
}
protected async populateDeployDatabaseDropdown(): Promise<boolean> {
this.databaseLoader.loading = true;
this.databaseDropdown.updateProperties({ values: [] });
if (!this.model.server) {
this.databaseLoader.loading = false;
return false;
}
let values = await this.getDatabaseValues();
//set the database to the first dropdown value if upgrading, otherwise it should get set to the textbox value
if (this.model.upgradeExisting) {
this.model.database = values[0].name;
}
this.databaseDropdown.updateProperties({
values: values
});
this.databaseLoader.loading = false;
return true;
}
private generateDatabaseName(filePath: string): string {
let result = path.parse(filePath);
return result.name;
}
}

View File

@@ -1,296 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as azdata from 'azdata';
import * as nls from 'vscode-nls';
import * as parser from 'htmlparser2';
import { DacFxDataModel } from '../api/models';
import { DataTierApplicationWizard } from '../dataTierApplicationWizard';
import { DacFxConfigPage } from '../api/dacFxConfigPage';
const localize = nls.loadMessageBundle();
export enum deployPlanXml {
AlertElement = 'Alert',
OperationElement = 'Operation',
ItemElement = 'Item',
NameAttribute = 'Name',
ValueAttribute = 'Value',
TypeAttribute = 'Type',
IdAttribute = 'Id',
DataIssueAttribute = 'DataIssue'
}
export class TableObject {
operation: string;
object: string;
type: string;
dataloss: boolean;
}
export class DeployPlanResult {
columnData: Array<Array<string>>;
dataLossAlerts: Set<string>;
}
export class DeployPlanPage extends DacFxConfigPage {
protected readonly wizardPage: azdata.window.WizardPage;
protected readonly instance: DataTierApplicationWizard;
protected readonly model: DacFxDataModel;
protected readonly view: azdata.ModelView;
private formBuilder: azdata.FormBuilder;
private form: azdata.FormContainer;
private table: azdata.TableComponent;
private loader: azdata.LoadingComponent;
private dataLossCheckbox: azdata.CheckBoxComponent;
private dataLossText: azdata.TextComponent;
private dataLossComponentGroup: azdata.FormComponentGroup;
private noDataLossTextComponent: azdata.FormComponent;
public constructor(instance: DataTierApplicationWizard, wizardPage: azdata.window.WizardPage, model: DacFxDataModel, view: azdata.ModelView) {
super(instance, wizardPage, model, view);
}
async start(): Promise<boolean> {
this.table = this.view.modelBuilder.table().component();
this.loader = this.view.modelBuilder.loadingComponent().withItem(this.table).component();
this.dataLossComponentGroup = await this.createDataLossComponents();
this.noDataLossTextComponent = await this.createNoDataLossText();
this.formBuilder = this.view.modelBuilder.formContainer()
.withFormItems(
[
{
component: this.loader,
title: ''
},
this.dataLossComponentGroup
], {
horizontal: true,
});
this.form = this.formBuilder.component();
await this.view.initializeModel(this.form);
return true;
}
async onPageEnter(): Promise<boolean> {
// reset checkbox settings
this.formBuilder.addFormItem(this.dataLossComponentGroup, { horizontal: true, componentWidth: 400 });
this.dataLossCheckbox.checked = false;
this.dataLossCheckbox.enabled = false;
this.formBuilder.removeFormItem(this.noDataLossTextComponent);
this.loader.loading = true;
this.table.data = [];
await this.populateTable();
this.loader.loading = false;
return true;
}
private async populateTable() {
let report = await this.instance.generateDeployPlan();
let result = this.parseXml(report);
this.table.updateProperties({
data: this.getColumnData(result),
columns: this.getTableColumns(result.dataLossAlerts.size > 0),
width: 875,
height: 300
});
if (result.dataLossAlerts.size > 0) {
// update message to list how many operations could result in data loss
this.dataLossText.updateProperties({
value: localize('dacfx.dataLossTextWithCount', '{0} of the deploy actions listed may result in data loss. Please ensure you have a backup or snapshot available in the event of an issue with the deployment.', result.dataLossAlerts.size)
});
this.dataLossCheckbox.enabled = true;
} else {
// check checkbox to enable Next button and remove checkbox because there won't be any possible data loss
this.dataLossCheckbox.checked = true;
this.formBuilder.removeFormItem(this.dataLossComponentGroup);
this.formBuilder.addFormItem(this.noDataLossTextComponent, { componentWidth: 300, horizontal: true });
}
}
private async createDataLossCheckbox(): Promise<azdata.FormComponent> {
this.dataLossCheckbox = this.view.modelBuilder.checkBox()
.withValidation(component => component.checked === true)
.withProperties({
label: localize('dacFx.dataLossCheckbox', 'Proceed despite possible data loss'),
}).component();
return {
component: this.dataLossCheckbox,
title: '',
required: true
};
}
private async createNoDataLossText(): Promise<azdata.FormComponent> {
let noDataLossText = this.view.modelBuilder.text()
.withProperties({
value: localize('dacfx.noDataLossText', 'No data loss will occur from the listed deploy actions.')
}).component();
return {
title: '',
component: noDataLossText
};
}
private async createDataLossComponents(): Promise<azdata.FormComponentGroup> {
let dataLossComponent = await this.createDataLossCheckbox();
this.dataLossText = this.view.modelBuilder.text()
.withProperties({
value: localize('dacfx.dataLossText', 'The deploy actions may result in data loss. Please ensure you have a backup or snapshot available in the event of an issue with the deployment.')
}).component();
return {
title: '',
components: [
{
component: this.dataLossText,
layout: {
componentWidth: 400,
horizontal: true
},
title: ''
},
dataLossComponent
]
};
}
private getColumnData(result: DeployPlanResult): Array<Array<string>> {
// remove data loss column data if there aren't any alerts
let columnData = result.columnData;
if (result.dataLossAlerts.size === 0) {
columnData.forEach(entry => {
entry.shift();
});
}
return columnData;
}
private getTableColumns(dataloss: boolean): azdata.TableColumn[] {
let columns: azdata.TableColumn[] = [
{
value: localize('dacfx.operationColumn', 'Operation'),
width: 75,
cssClass: 'align-with-header',
toolTip: localize('dacfx.operationTooltip', 'Operation(Create, Alter, Delete) that will occur during deployment')
},
{
value: localize('dacfx.typeColumn', 'Type'),
width: 100,
cssClass: 'align-with-header',
toolTip: localize('dacfx.typeTooltip', 'Type of object that will be affected by deployment')
},
{
value: localize('dacfx.objectColumn', 'Object'),
width: 300,
cssClass: 'align-with-header',
toolTip: localize('dacfx.objecTooltip', 'Name of object that will be affected by deployment')
}];
if (dataloss) {
columns.unshift(
{
value: localize('dacfx.dataLossColumn', 'Data Loss'),
width: 50,
cssClass: 'center-align',
toolTip: localize('dacfx.dataLossTooltip', 'Operations that may result in data loss are marked with a warning sign')
});
}
return columns;
}
private parseXml(report: string): DeployPlanResult {
let operations = new Array<TableObject>();
let dataLossAlerts = new Set<string>();
let currentOperation = '';
let dataIssueAlert = false;
let currentReportSection: deployPlanXml;
let currentTableObj: TableObject;
let p = new parser.Parser({
onopentagname(name) {
if (name === deployPlanXml.AlertElement) {
currentReportSection = deployPlanXml.AlertElement;
} else if (name === deployPlanXml.OperationElement) {
currentReportSection = deployPlanXml.OperationElement;
} else if (name === deployPlanXml.ItemElement) {
currentTableObj = new TableObject();
}
},
onattribute: function (name, value) {
if (currentReportSection === deployPlanXml.AlertElement) {
switch (name) {
case deployPlanXml.NameAttribute: {
// only care about showing data loss alerts
if (value === deployPlanXml.DataIssueAttribute) {
dataIssueAlert = true;
}
break;
}
case deployPlanXml.IdAttribute: {
if (dataIssueAlert) {
dataLossAlerts.add(value);
}
break;
}
}
} else if (currentReportSection === deployPlanXml.OperationElement) {
switch (name) {
case deployPlanXml.NameAttribute: {
currentOperation = value;
break;
}
case deployPlanXml.ValueAttribute: {
currentTableObj.object = value;
break;
}
case deployPlanXml.TypeAttribute: {
currentTableObj.type = value;
break;
}
case deployPlanXml.IdAttribute: {
if (dataLossAlerts.has(value)) {
currentTableObj.dataloss = true;
}
break;
}
}
}
},
onclosetag: function (name) {
if (name === deployPlanXml.ItemElement) {
currentTableObj.operation = currentOperation;
operations.push(currentTableObj);
}
}
}, { xmlMode: true, decodeEntities: true });
p.parseChunk(report);
let data = new Array<Array<string>>();
operations.forEach(operation => {
let isDataLoss = operation.dataloss ? '⚠️' : '';
data.push([isDataLoss, operation.operation, operation.type, operation.object]);
});
return {
columnData: data,
dataLossAlerts: dataLossAlerts
};
}
public setupNavigationValidator() {
this.instance.registerNavigationValidator(() => {
return true;
});
}
}

View File

@@ -1,100 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as azdata from 'azdata';
import * as nls from 'vscode-nls';
import * as vscode from 'vscode';
import { DacFxDataModel } from '../api/models';
import { DataTierApplicationWizard } from '../dataTierApplicationWizard';
import { DacFxConfigPage } from '../api/dacFxConfigPage';
const localize = nls.loadMessageBundle();
export class ExportConfigPage extends DacFxConfigPage {
protected readonly wizardPage: azdata.window.WizardPage;
protected readonly instance: DataTierApplicationWizard;
protected readonly model: DacFxDataModel;
protected readonly view: azdata.ModelView;
private form: azdata.FormContainer;
public constructor(instance: DataTierApplicationWizard, wizardPage: azdata.window.WizardPage, model: DacFxDataModel, view: azdata.ModelView) {
super(instance, wizardPage, model, view);
this.fileExtension = '.bacpac';
}
async start(): Promise<boolean> {
let databaseComponent = await this.createDatabaseDropdown();
let serverComponent = await this.createServerDropdown(false);
let fileBrowserComponent = await this.createFileBrowser();
this.form = this.view.modelBuilder.formContainer()
.withFormItems(
[
serverComponent,
databaseComponent,
fileBrowserComponent,
], {
horizontal: true,
componentWidth: 400
}).component();
await this.view.initializeModel(this.form);
return true;
}
async onPageEnter(): Promise<boolean> {
let r1 = await this.populateServerDropdown();
let r2 = await this.populateDatabaseDropdown();
return r1 && r2;
}
public setupNavigationValidator() {
this.instance.registerNavigationValidator(() => {
if (this.databaseLoader.loading) {
return false;
}
return true;
});
}
private async createFileBrowser(): Promise<azdata.FormComponent> {
this.createFileBrowserParts();
// default filepath
this.fileTextBox.value = this.generateFilePath();
this.model.filePath = this.fileTextBox.value;
this.fileButton.onDidClick(async (click) => {
let fileUri = await vscode.window.showSaveDialog(
{
defaultUri: vscode.Uri.file(this.fileTextBox.value),
saveLabel: localize('dacfxExport.saveFile', 'Save'),
filters: {
'bacpac Files': ['bacpac'],
}
}
);
if (!fileUri) {
return;
}
this.fileTextBox.value = fileUri.fsPath;
this.model.filePath = fileUri.fsPath;
});
this.fileTextBox.onTextChanged(async () => {
this.model.filePath = this.fileTextBox.value;
});
return {
component: this.fileTextBox,
title: localize('dacFxExport.fileTextboxTitle', 'File Location'),
actions: [this.fileButton]
};
}
}

View File

@@ -1,122 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as azdata from 'azdata';
import * as nls from 'vscode-nls';
import * as vscode from 'vscode';
import { DacFxDataModel } from '../api/models';
import { DataTierApplicationWizard } from '../dataTierApplicationWizard';
import { DacFxConfigPage } from '../api/dacFxConfigPage';
const localize = nls.loadMessageBundle();
export class ExtractConfigPage extends DacFxConfigPage {
protected readonly wizardPage: azdata.window.WizardPage;
protected readonly instance: DataTierApplicationWizard;
protected readonly model: DacFxDataModel;
protected readonly view: azdata.ModelView;
private form: azdata.FormContainer;
private versionTextBox: azdata.InputBoxComponent;
public constructor(instance: DataTierApplicationWizard, wizardPage: azdata.window.WizardPage, model: DacFxDataModel, view: azdata.ModelView) {
super(instance, wizardPage, model, view);
this.fileExtension = '.dacpac';
}
async start(): Promise<boolean> {
let databaseComponent = await this.createDatabaseDropdown();
let serverComponent = await this.createServerDropdown(false);
let fileBrowserComponent = await this.createFileBrowser();
let versionComponent = await this.createVersionTextBox();
this.form = this.view.modelBuilder.formContainer()
.withFormItems(
[
serverComponent,
databaseComponent,
versionComponent,
fileBrowserComponent,
], {
horizontal: true,
componentWidth: 400
}).component();
await this.view.initializeModel(this.form);
return true;
}
async onPageEnter(): Promise<boolean> {
let r1 = await this.populateServerDropdown();
let r2 = await this.populateDatabaseDropdown();
return r1 && r2;
}
public setupNavigationValidator() {
this.instance.registerNavigationValidator(() => {
if (this.databaseLoader.loading) {
return false;
}
return true;
});
}
private async createFileBrowser(): Promise<azdata.FormComponent> {
this.createFileBrowserParts();
// default filepath
this.fileTextBox.value = this.generateFilePath();
this.model.filePath = this.fileTextBox.value;
this.fileButton.onDidClick(async (click) => {
let fileUri = await vscode.window.showSaveDialog(
{
defaultUri: vscode.Uri.file(this.fileTextBox.value),
saveLabel: localize('dacfxExtract.saveFile', 'Save'),
filters: {
'dacpac Files': ['dacpac'],
}
}
);
if (!fileUri) {
return;
}
this.fileTextBox.value = fileUri.fsPath;
this.model.filePath = fileUri.fsPath;
});
this.fileTextBox.onTextChanged(async () => {
this.model.filePath = this.fileTextBox.value;
});
return {
component: this.fileTextBox,
title: localize('dacFxExtract.fileTextboxTitle', 'File Location'),
actions: [this.fileButton]
};
}
private async createVersionTextBox(): Promise<azdata.FormComponent> {
this.versionTextBox = this.view.modelBuilder.inputBox().withProperties({
required: true
}).component();
// default filepath
this.versionTextBox.value = '1.0.0.0';
this.model.version = this.versionTextBox.value;
this.versionTextBox.onTextChanged(async () => {
this.model.version = this.versionTextBox.value;
});
return {
component: this.versionTextBox,
title: localize('dacFxExtract.versionTextboxTitle', 'Version (use x.x.x.x where x is a number)'),
};
}
}

View File

@@ -1,101 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as azdata from 'azdata';
import * as nls from 'vscode-nls';
import * as vscode from 'vscode';
import * as path from 'path';
import * as os from 'os';
import { DacFxDataModel } from '../api/models';
import { DataTierApplicationWizard } from '../dataTierApplicationWizard';
import { DacFxConfigPage } from '../api/dacFxConfigPage';
const localize = nls.loadMessageBundle();
export class ImportConfigPage extends DacFxConfigPage {
protected readonly wizardPage: azdata.window.WizardPage;
protected readonly instance: DataTierApplicationWizard;
protected readonly model: DacFxDataModel;
protected readonly view: azdata.ModelView;
private form: azdata.FormContainer;
public constructor(instance: DataTierApplicationWizard, wizardPage: azdata.window.WizardPage, model: DacFxDataModel, view: azdata.ModelView) {
super(instance, wizardPage, model, view);
this.fileExtension = '.bacpac';
}
async start(): Promise<boolean> {
let databaseComponent = await this.createDatabaseTextBox();
let serverComponent = await this.createServerDropdown(true);
let fileBrowserComponent = await this.createFileBrowser();
this.form = this.view.modelBuilder.formContainer()
.withFormItems(
[
fileBrowserComponent,
serverComponent,
databaseComponent,
], {
horizontal: true,
componentWidth: 400
}).component();
await this.view.initializeModel(this.form);
return true;
}
async onPageEnter(): Promise<boolean> {
let r1 = await this.populateServerDropdown();
return r1;
}
private async createFileBrowser(): Promise<azdata.FormComponent> {
this.createFileBrowserParts();
this.fileButton.onDidClick(async (click) => {
let fileUris = await vscode.window.showOpenDialog(
{
canSelectFiles: true,
canSelectFolders: false,
canSelectMany: false,
defaultUri: vscode.Uri.file(os.homedir()),
openLabel: localize('dacFxImport.openFile', 'Open'),
filters: {
'bacpac Files': ['bacpac'],
}
}
);
if (!fileUris || fileUris.length === 0) {
return;
}
let fileUri = fileUris[0];
this.fileTextBox.value = fileUri.fsPath;
this.model.filePath = fileUri.fsPath;
this.model.database = this.generateDatabaseName(this.model.filePath);
this.databaseTextBox.value = this.model.database;
});
this.fileTextBox.onTextChanged(async () => {
this.model.filePath = this.fileTextBox.value;
this.model.database = this.generateDatabaseName(this.model.filePath);
this.databaseTextBox.value = this.model.database;
});
return {
component: this.fileTextBox,
title: localize('dacFxImport.fileTextboxTitle', 'File Location'),
actions: [this.fileButton]
};
}
private generateDatabaseName(filePath: string): string {
let result = path.parse(filePath);
return result.name;
}
}

View File

@@ -1,185 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as azdata from 'azdata';
import * as nls from 'vscode-nls';
import { DacFxDataModel } from '../api/models';
import { DataTierApplicationWizard, Operation, DeployOperationPath, ExtractOperationPath, ImportOperationPath, ExportOperationPath } from '../dataTierApplicationWizard';
import { BasePage } from '../api/basePage';
const localize = nls.loadMessageBundle();
export class SelectOperationPage extends BasePage {
protected readonly wizardPage: azdata.window.WizardPage;
protected readonly instance: DataTierApplicationWizard;
protected readonly model: DacFxDataModel;
protected readonly view: azdata.ModelView;
private deployRadioButton: azdata.RadioButtonComponent;
private extractRadioButton: azdata.RadioButtonComponent;
private importRadioButton: azdata.RadioButtonComponent;
private exportRadioButton: azdata.RadioButtonComponent;
private form: azdata.FormContainer;
public constructor(instance: DataTierApplicationWizard, wizardPage: azdata.window.WizardPage, model: DacFxDataModel, view: azdata.ModelView) {
super();
this.instance = instance;
this.wizardPage = wizardPage;
this.model = model;
this.view = view;
}
async start(): Promise<boolean> {
let deployComponent = await this.createDeployRadioButton();
let extractComponent = await this.createExtractRadioButton();
let importComponent = await this.createImportRadioButton();
let exportComponent = await this.createExportRadioButton();
this.form = this.view.modelBuilder.formContainer()
.withFormItems(
[
deployComponent,
extractComponent,
importComponent,
exportComponent
], {
horizontal: true
}).component();
await this.view.initializeModel(this.form);
// default have the first radio button checked
this.deployRadioButton.checked = true;
this.instance.setDoneButton(Operation.deploy);
return true;
}
async onPageEnter(): Promise<boolean> {
return true;
}
private async createDeployRadioButton(): Promise<azdata.FormComponent> {
this.deployRadioButton = this.view.modelBuilder.radioButton()
.withProperties({
name: 'selectedOperation',
label: localize('dacFx.deployRadioButtonLabel', 'Deploy a data-tier application .dacpac file to an instance of SQL Server [Deploy Dacpac]'),
}).component();
this.deployRadioButton.onDidClick(() => {
this.removePages();
//add deploy pages
let configPage = this.instance.pages.get('deployConfig');
this.instance.wizard.addPage(configPage.wizardPage, DeployOperationPath.deployOptions);
let deployPlanPage = this.instance.pages.get('deployPlan');
this.instance.wizard.addPage(deployPlanPage.wizardPage, DeployOperationPath.deployPlan);
let actionPage = this.instance.pages.get('deployAction');
this.instance.wizard.addPage(actionPage.wizardPage, DeployOperationPath.deployAction);
this.addSummaryPage(DeployOperationPath.summary);
// change button text and operation
this.instance.setDoneButton(Operation.deploy);
});
return {
component: this.deployRadioButton,
title: ''
};
}
private async createExtractRadioButton(): Promise<azdata.FormComponent> {
this.extractRadioButton = this.view.modelBuilder.radioButton()
.withProperties({
name: 'selectedOperation',
label: localize('dacFx.extractRadioButtonLabel', 'Extract a data-tier application from an instance of SQL Server to a .dacpac file [Extract Dacpac]'),
}).component();
this.extractRadioButton.onDidClick(() => {
this.removePages();
// add the extract page
let page = this.instance.pages.get('extractConfig');
this.instance.wizard.addPage(page.wizardPage, ExtractOperationPath.options);
this.addSummaryPage(ExtractOperationPath.summary);
// change button text and operation
this.instance.setDoneButton(Operation.extract);
});
return {
component: this.extractRadioButton,
title: ''
};
}
private async createImportRadioButton(): Promise<azdata.FormComponent> {
this.importRadioButton = this.view.modelBuilder.radioButton()
.withProperties({
name: 'selectedOperation',
label: localize('dacFx.importRadioButtonLabel', 'Create a database from a .bacpac file [Import Bacpac]'),
}).component();
this.importRadioButton.onDidClick(() => {
this.removePages();
// add the import page
let page = this.instance.pages.get('importConfig');
this.instance.wizard.addPage(page.wizardPage, ImportOperationPath.options);
this.addSummaryPage(ImportOperationPath.summary);
// change button text and operation
this.instance.setDoneButton(Operation.import);
});
return {
component: this.importRadioButton,
title: ''
};
}
private async createExportRadioButton(): Promise<azdata.FormComponent> {
this.exportRadioButton = this.view.modelBuilder.radioButton()
.withProperties({
name: 'selectedOperation',
label: localize('dacFx.exportRadioButtonLabel', 'Export the schema and data from a database to the logical .bacpac file format [Export Bacpac]'),
}).component();
this.exportRadioButton.onDidClick(() => {
this.removePages();
// add the export pages
let page = this.instance.pages.get('exportConfig');
this.instance.wizard.addPage(page.wizardPage, ExportOperationPath.options);
this.addSummaryPage(ExportOperationPath.summary);
// change button text and operation
this.instance.setDoneButton(Operation.export);
});
return {
component: this.exportRadioButton,
title: ''
};
}
private removePages() {
let numPages = this.instance.wizard.pages.length;
for (let i = numPages - 1; i > 0; --i) {
this.instance.wizard.removePage(i);
}
}
private addSummaryPage(index: number) {
let summaryPage = this.instance.pages.get('summary');
this.instance.wizard.addPage(summaryPage.wizardPage, index);
}
public setupNavigationValidator() {
this.instance.registerNavigationValidator(() => {
return true;
});
}
}