mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-13 17:22:15 -05:00
DacFx import/export wizard (#3162)
Basic wizard to import/export bacpacs and deploy/extract dacpacs
This commit is contained in:
@@ -33,6 +33,15 @@
|
||||
"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": [
|
||||
@@ -48,6 +57,11 @@
|
||||
"command": "flatFileImport.start",
|
||||
"when": "connectionProvider == MSSQL && nodeType && nodeType == Database",
|
||||
"group": "import"
|
||||
},
|
||||
{
|
||||
"command": "dacFx.start",
|
||||
"when": "connectionProvider == MSSQL && nodeType && nodeType == Database",
|
||||
"group": "export"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ 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
|
||||
@@ -35,10 +36,15 @@ export default class MainController extends ControllerBase {
|
||||
this.initializeFlatFileProvider(provider);
|
||||
});
|
||||
|
||||
this.initializeDacFxWizard();
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
private initializeFlatFileProvider(provider: FlatFileProvider) {
|
||||
sqlops.tasks.registerTask('flatFileImport.start', (profile: sqlops.IConnectionProfile, ...args: any[]) => new FlatFileWizard(provider).start(profile, args));
|
||||
}
|
||||
|
||||
private initializeDacFxWizard() {
|
||||
sqlops.tasks.registerTask('dacFx.start', (profile: sqlops.IConnectionProfile, ...args: any[]) => new DataTierApplicationWizard().start(profile, args));
|
||||
}
|
||||
}
|
||||
|
||||
139
extensions/import/src/wizard/api/basePage.ts
Normal file
139
extensions/import/src/wizard/api/basePage.ts
Normal file
@@ -0,0 +1,139 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as sqlops from 'sqlops';
|
||||
import { BaseDataModel } from './models';
|
||||
|
||||
export abstract class BasePage {
|
||||
|
||||
protected readonly wizardPage: sqlops.window.modelviewdialog.WizardPage;
|
||||
protected readonly model: BaseDataModel;
|
||||
protected readonly view: sqlops.ModelView;
|
||||
|
||||
/**
|
||||
* This method constructs all the elements of the page.
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
public async abstract start(): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* This method is called when the user is entering the page.
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
public async abstract onPageEnter(): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* This method is called when the user is leaving the page.
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
async onPageLeave(): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this method to cleanup what you don't need cached in the page.
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
public async cleanup(): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up a navigation validator.
|
||||
* This will be called right before onPageEnter().
|
||||
*/
|
||||
public abstract setupNavigationValidator();
|
||||
|
||||
protected async getServerValues(): Promise<{ connection, displayName, name }[]> {
|
||||
let cons = await sqlops.connection.getActiveConnections();
|
||||
// This user has no active connections ABORT MISSION
|
||||
if (!cons || cons.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let count = -1;
|
||||
let idx = -1;
|
||||
|
||||
|
||||
let values = cons.map(c => {
|
||||
// Handle the code to remember what the user's choice was from before
|
||||
count++;
|
||||
if (idx === -1) {
|
||||
if (this.model.server && c.connectionId === this.model.server.connectionId) {
|
||||
idx = count;
|
||||
} else if (this.model.serverId && c.connectionId === this.model.serverId) {
|
||||
idx = count;
|
||||
}
|
||||
}
|
||||
|
||||
let db = c.options.databaseDisplayName;
|
||||
let usr = c.options.user;
|
||||
let srv = c.options.server;
|
||||
|
||||
if (!db) {
|
||||
db = '<default>';
|
||||
}
|
||||
|
||||
if (!usr) {
|
||||
usr = 'default';
|
||||
}
|
||||
|
||||
let finalName = `${srv}, ${db} (${usr})`;
|
||||
return {
|
||||
connection: c,
|
||||
displayName: finalName,
|
||||
name: c.connectionId
|
||||
};
|
||||
});
|
||||
|
||||
if (idx >= 0) {
|
||||
let tmp = values[0];
|
||||
values[0] = values[idx];
|
||||
values[idx] = tmp;
|
||||
} else {
|
||||
this.deleteServerValues();
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
protected async getDatabaseValues(): Promise<{ displayName, name }[]> {
|
||||
let idx = -1;
|
||||
let count = -1;
|
||||
let values = (await sqlops.connection.listDatabases(this.model.server.connectionId)).map(db => {
|
||||
count++;
|
||||
if (this.model.database && db === this.model.database) {
|
||||
idx = count;
|
||||
}
|
||||
|
||||
return {
|
||||
displayName: db,
|
||||
name: db
|
||||
};
|
||||
});
|
||||
|
||||
if (idx >= 0) {
|
||||
let tmp = values[0];
|
||||
values[0] = values[idx];
|
||||
values[idx] = tmp;
|
||||
} else {
|
||||
this.deleteDatabaseValues();
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
protected deleteServerValues() {
|
||||
delete this.model.server;
|
||||
delete this.model.serverId;
|
||||
delete this.model.database;
|
||||
}
|
||||
|
||||
protected deleteDatabaseValues() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
158
extensions/import/src/wizard/api/dacFxConfigPage.ts
Normal file
158
extensions/import/src/wizard/api/dacFxConfigPage.ts
Normal file
@@ -0,0 +1,158 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as sqlops from 'sqlops';
|
||||
import * as nls from 'vscode-nls';
|
||||
import * 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: sqlops.window.modelviewdialog.WizardPage;
|
||||
protected readonly instance: DataTierApplicationWizard;
|
||||
protected readonly model: DacFxDataModel;
|
||||
protected readonly view: sqlops.ModelView;
|
||||
protected serverDropdown: sqlops.DropDownComponent;
|
||||
protected databaseTextBox: sqlops.InputBoxComponent;
|
||||
protected databaseDropdown: sqlops.DropDownComponent;
|
||||
protected databaseLoader: sqlops.LoadingComponent;
|
||||
protected fileTextBox: sqlops.InputBoxComponent;
|
||||
protected fileButton: sqlops.ButtonComponent;
|
||||
protected fileExtension: string;
|
||||
|
||||
protected constructor(instance: DataTierApplicationWizard, wizardPage: sqlops.window.modelviewdialog.WizardPage, model: DacFxDataModel, view: sqlops.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<sqlops.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<sqlops.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<sqlops.FormComponent> {
|
||||
this.databaseDropdown = this.view.modelBuilder.dropDown().withProperties({
|
||||
required: true
|
||||
}).component();
|
||||
|
||||
// Handle database changes
|
||||
this.databaseDropdown.onValueChanged(async () => {
|
||||
this.model.database = (<sqlops.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 sqlops.CategoryValue {
|
||||
connection: sqlops.connection.Connection;
|
||||
}
|
||||
|
||||
@@ -8,8 +8,9 @@ import { ImportDataModel } from './models';
|
||||
import * as sqlops from 'sqlops';
|
||||
import { FlatFileProvider } from '../../services/contracts';
|
||||
import { FlatFileWizard } from '../flatFileWizard';
|
||||
import { BasePage } from './basePage';
|
||||
|
||||
export abstract class ImportPage {
|
||||
export abstract class ImportPage extends BasePage {
|
||||
|
||||
protected readonly wizardPage: sqlops.window.modelviewdialog.WizardPage;
|
||||
protected readonly instance: FlatFileWizard;
|
||||
@@ -18,42 +19,11 @@ export abstract class ImportPage {
|
||||
protected readonly provider: FlatFileProvider;
|
||||
|
||||
protected constructor(instance: FlatFileWizard, wizardPage: sqlops.window.modelviewdialog.WizardPage, model: ImportDataModel, view: sqlops.ModelView, provider: FlatFileProvider) {
|
||||
super();
|
||||
this.instance = instance;
|
||||
this.wizardPage = wizardPage;
|
||||
this.model = model;
|
||||
this.view = view;
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method constructs all the elements of the page.
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
public async abstract start(): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* This method is called when the user is entering the page.
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
public async abstract onPageEnter(): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* This method is called when the user is leaving the page.
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
public async abstract onPageLeave(): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Sets up a navigation validator.
|
||||
* This will be called right before onPageEnter().
|
||||
*/
|
||||
public abstract setupNavigationValidator();
|
||||
|
||||
/**
|
||||
* Override this method to cleanup what you don't need cached in the page.
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
public async cleanup(): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,15 +6,19 @@
|
||||
|
||||
import * as sqlops from 'sqlops';
|
||||
|
||||
export interface BaseDataModel {
|
||||
server: sqlops.connection.Connection;
|
||||
serverId: string;
|
||||
database: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* The main data model that communicates between the pages.
|
||||
*/
|
||||
export interface ImportDataModel {
|
||||
export interface ImportDataModel extends BaseDataModel {
|
||||
ownerUri: string;
|
||||
proseColumns: ColumnMetadata[];
|
||||
proseDataPreview: string[][];
|
||||
server: sqlops.connection.Connection;
|
||||
serverId: string;
|
||||
database: string;
|
||||
table: string;
|
||||
schema: string;
|
||||
@@ -31,3 +35,14 @@ 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;
|
||||
}
|
||||
|
||||
253
extensions/import/src/wizard/dataTierApplicationWizard.ts
Normal file
253
extensions/import/src/wizard/dataTierApplicationWizard.ts
Normal file
@@ -0,0 +1,253 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import * as sqlops from 'sqlops';
|
||||
import { SelectOperationPage } from './pages/selectOperationpage';
|
||||
import { DeployConfigPage } from './pages/deployConfigPage';
|
||||
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: sqlops.window.modelviewdialog.WizardPage;
|
||||
dacFxPage: BasePage;
|
||||
|
||||
constructor(wizardPage: sqlops.window.modelviewdialog.WizardPage) {
|
||||
this.wizardPage = wizardPage;
|
||||
}
|
||||
}
|
||||
|
||||
export enum Operation {
|
||||
deploy,
|
||||
extract,
|
||||
import,
|
||||
export
|
||||
}
|
||||
|
||||
export class DataTierApplicationWizard {
|
||||
public wizard: sqlops.window.modelviewdialog.Wizard;
|
||||
private connection: sqlops.connection.Connection;
|
||||
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 ? <sqlops.IConnectionProfile>p.connectionProfile : undefined;
|
||||
if (profile) {
|
||||
this.model.serverId = profile.id;
|
||||
this.model.database = profile.databaseName;
|
||||
}
|
||||
|
||||
this.connection = await sqlops.connection.getCurrentConnection();
|
||||
if (!this.connection) {
|
||||
this.connection = await sqlops.connection.openConnectionDialog();
|
||||
}
|
||||
|
||||
this.wizard = sqlops.window.modelviewdialog.createWizard('Data-tier Application Wizard');
|
||||
let selectOperationWizardPage = sqlops.window.modelviewdialog.createWizardPage(localize('dacFx.selectOperationPageName', 'Select an Operation'));
|
||||
let deployConfigWizardPage = sqlops.window.modelviewdialog.createWizardPage(localize('dacFx.deployConfigPageName', 'Select Deploy Dacpac Settings'));
|
||||
let summaryWizardPage = sqlops.window.modelviewdialog.createWizardPage(localize('dacFx.summaryPageName', 'Summary'));
|
||||
let extractConfigWizardPage = sqlops.window.modelviewdialog.createWizardPage(localize('dacFx.extractConfigPageName', 'Select Extract Dacpac Settings'));
|
||||
let importConfigWizardPage = sqlops.window.modelviewdialog.createWizardPage(localize('dacFx.importConfigPageName', 'Select Import Bacpac Settings'));
|
||||
let exportConfigWizardPage = sqlops.window.modelviewdialog.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('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();
|
||||
});
|
||||
|
||||
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: 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 (idx === 2) {
|
||||
page = this.pages.get('summary');
|
||||
}
|
||||
|
||||
if (page !== undefined) {
|
||||
page.dacFxPage.setupNavigationValidator();
|
||||
page.dacFxPage.onPageEnter();
|
||||
}
|
||||
});
|
||||
|
||||
this.wizard.pages = [selectOperationWizardPage, deployConfigWizardPage, summaryWizardPage];
|
||||
this.wizard.generateScriptButton.hidden = true;
|
||||
this.wizard.doneButton.onClick(async () => await this.executeOperation());
|
||||
|
||||
this.wizard.open();
|
||||
}
|
||||
|
||||
public registerNavigationValidator(validator: (pageChangeInfo: sqlops.window.modelviewdialog.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async deploy() {
|
||||
let service = await DataTierApplicationWizard.getService();
|
||||
let ownerUri = await sqlops.connection.getUriForConnection(this.model.server.connectionId);
|
||||
|
||||
let result = await service.deployDacpac(this.model.filePath, this.model.database, this.model.upgradeExisting, ownerUri, sqlops.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();
|
||||
let ownerUri = await sqlops.connection.getUriForConnection(this.model.server.connectionId);
|
||||
|
||||
let result = await service.extractDacpac(this.model.database, this.model.filePath, this.model.database, this.model.version, ownerUri, sqlops.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();
|
||||
let ownerUri = await sqlops.connection.getUriForConnection(this.model.server.connectionId);
|
||||
|
||||
let result = await service.exportBacpac(this.model.database, this.model.filePath, ownerUri, sqlops.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();
|
||||
let ownerUri = await sqlops.connection.getUriForConnection(this.model.server.connectionId);
|
||||
|
||||
let result = await service.importBacpac(this.model.filePath, this.model.database, ownerUri, sqlops.TaskExecutionMode.execute);
|
||||
if (!result || !result.success) {
|
||||
vscode.window.showErrorMessage(
|
||||
localize('alertData.importErrorMessage', "Import failed '{0}'", result.errorMessage ? result.errorMessage : 'Unknown'));
|
||||
}
|
||||
}
|
||||
|
||||
public static async getService(): Promise<sqlops.DacFxServicesProvider> {
|
||||
let currentConnection = await sqlops.connection.getCurrentConnection();
|
||||
let service = sqlops.dataprotocol.getProvider<sqlops.DacFxServicesProvider>(currentConnection.providerName, sqlops.DataProviderType.DacFxServicesProvider);
|
||||
return service;
|
||||
}
|
||||
}
|
||||
112
extensions/import/src/wizard/pages/dacFxSummaryPage.ts
Normal file
112
extensions/import/src/wizard/pages/dacFxSummaryPage.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
import * as sqlops from 'sqlops';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { 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: sqlops.window.modelviewdialog.WizardPage;
|
||||
protected readonly instance: DataTierApplicationWizard;
|
||||
protected readonly model: DacFxDataModel;
|
||||
protected readonly view: sqlops.ModelView;
|
||||
|
||||
private form: sqlops.FormContainer;
|
||||
private table: sqlops.TableComponent;
|
||||
private loader: sqlops.LoadingComponent;
|
||||
|
||||
public constructor(instance: DataTierApplicationWizard, wizardPage: sqlops.window.modelviewdialog.WizardPage, model: DacFxDataModel, view: sqlops.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;
|
||||
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');
|
||||
|
||||
switch (this.instance.selectedOperation) {
|
||||
case Operation.deploy: {
|
||||
data = [
|
||||
[targetServer, this.model.serverName],
|
||||
[fileLocation, this.model.filePath],
|
||||
[targetDatabase, this.model.database]];
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
this.table.updateProperties({
|
||||
data: data,
|
||||
columns: ['Setting', 'Value'],
|
||||
width: 600,
|
||||
height: 200
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
190
extensions/import/src/wizard/pages/deployConfigPage.ts
Normal file
190
extensions/import/src/wizard/pages/deployConfigPage.ts
Normal file
@@ -0,0 +1,190 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
import * as sqlops from 'sqlops';
|
||||
import * as nls from 'vscode-nls';
|
||||
import * 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 DeployConfigPage extends DacFxConfigPage {
|
||||
|
||||
protected readonly wizardPage: sqlops.window.modelviewdialog.WizardPage;
|
||||
protected readonly instance: DataTierApplicationWizard;
|
||||
protected readonly model: DacFxDataModel;
|
||||
protected readonly view: sqlops.ModelView;
|
||||
private databaseDropdownComponent: sqlops.FormComponent;
|
||||
private databaseComponent: sqlops.FormComponent;
|
||||
private formBuilder: sqlops.FormBuilder;
|
||||
private form: sqlops.FormContainer;
|
||||
|
||||
public constructor(instance: DataTierApplicationWizard, wizardPage: sqlops.window.modelviewdialog.WizardPage, model: DacFxDataModel, view: sqlops.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<sqlops.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<sqlops.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 = (<sqlops.CategoryValue>this.databaseDropdown.value).name;
|
||||
});
|
||||
|
||||
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;
|
||||
});
|
||||
|
||||
// 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<sqlops.FormComponent> {
|
||||
this.databaseDropdown = this.view.modelBuilder.dropDown().withProperties({
|
||||
required: true
|
||||
}).component();
|
||||
// Handle database changes
|
||||
this.databaseDropdown.onValueChanged(async () => {
|
||||
this.model.database = (<sqlops.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();
|
||||
|
||||
if (this.model.database === undefined) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
100
extensions/import/src/wizard/pages/exportConfigPage.ts
Normal file
100
extensions/import/src/wizard/pages/exportConfigPage.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
import * as sqlops from 'sqlops';
|
||||
import * as nls from 'vscode-nls';
|
||||
import * 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: sqlops.window.modelviewdialog.WizardPage;
|
||||
protected readonly instance: DataTierApplicationWizard;
|
||||
protected readonly model: DacFxDataModel;
|
||||
protected readonly view: sqlops.ModelView;
|
||||
|
||||
private form: sqlops.FormContainer;
|
||||
|
||||
public constructor(instance: DataTierApplicationWizard, wizardPage: sqlops.window.modelviewdialog.WizardPage, model: DacFxDataModel, view: sqlops.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<sqlops.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]
|
||||
};
|
||||
}
|
||||
}
|
||||
122
extensions/import/src/wizard/pages/extractConfigPage.ts
Normal file
122
extensions/import/src/wizard/pages/extractConfigPage.ts
Normal file
@@ -0,0 +1,122 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
import * as sqlops from 'sqlops';
|
||||
import * as nls from 'vscode-nls';
|
||||
import * 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: sqlops.window.modelviewdialog.WizardPage;
|
||||
protected readonly instance: DataTierApplicationWizard;
|
||||
protected readonly model: DacFxDataModel;
|
||||
protected readonly view: sqlops.ModelView;
|
||||
|
||||
private form: sqlops.FormContainer;
|
||||
private versionTextBox: sqlops.InputBoxComponent;
|
||||
|
||||
public constructor(instance: DataTierApplicationWizard, wizardPage: sqlops.window.modelviewdialog.WizardPage, model: DacFxDataModel, view: sqlops.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<sqlops.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<sqlops.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)'),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -102,57 +102,9 @@ export class FileConfigPage extends ImportPage {
|
||||
}
|
||||
|
||||
private async populateServerDropdown(): Promise<boolean> {
|
||||
let cons = await sqlops.connection.getActiveConnections();
|
||||
// This user has no active connections ABORT MISSION
|
||||
if (!cons || cons.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
let count = -1;
|
||||
let idx = -1;
|
||||
|
||||
|
||||
let values = cons.map(c => {
|
||||
// Handle the code to remember what the user's choice was from before
|
||||
count++;
|
||||
if (idx === -1) {
|
||||
if (this.model.server && c.connectionId === this.model.server.connectionId) {
|
||||
idx = count;
|
||||
} else if (this.model.serverId && c.connectionId === this.model.serverId) {
|
||||
idx = count;
|
||||
}
|
||||
}
|
||||
|
||||
let db = c.options.databaseDisplayName;
|
||||
let usr = c.options.user;
|
||||
let srv = c.options.server;
|
||||
|
||||
if (!db) {
|
||||
db = '<default>';
|
||||
}
|
||||
|
||||
if (!usr) {
|
||||
usr = 'default';
|
||||
}
|
||||
|
||||
let finalName = `${srv}, ${db} (${usr})`;
|
||||
return {
|
||||
connection: c,
|
||||
displayName: finalName,
|
||||
name: c.connectionId
|
||||
};
|
||||
});
|
||||
|
||||
if (idx >= 0) {
|
||||
let tmp = values[0];
|
||||
values[0] = values[idx];
|
||||
values[idx] = tmp;
|
||||
} else {
|
||||
delete this.model.server;
|
||||
delete this.model.serverId;
|
||||
delete this.model.database;
|
||||
delete this.model.schema;
|
||||
let values = await this.getServerValues();
|
||||
if (values === undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.model.server = values[0].connection;
|
||||
@@ -195,29 +147,7 @@ export class FileConfigPage extends ImportPage {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
let idx = -1;
|
||||
let count = -1;
|
||||
let values = (await sqlops.connection.listDatabases(this.model.server.connectionId)).map(db => {
|
||||
count++;
|
||||
if (this.model.database && db === this.model.database) {
|
||||
idx = count;
|
||||
}
|
||||
|
||||
return {
|
||||
displayName: db,
|
||||
name: db
|
||||
};
|
||||
});
|
||||
|
||||
if (idx >= 0) {
|
||||
let tmp = values[0];
|
||||
values[0] = values[idx];
|
||||
values[idx] = tmp;
|
||||
} else {
|
||||
delete this.model.database;
|
||||
delete this.model.schema;
|
||||
}
|
||||
let values = await this.getDatabaseValues();
|
||||
|
||||
this.model.database = values[0].name;
|
||||
|
||||
@@ -377,6 +307,18 @@ export class FileConfigPage extends ImportPage {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected deleteServerValues() {
|
||||
delete this.model.server;
|
||||
delete this.model.serverId;
|
||||
delete this.model.database;
|
||||
delete this.model.schema;
|
||||
}
|
||||
|
||||
protected deleteDatabaseValues() {
|
||||
delete this.model.database;
|
||||
delete this.model.schema;
|
||||
}
|
||||
|
||||
// private async populateTableNames(): Promise<boolean> {
|
||||
// this.tableNames = [];
|
||||
// let databaseName = (<sqlops.CategoryValue>this.databaseDropdown.value).name;
|
||||
|
||||
101
extensions/import/src/wizard/pages/importConfigPage.ts
Normal file
101
extensions/import/src/wizard/pages/importConfigPage.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
import * as sqlops from 'sqlops';
|
||||
import * as nls from 'vscode-nls';
|
||||
import * 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: sqlops.window.modelviewdialog.WizardPage;
|
||||
protected readonly instance: DataTierApplicationWizard;
|
||||
protected readonly model: DacFxDataModel;
|
||||
protected readonly view: sqlops.ModelView;
|
||||
|
||||
private form: sqlops.FormContainer;
|
||||
|
||||
public constructor(instance: DataTierApplicationWizard, wizardPage: sqlops.window.modelviewdialog.WizardPage, model: DacFxDataModel, view: sqlops.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<sqlops.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;
|
||||
}
|
||||
}
|
||||
174
extensions/import/src/wizard/pages/selectOperationpage.ts
Normal file
174
extensions/import/src/wizard/pages/selectOperationpage.ts
Normal file
@@ -0,0 +1,174 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
import * as sqlops from 'sqlops';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { DacFxDataModel } from '../api/models';
|
||||
import { DataTierApplicationWizard, Operation } from '../DataTierApplicationWizard';
|
||||
import { BasePage } from '../api/basePage';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class SelectOperationPage extends BasePage {
|
||||
|
||||
protected readonly wizardPage: sqlops.window.modelviewdialog.WizardPage;
|
||||
protected readonly instance: DataTierApplicationWizard;
|
||||
protected readonly model: DacFxDataModel;
|
||||
protected readonly view: sqlops.ModelView;
|
||||
|
||||
private deployRadioButton: sqlops.RadioButtonComponent;
|
||||
private extractRadioButton: sqlops.RadioButtonComponent;
|
||||
private importRadioButton: sqlops.RadioButtonComponent;
|
||||
private exportRadioButton: sqlops.RadioButtonComponent;
|
||||
private form: sqlops.FormContainer;
|
||||
|
||||
public constructor(instance: DataTierApplicationWizard, wizardPage: sqlops.window.modelviewdialog.WizardPage, model: DacFxDataModel, view: sqlops.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> {
|
||||
let numPages = this.instance.wizard.pages.length;
|
||||
for (let i = numPages - 1; i > 2; --i) {
|
||||
await this.instance.wizard.removePage(i);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async createDeployRadioButton(): Promise<sqlops.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(() => {
|
||||
// remove the previous page
|
||||
this.instance.wizard.removePage(1);
|
||||
|
||||
// add deploy page
|
||||
let page = this.instance.pages.get('deployConfig');
|
||||
this.instance.wizard.addPage(page.wizardPage, 1);
|
||||
|
||||
// change button text and operation
|
||||
this.instance.setDoneButton(Operation.deploy);
|
||||
});
|
||||
|
||||
return {
|
||||
component: this.deployRadioButton,
|
||||
title: ''
|
||||
};
|
||||
}
|
||||
|
||||
private async createExtractRadioButton(): Promise<sqlops.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(() => {
|
||||
// remove the previous pages
|
||||
this.instance.wizard.removePage(1);
|
||||
|
||||
// add the extract page
|
||||
let page = this.instance.pages.get('extractConfig');
|
||||
this.instance.wizard.addPage(page.wizardPage, 1);
|
||||
|
||||
// change button text and operation
|
||||
this.instance.setDoneButton(Operation.extract);
|
||||
});
|
||||
|
||||
return {
|
||||
component: this.extractRadioButton,
|
||||
title: ''
|
||||
};
|
||||
}
|
||||
|
||||
private async createImportRadioButton(): Promise<sqlops.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(() => {
|
||||
// remove the previous page
|
||||
this.instance.wizard.removePage(1);
|
||||
|
||||
// add the import page
|
||||
let page = this.instance.pages.get('importConfig');
|
||||
this.instance.wizard.addPage(page.wizardPage, 1);
|
||||
|
||||
// change button text and operation
|
||||
this.instance.setDoneButton(Operation.import);
|
||||
});
|
||||
|
||||
return {
|
||||
component: this.importRadioButton,
|
||||
title: ''
|
||||
};
|
||||
}
|
||||
|
||||
private async createExportRadioButton(): Promise<sqlops.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(() => {
|
||||
// remove the 2 previous pages
|
||||
this.instance.wizard.removePage(1);
|
||||
|
||||
// add the export pages
|
||||
let page = this.instance.pages.get('exportConfig');
|
||||
this.instance.wizard.addPage(page.wizardPage, 1);
|
||||
|
||||
// change button text and operation
|
||||
this.instance.setDoneButton(Operation.export);
|
||||
});
|
||||
|
||||
return {
|
||||
component: this.exportRadioButton,
|
||||
title: ''
|
||||
};
|
||||
}
|
||||
|
||||
public setupNavigationValidator() {
|
||||
this.instance.registerNavigationValidator(() => {
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -291,3 +291,60 @@ export namespace DeleteAgentJobScheduleRequest {
|
||||
}
|
||||
|
||||
// ------------------------------- < Agent Management > ------------------------------------
|
||||
|
||||
// ------------------------------- < DacFx > ------------------------------------
|
||||
|
||||
export enum TaskExecutionMode {
|
||||
execute = 0,
|
||||
script = 1,
|
||||
executeAndScript = 2,
|
||||
}
|
||||
export interface ExportParams {
|
||||
databaseName: string;
|
||||
packageFilePath: string;
|
||||
ownerUri: string;
|
||||
taskExecutionMode: TaskExecutionMode;
|
||||
}
|
||||
|
||||
export interface ImportParams {
|
||||
packageFilePath: string;
|
||||
databaseName: string;
|
||||
ownerUri: string;
|
||||
taskExecutionMode: TaskExecutionMode;
|
||||
}
|
||||
|
||||
|
||||
export interface ExtractParams {
|
||||
databaseName: string;
|
||||
packageFilePath: string;
|
||||
applicationName: string;
|
||||
applicationVersion: string;
|
||||
ownerUri: string;
|
||||
taskExecutionMode: TaskExecutionMode;
|
||||
}
|
||||
|
||||
export interface DeployParams {
|
||||
packageFilePath: string;
|
||||
databaseName: string;
|
||||
upgradeExisting: boolean;
|
||||
ownerUri: string;
|
||||
taskExecutionMode: TaskExecutionMode;
|
||||
}
|
||||
|
||||
export namespace ExportRequest {
|
||||
export const type = new RequestType<ExportParams, sqlops.DacFxResult, void, void>('dacfx/export');
|
||||
}
|
||||
|
||||
export namespace ImportRequest {
|
||||
export const type = new RequestType<ImportParams, sqlops.DacFxResult, void, void>('dacfx/import');
|
||||
}
|
||||
|
||||
export namespace ExtractRequest {
|
||||
export const type = new RequestType<ExtractParams, sqlops.DacFxResult, void, void>('dacfx/extract');
|
||||
}
|
||||
|
||||
export namespace DeployRequest {
|
||||
export const type = new RequestType<DeployParams, sqlops.DacFxResult, void, void>('dacfx/deploy');
|
||||
}
|
||||
|
||||
// ------------------------------- < DacFx > ------------------------------------
|
||||
@@ -8,7 +8,7 @@ import { SqlOpsDataClient, SqlOpsFeature } from 'dataprotocol-client';
|
||||
import { ClientCapabilities, StaticFeature, RPCMessageType, ServerCapabilities } from 'vscode-languageclient';
|
||||
import { Disposable } from 'vscode';
|
||||
import { Telemetry } from './telemetry';
|
||||
import * as contracts from './contracts';
|
||||
import * as contracts from './contracts';
|
||||
import * as sqlops from 'sqlops';
|
||||
import * as Utils from './utils';
|
||||
import * as UUID from 'vscode-languageclient/lib/utils/uuid';
|
||||
@@ -28,6 +28,94 @@ export class TelemetryFeature implements StaticFeature {
|
||||
}
|
||||
}
|
||||
|
||||
export class DacFxServicesFeature extends SqlOpsFeature<undefined> {
|
||||
private static readonly messageTypes: RPCMessageType[] = [
|
||||
contracts.ExportRequest.type,
|
||||
contracts.ImportRequest.type,
|
||||
contracts.ExtractRequest.type,
|
||||
contracts.DeployRequest.type
|
||||
];
|
||||
|
||||
constructor(client: SqlOpsDataClient) {
|
||||
super(client, DacFxServicesFeature.messageTypes);
|
||||
}
|
||||
|
||||
public fillClientCapabilities(capabilities: ClientCapabilities): void {
|
||||
}
|
||||
|
||||
public initialize(capabilities: ServerCapabilities): void {
|
||||
this.register(this.messages, {
|
||||
id: UUID.generateUuid(),
|
||||
registerOptions: undefined
|
||||
});
|
||||
}
|
||||
|
||||
protected registerProvider(options: undefined): Disposable {
|
||||
const client = this._client;
|
||||
let self = this;
|
||||
|
||||
let exportBacpac = (databaseName: string, packageFilePath: string, ownerUri: string, taskExecutionMode: sqlops.TaskExecutionMode): Thenable<sqlops.DacFxResult> => {
|
||||
let params: contracts.ExportParams = { databaseName: databaseName, packageFilePath: packageFilePath, ownerUri: ownerUri, taskExecutionMode: taskExecutionMode };
|
||||
return client.sendRequest(contracts.ExportRequest.type, params).then(
|
||||
r => {
|
||||
return r;
|
||||
},
|
||||
e => {
|
||||
client.logFailedRequest(contracts.ExportRequest.type, e);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
let importBacpac = (packageFilePath: string, databaseName: string, ownerUri: string, taskExecutionMode: sqlops.TaskExecutionMode): Thenable<sqlops.DacFxResult> => {
|
||||
let params: contracts.ImportParams = { packageFilePath: packageFilePath, databaseName: databaseName, ownerUri: ownerUri, taskExecutionMode: taskExecutionMode };
|
||||
return client.sendRequest(contracts.ImportRequest.type, params).then(
|
||||
r => {
|
||||
return r;
|
||||
},
|
||||
e => {
|
||||
client.logFailedRequest(contracts.ImportRequest.type, e);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
let extractDacpac = (databaseName: string, packageFilePath: string, applicationName: string, applicationVersion: string, ownerUri: string, taskExecutionMode: sqlops.TaskExecutionMode): Thenable<sqlops.DacFxResult> => {
|
||||
let params: contracts.ExtractParams = { databaseName: databaseName, packageFilePath: packageFilePath, applicationName: applicationName, applicationVersion: applicationVersion, ownerUri: ownerUri, taskExecutionMode: taskExecutionMode };
|
||||
return client.sendRequest(contracts.ExtractRequest.type, params).then(
|
||||
r => {
|
||||
return r;
|
||||
},
|
||||
e => {
|
||||
client.logFailedRequest(contracts.ExtractRequest.type, e);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
let deployDacpac = (packageFilePath: string, targetDatabaseName: string, upgradeExisting: boolean, ownerUri: string, taskExecutionMode: sqlops.TaskExecutionMode): Thenable<sqlops.DacFxResult> => {
|
||||
let params: contracts.DeployParams = { packageFilePath: packageFilePath, databaseName: targetDatabaseName, upgradeExisting: upgradeExisting, ownerUri: ownerUri, taskExecutionMode: taskExecutionMode };
|
||||
return client.sendRequest(contracts.DeployRequest.type, params).then(
|
||||
r => {
|
||||
return r;
|
||||
},
|
||||
e => {
|
||||
client.logFailedRequest(contracts.DeployRequest.type, e);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
return sqlops.dataprotocol.registerDacFxServicesProvider({
|
||||
providerId: client.providerId,
|
||||
exportBacpac,
|
||||
importBacpac,
|
||||
extractDacpac,
|
||||
deployDacpac
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class AgentServicesFeature extends SqlOpsFeature<undefined> {
|
||||
private static readonly messagesTypes: RPCMessageType[] = [
|
||||
contracts.AgentJobsRequest.type,
|
||||
@@ -229,7 +317,7 @@ export class AgentServicesFeature extends SqlOpsFeature<undefined> {
|
||||
};
|
||||
|
||||
// Alert management methods
|
||||
let getAlerts = (ownerUri: string): Thenable<sqlops.AgentAlertsResult> => {
|
||||
let getAlerts = (ownerUri: string): Thenable<sqlops.AgentAlertsResult> => {
|
||||
let params: contracts.AgentAlertsParams = {
|
||||
ownerUri: ownerUri
|
||||
};
|
||||
@@ -299,7 +387,7 @@ export class AgentServicesFeature extends SqlOpsFeature<undefined> {
|
||||
};
|
||||
|
||||
// Operator management methods
|
||||
let getOperators = (ownerUri: string): Thenable<sqlops.AgentOperatorsResult> => {
|
||||
let getOperators = (ownerUri: string): Thenable<sqlops.AgentOperatorsResult> => {
|
||||
let params: contracts.AgentOperatorsParams = {
|
||||
ownerUri: ownerUri
|
||||
};
|
||||
@@ -369,7 +457,7 @@ export class AgentServicesFeature extends SqlOpsFeature<undefined> {
|
||||
};
|
||||
|
||||
// Proxy management methods
|
||||
let getProxies = (ownerUri: string): Thenable<sqlops.AgentProxiesResult> => {
|
||||
let getProxies = (ownerUri: string): Thenable<sqlops.AgentProxiesResult> => {
|
||||
let params: contracts.AgentProxiesParams = {
|
||||
ownerUri: ownerUri
|
||||
};
|
||||
@@ -439,7 +527,7 @@ export class AgentServicesFeature extends SqlOpsFeature<undefined> {
|
||||
};
|
||||
|
||||
// Agent Credential Method
|
||||
let getCredentials = (ownerUri: string): Thenable<sqlops.GetCredentialsResult> => {
|
||||
let getCredentials = (ownerUri: string): Thenable<sqlops.GetCredentialsResult> => {
|
||||
let params: contracts.GetCredentialsParams = {
|
||||
ownerUri: ownerUri
|
||||
};
|
||||
@@ -455,7 +543,7 @@ export class AgentServicesFeature extends SqlOpsFeature<undefined> {
|
||||
|
||||
|
||||
// Job Schedule management methods
|
||||
let getJobSchedules = (ownerUri: string): Thenable<sqlops.AgentJobSchedulesResult> => {
|
||||
let getJobSchedules = (ownerUri: string): Thenable<sqlops.AgentJobSchedulesResult> => {
|
||||
let params: contracts.AgentJobScheduleParams = {
|
||||
ownerUri: ownerUri
|
||||
};
|
||||
|
||||
@@ -16,7 +16,7 @@ import { CredentialStore } from './credentialstore/credentialstore';
|
||||
import { AzureResourceProvider } from './resourceProvider/resourceProvider';
|
||||
import * as Utils from './utils';
|
||||
import { Telemetry, LanguageClientErrorHandler } from './telemetry';
|
||||
import { TelemetryFeature, AgentServicesFeature } from './features';
|
||||
import { TelemetryFeature, AgentServicesFeature, DacFxServicesFeature } from './features';
|
||||
|
||||
const baseConfig = require('./config.json');
|
||||
const outputChannel = vscode.window.createOutputChannel(Constants.serviceName);
|
||||
@@ -55,7 +55,8 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
// we only want to add new features
|
||||
...SqlOpsDataClient.defaultFeatures,
|
||||
TelemetryFeature,
|
||||
AgentServicesFeature
|
||||
AgentServicesFeature,
|
||||
DacFxServicesFeature,
|
||||
],
|
||||
outputChannel: new CustomOutputChannel()
|
||||
};
|
||||
|
||||
78
src/sql/services/dacfx/dacFxService.ts
Normal file
78
src/sql/services/dacfx/dacFxService.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as sqlops from 'sqlops';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IConnectionManagementService } from 'sql/parts/connection/common/connectionManagement';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { localize } from 'vs/nls';
|
||||
import { data } from 'vs/base/test/common/filters.perf.data';
|
||||
|
||||
export const SERVICE_ID = 'dacFxService';
|
||||
export const IDacFxService = createDecorator<IDacFxService>(SERVICE_ID);
|
||||
|
||||
export interface IDacFxService {
|
||||
_serviceBrand: any;
|
||||
|
||||
registerProvider(providerId: string, provider: sqlops.DacFxServicesProvider): void;
|
||||
exportBacpac(sourceDatabaseName: string, packageFilePath: string, ownerUri: string, taskExecutionMode: sqlops.TaskExecutionMode): void;
|
||||
importBacpac(packageFilePath: string, targetDatabaseName: string, ownerUri: string, taskExecutionMode: sqlops.TaskExecutionMode): void;
|
||||
extractDacpac(sourceDatabaseName: string, packageFilePath: string, applicationName: string, applicationVersion: string, ownerUri: string, taskExecutionMode: sqlops.TaskExecutionMode): void;
|
||||
deployDacpac(packageFilePath: string, targetDatabaseName: string, upgradeExisting: boolean, ownerUri: string, taskExecutionMode: sqlops.TaskExecutionMode): void;
|
||||
}
|
||||
|
||||
export class DacFxService implements IDacFxService {
|
||||
_serviceBrand: any;
|
||||
private _providers: { [handle: string]: sqlops.DacFxServicesProvider; } = Object.create(null);
|
||||
|
||||
constructor(
|
||||
@IConnectionManagementService private _connectionService: IConnectionManagementService
|
||||
) {
|
||||
}
|
||||
|
||||
registerProvider(providerId: string, provider: sqlops.DacFxServicesProvider): void {
|
||||
this._providers[providerId] = provider;
|
||||
}
|
||||
|
||||
exportBacpac(databasesName: string, packageFilePath: string, ownerUri: string, taskExecutionMode: sqlops.TaskExecutionMode): Thenable<sqlops.DacFxResult> {
|
||||
return this._runAction(ownerUri, (runner) => {
|
||||
return runner.exportBacpac(databasesName, packageFilePath, ownerUri, taskExecutionMode);
|
||||
});
|
||||
}
|
||||
|
||||
importBacpac(packageFilePath: string, databaseName: string, ownerUri: string, taskExecutionMode: sqlops.TaskExecutionMode): Thenable<sqlops.DacFxResult> {
|
||||
return this._runAction(ownerUri, (runner) => {
|
||||
return runner.importBacpac(packageFilePath, databaseName, ownerUri, taskExecutionMode);
|
||||
});
|
||||
}
|
||||
|
||||
extractDacpac(databaseName: string, packageFilePath: string, applicationName: string, applicationVersion: string, ownerUri: string, taskExecutionMode: sqlops.TaskExecutionMode): Thenable<sqlops.DacFxResult> {
|
||||
return this._runAction(ownerUri, (runner) => {
|
||||
return runner.extractDacpac(databaseName, packageFilePath, applicationName, applicationVersion, ownerUri, taskExecutionMode);
|
||||
});
|
||||
}
|
||||
|
||||
deployDacpac(packageFilePath: string, databaseName: string, upgradeExisting: boolean, ownerUri: string, taskExecutionMode: sqlops.TaskExecutionMode): Thenable<sqlops.DacFxResult> {
|
||||
return this._runAction(ownerUri, (runner) => {
|
||||
return runner.deployDacpac(packageFilePath, databaseName, upgradeExisting, ownerUri, taskExecutionMode);
|
||||
});
|
||||
}
|
||||
|
||||
private _runAction<T>(uri: string, action: (handler: sqlops.DacFxServicesProvider) => Thenable<T>): Thenable<T> {
|
||||
let providerId: string = this._connectionService.getProviderIdFromUri(uri);
|
||||
|
||||
if (!providerId) {
|
||||
return TPromise.wrapError(new Error(localize('providerIdNotValidError', "Connection is required in order to interact with DacFxService")));
|
||||
}
|
||||
let handler = this._providers[providerId];
|
||||
if (handler) {
|
||||
return action(handler);
|
||||
} else {
|
||||
return TPromise.wrapError(new Error(localize('noHandlerRegistered', "No Handler Registered")));
|
||||
}
|
||||
}
|
||||
}
|
||||
45
src/sql/sqlops.d.ts
vendored
45
src/sql/sqlops.d.ts
vendored
@@ -37,6 +37,8 @@ declare module 'sqlops' {
|
||||
|
||||
export function registerCapabilitiesServiceProvider(provider: CapabilitiesProvider): vscode.Disposable;
|
||||
|
||||
export function registerDacFxServicesProvider(provider: DacFxServicesProvider): vscode.Disposable;
|
||||
|
||||
/**
|
||||
* An [event](#Event) which fires when the specific flavor of a language used in DMP
|
||||
* connections has changed. And example is for a SQL connection, the flavor changes
|
||||
@@ -1583,6 +1585,49 @@ declare module 'sqlops' {
|
||||
registerOnUpdated(handler: () => any): void;
|
||||
}
|
||||
|
||||
// DacFx interfaces -----------------------------------------------------------------------
|
||||
export interface DacFxResult extends ResultStatus {
|
||||
operationId: string;
|
||||
}
|
||||
|
||||
export interface ExportParams {
|
||||
databaseName: string;
|
||||
packageFilePath: string;
|
||||
ownerUri: string;
|
||||
taskExecutionMode: TaskExecutionMode;
|
||||
}
|
||||
|
||||
export interface ImportParams {
|
||||
packageFilePath: string;
|
||||
databaseName: string;
|
||||
ownerUri: string;
|
||||
taskExecutionMode: TaskExecutionMode;
|
||||
}
|
||||
|
||||
export interface ExtractParams {
|
||||
databaseName: string;
|
||||
packageFilePath: string;
|
||||
applicationName: string;
|
||||
applicationVersion: string;
|
||||
ownerUri: string;
|
||||
taskExecutionMode: TaskExecutionMode;
|
||||
}
|
||||
|
||||
export interface DeployParams {
|
||||
packageFilePath: string;
|
||||
databaseName: string;
|
||||
upgradeExisting: boolean;
|
||||
ownerUri: string;
|
||||
taskExecutionMode: TaskExecutionMode;
|
||||
}
|
||||
|
||||
export interface DacFxServicesProvider extends DataProvider {
|
||||
exportBacpac(databaseName: string, packageFilePath: string, ownerUri: string, taskExecutionMode: TaskExecutionMode): Thenable<DacFxResult>;
|
||||
importBacpac(packageFilePath: string, databaseName: string, ownerUri: string, taskExecutionMode: TaskExecutionMode): Thenable<DacFxResult>;
|
||||
extractDacpac(databaseName: string, packageFilePath: string, applicationName: string, applicationVersion: string, ownerUri: string, taskExecutionMode: TaskExecutionMode): Thenable<DacFxResult>;
|
||||
deployDacpac(packageFilePath: string, databaseName: string, upgradeExisting: boolean, ownerUri: string, taskExecutionMode: TaskExecutionMode): Thenable<DacFxResult>;
|
||||
}
|
||||
|
||||
// Security service interfaces ------------------------------------------------------------------------
|
||||
export interface CredentialInfo {
|
||||
id: number;
|
||||
|
||||
3
src/sql/sqlops.proposed.d.ts
vendored
3
src/sql/sqlops.proposed.d.ts
vendored
@@ -1232,7 +1232,8 @@ declare module 'sqlops' {
|
||||
QueryProvider = 'QueryProvider',
|
||||
AdminServicesProvider = 'AdminServicesProvider',
|
||||
AgentServicesProvider = 'AgentServicesProvider',
|
||||
CapabilitiesProvider = 'CapabilitiesProvider'
|
||||
CapabilitiesProvider = 'CapabilitiesProvider',
|
||||
DacFxServicesProvider = 'DacFxServicesProvider',
|
||||
}
|
||||
|
||||
export namespace dataprotocol {
|
||||
|
||||
@@ -285,7 +285,8 @@ export enum DataProviderType {
|
||||
QueryProvider = 'QueryProvider',
|
||||
AdminServicesProvider = 'AdminServicesProvider',
|
||||
AgentServicesProvider = 'AgentServicesProvider',
|
||||
CapabilitiesProvider = 'CapabilitiesProvider'
|
||||
CapabilitiesProvider = 'CapabilitiesProvider',
|
||||
DacFxServicesProvider = 'DacFxServicesProvider',
|
||||
}
|
||||
|
||||
export enum DeclarativeDataType {
|
||||
|
||||
@@ -156,6 +156,12 @@ export class ExtHostDataProtocol extends ExtHostDataProtocolShape {
|
||||
return rt;
|
||||
}
|
||||
|
||||
$registerDacFxServiceProvider(provider: sqlops.DacFxServicesProvider): vscode.Disposable {
|
||||
let rt = this.registerProvider(provider, DataProviderType.DacFxServicesProvider);
|
||||
this._proxy.$registerDacFxServicesProvider(provider.providerId, provider.handle);
|
||||
return rt;
|
||||
}
|
||||
|
||||
// Capabilities Discovery handlers
|
||||
$getServerCapabilities(handle: number, client: sqlops.DataProtocolClientCapabilities): Thenable<sqlops.DataProtocolServerCapabilities> {
|
||||
return this._resolveProvider<sqlops.CapabilitiesProvider>(handle).getServerCapabilities(client);
|
||||
|
||||
@@ -26,6 +26,7 @@ import { IProfilerService } from 'sql/parts/profiler/service/interfaces';
|
||||
import { ISerializationService } from 'sql/services/serialization/serializationService';
|
||||
import { IFileBrowserService } from 'sql/parts/fileBrowser/common/interfaces';
|
||||
import { IExtHostContext } from 'vs/workbench/api/node/extHost.protocol';
|
||||
import { IDacFxService } from 'sql/services/dacfx/dacFxService';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
|
||||
|
||||
/**
|
||||
@@ -55,7 +56,8 @@ export class MainThreadDataProtocol implements MainThreadDataProtocolShape {
|
||||
@ITaskService private _taskService: ITaskService,
|
||||
@IProfilerService private _profilerService: IProfilerService,
|
||||
@ISerializationService private _serializationService: ISerializationService,
|
||||
@IFileBrowserService private _fileBrowserService: IFileBrowserService
|
||||
@IFileBrowserService private _fileBrowserService: IFileBrowserService,
|
||||
@IDacFxService private _dacFxService: IDacFxService,
|
||||
) {
|
||||
if (extHostContext) {
|
||||
this._proxy = extHostContext.getProxy(SqlExtHostContext.ExtHostDataProtocol);
|
||||
@@ -399,6 +401,26 @@ export class MainThreadDataProtocol implements MainThreadDataProtocolShape {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public $registerDacFxServicesProvider(providerId: string, handle: number): TPromise<any> {
|
||||
const self = this;
|
||||
this._dacFxService.registerProvider(providerId, <sqlops.DacFxServicesProvider>{
|
||||
exportBacpac(databaseName: string, packageFilePath: string, ownerUri: string, taskExecutionMode: sqlops.TaskExecutionMode): Thenable<sqlops.DacFxResult> {
|
||||
return self._proxy.$exportBacpac(handle, databaseName, packageFilePath, ownerUri, taskExecutionMode);
|
||||
},
|
||||
importBacpac(packageFilePath: string, databaseName: string, ownerUri: string, taskExecutionMode: sqlops.TaskExecutionMode): Thenable<sqlops.DacFxResult> {
|
||||
return self._proxy.$importBacpac(handle, packageFilePath, databaseName, ownerUri, taskExecutionMode);
|
||||
},
|
||||
extractDacpac(databaseName: string, packageFilePath: string, applicationName: string, applicationVersion: string, ownerUri: string, taskExecutionMode: sqlops.TaskExecutionMode): Thenable<sqlops.DacFxResult> {
|
||||
return self._proxy.$extractDacpac(handle, databaseName, packageFilePath, applicationName, applicationVersion, ownerUri, taskExecutionMode);
|
||||
},
|
||||
deployDacpac(packageFilePath: string, databaseName: string, upgradeExisting: boolean, ownerUri: string, taskExecutionMode: sqlops.TaskExecutionMode): Thenable<sqlops.DacFxResult> {
|
||||
return self._proxy.$deployDacpac(handle, packageFilePath, databaseName, upgradeExisting, ownerUri, taskExecutionMode);
|
||||
}
|
||||
});
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Connection Management handlers
|
||||
public $onConnectionComplete(handle: number, connectionInfoSummary: sqlops.ConnectionInfoSummary): void {
|
||||
this._connectionManagementService.onConnectionComplete(handle, connectionInfoSummary);
|
||||
|
||||
@@ -310,6 +310,10 @@ export function createApiFactory(
|
||||
return extHostDataProvider.$registerAgentServiceProvider(provider);
|
||||
};
|
||||
|
||||
let registerDacFxServicesProvider = (provider: sqlops.DacFxServicesProvider): vscode.Disposable => {
|
||||
return extHostDataProvider.$registerDacFxServiceProvider(provider);
|
||||
};
|
||||
|
||||
// namespace: dataprotocol
|
||||
const dataprotocol: typeof sqlops.dataprotocol = {
|
||||
registerBackupProvider,
|
||||
@@ -325,6 +329,7 @@ export function createApiFactory(
|
||||
registerAdminServicesProvider,
|
||||
registerAgentServicesProvider,
|
||||
registerCapabilitiesServiceProvider,
|
||||
registerDacFxServicesProvider,
|
||||
onDidChangeLanguageFlavor(listener: (e: sqlops.DidChangeLanguageFlavorParams) => any, thisArgs?: any, disposables?: extHostTypes.Disposable[]) {
|
||||
return extHostDataProvider.onDidChangeLanguageFlavor(listener, thisArgs, disposables);
|
||||
},
|
||||
|
||||
@@ -409,6 +409,26 @@ export abstract class ExtHostDataProtocolShape {
|
||||
* Get Agent Credentials list
|
||||
*/
|
||||
$getCredentials(handle: number, connectionUri: string): Thenable<sqlops.GetCredentialsResult> { throw ni(); }
|
||||
|
||||
/**
|
||||
* DacFx export bacpac
|
||||
*/
|
||||
$exportBacpac(handle: number, databaseName: string, packageFilePath: string, ownerUri: string, taskExecutionMode: sqlops.TaskExecutionMode): Thenable<sqlops.DacFxResult> { throw ni(); }
|
||||
|
||||
/**
|
||||
* DacFx import bacpac
|
||||
*/
|
||||
$importBacpac(handle: number, packageFilePath: string, databaseName: string, ownerUri: string, taskExecutionMode: sqlops.TaskExecutionMode): Thenable<sqlops.DacFxResult> { throw ni(); }
|
||||
|
||||
/**
|
||||
* DacFx extract dacpac
|
||||
*/
|
||||
$extractDacpac(handle: number, databaseName: string, packageFilePath: string, applicationName: string, applicationVersion: string, ownerUri: string, taskExecutionMode: sqlops.TaskExecutionMode): Thenable<sqlops.DacFxResult> { throw ni(); }
|
||||
|
||||
/**
|
||||
* DacFx deploy dacpac
|
||||
*/
|
||||
$deployDacpac(handle: number, packageFilePath: string, databaseName: string, upgradeExisting: boolean, ownerUri: string, taskExecutionMode: sqlops.TaskExecutionMode): Thenable<sqlops.DacFxResult> { throw ni(); }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -476,6 +496,7 @@ export interface MainThreadDataProtocolShape extends IDisposable {
|
||||
$registerCapabilitiesServiceProvider(providerId: string, handle: number): TPromise<any>;
|
||||
$registerAdminServicesProvider(providerId: string, handle: number): TPromise<any>;
|
||||
$registerAgentServicesProvider(providerId: string, handle: number): TPromise<any>;
|
||||
$registerDacFxServicesProvider(providerId: string, handle: number): TPromise<any>;
|
||||
$unregisterProvider(handle: number): TPromise<any>;
|
||||
$onConnectionComplete(handle: number, connectionInfoSummary: sqlops.ConnectionInfoSummary): void;
|
||||
$onIntelliSenseCacheComplete(handle: number, connectionUri: string): void;
|
||||
|
||||
@@ -137,6 +137,7 @@ import { IScriptingService, ScriptingService } from 'sql/services/scripting/scri
|
||||
import { IAdminService, AdminService } from 'sql/parts/admin/common/adminService';
|
||||
import { IJobManagementService } from 'sql/parts/jobManagement/common/interfaces';
|
||||
import { JobManagementService } from 'sql/parts/jobManagement/common/jobManagementService';
|
||||
import { IDacFxService, DacFxService } from 'sql/services/dacfx/dacFxService';
|
||||
import { IBackupService, IBackupUiService } from 'sql/parts/disasterRecovery/backup/common/backupService';
|
||||
import { BackupService, BackupUiService } from 'sql/parts/disasterRecovery/backup/common/backupServiceImp';
|
||||
import { IRestoreDialogController, IRestoreService } from 'sql/parts/disasterRecovery/restore/common/restoreService';
|
||||
@@ -586,6 +587,8 @@ export class Workbench extends Disposable implements IPartService {
|
||||
// {{SQL CARBON EDIT}}
|
||||
serviceCollection.set(ICommandLineProcessing, this.instantiationService.createInstance(CommandLineService));
|
||||
// {{SQL CARBON EDIT}}
|
||||
serviceCollection.set(IDacFxService, this.instantiationService.createInstance(DacFxService));
|
||||
|
||||
this._register(toDisposable(() => connectionManagementService.shutdown()));
|
||||
this._register(toDisposable(() => accountManagementService.shutdown()));
|
||||
this._register(toDisposable(() => notebookService.shutdown()));
|
||||
|
||||
Reference in New Issue
Block a user