mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
Add Deploy Plan page to DacFx wizard (#3911)
* upgrade plan is piped through and returns the xml plan * Added review deploy plan page * checkbox validation now working and columns formatted * formatting and cleaning up code * refactored populateTable() * addressing comments * addressing comments * updating tooltips * add padding to table cells to align with headers * fix problems when going back and forth between pages and changing config options * bump sqltoolsservice version to 71 * fix localization
This commit is contained in:
@@ -8,6 +8,7 @@ import * as nls from 'vscode-nls';
|
||||
import * as sqlops from 'sqlops';
|
||||
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';
|
||||
@@ -38,10 +39,17 @@ export enum Operation {
|
||||
export enum DeployOperationPath {
|
||||
selectOperation,
|
||||
deployOptions,
|
||||
deployPlan,
|
||||
deployAction,
|
||||
summary
|
||||
}
|
||||
|
||||
export enum DeployNewOperationPath {
|
||||
selectOperation,
|
||||
deployOptions,
|
||||
summary
|
||||
}
|
||||
|
||||
export enum ExtractOperationPath {
|
||||
selectOperation,
|
||||
options,
|
||||
@@ -87,6 +95,7 @@ export class DataTierApplicationWizard {
|
||||
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 deployPlanWizardPage = sqlops.window.modelviewdialog.createWizardPage(localize('dacFx.deployPlanPage', 'Review the deploy plan'));
|
||||
let deployActionWizardPage = sqlops.window.modelviewdialog.createWizardPage(localize('dacFx.deployActionPageName', 'Select Action'));
|
||||
let summaryWizardPage = sqlops.window.modelviewdialog.createWizardPage(localize('dacFx.summaryPageName', 'Summary'));
|
||||
let extractConfigWizardPage = sqlops.window.modelviewdialog.createWizardPage(localize('dacFx.extractConfigPageName', 'Select Extract Dacpac Settings'));
|
||||
@@ -95,6 +104,7 @@ export class DataTierApplicationWizard {
|
||||
|
||||
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));
|
||||
@@ -116,6 +126,12 @@ export class DataTierApplicationWizard {
|
||||
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;
|
||||
@@ -166,7 +182,7 @@ export class DataTierApplicationWizard {
|
||||
}
|
||||
});
|
||||
|
||||
this.wizard.pages = [selectOperationWizardPage, deployConfigWizardPage, deployActionWizardPage, summaryWizardPage];
|
||||
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());
|
||||
@@ -323,10 +339,12 @@ export class DataTierApplicationWizard {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if ((this.selectedOperation === Operation.deploy || this.selectedOperation === Operation.generateDeployScript) && idx === DeployOperationPath.deployAction) {
|
||||
page = this.pages.get('deployAction');
|
||||
} 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;
|
||||
@@ -336,9 +354,24 @@ export class DataTierApplicationWizard {
|
||||
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 sqlops.connection.getUriForConnection(this.model.server.connectionId);
|
||||
|
||||
let result = await service.generateDeployPlan(this.model.filePath, this.model.database, ownerUri, sqlops.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<sqlops.DacFxServicesProvider> {
|
||||
let service = sqlops.dataprotocol.getProvider<sqlops.DacFxServicesProvider>(providerName, sqlops.DataProviderType.DacFxServicesProvider);
|
||||
return service;
|
||||
|
||||
@@ -131,7 +131,15 @@ export class DacFxSummaryPage extends BasePage {
|
||||
|
||||
this.table.updateProperties({
|
||||
data: data,
|
||||
columns: ['Setting', 'Value'],
|
||||
columns: [
|
||||
{
|
||||
value: localize('dacfx.settingColumn', 'Setting'),
|
||||
cssClass: 'align-with-header'
|
||||
},
|
||||
{
|
||||
value: localize('dacfx.valueColumn', 'Value'),
|
||||
cssClass: 'align-with-header'
|
||||
}],
|
||||
width: 700,
|
||||
height: 200
|
||||
});
|
||||
|
||||
@@ -54,6 +54,8 @@ export class DeployActionPage extends DacFxConfigPage {
|
||||
}
|
||||
|
||||
async onPageEnter(): Promise<boolean> {
|
||||
// generate script file path in case the database changed since last time the page was entered
|
||||
this.setDefaultScriptFilePath();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -120,11 +122,7 @@ export class DeployActionPage extends DacFxConfigPage {
|
||||
this.createFileBrowserParts();
|
||||
|
||||
//default filepath
|
||||
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;
|
||||
|
||||
this.setDefaultScriptFilePath();
|
||||
this.fileButton.onDidClick(async (click) => {
|
||||
let fileUri = await vscode.window.showSaveDialog(
|
||||
{
|
||||
@@ -151,15 +149,15 @@ export class DeployActionPage extends DacFxConfigPage {
|
||||
return {
|
||||
title: '',
|
||||
components: [
|
||||
{
|
||||
title: localize('dacfx.generatedScriptLocation','Deployment Script Location'),
|
||||
component: this.fileTextBox,
|
||||
layout: {
|
||||
horizontal: true,
|
||||
componentWidth: 400
|
||||
},
|
||||
actions: [this.fileButton]
|
||||
},],
|
||||
{
|
||||
title: localize('dacfx.generatedScriptLocation', 'Deployment Script Location'),
|
||||
component: this.fileTextBox,
|
||||
layout: {
|
||||
horizontal: true,
|
||||
componentWidth: 400
|
||||
},
|
||||
actions: [this.fileButton]
|
||||
},],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -168,6 +166,13 @@ export class DeployActionPage extends DacFxConfigPage {
|
||||
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;
|
||||
|
||||
@@ -10,7 +10,7 @@ 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 { DataTierApplicationWizard, DeployOperationPath, Operation } from '../dataTierApplicationWizard';
|
||||
import { DacFxConfigPage } from '../api/dacFxConfigPage';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
@@ -122,6 +122,12 @@ export class DeployConfigPage extends DacFxConfigPage {
|
||||
this.formBuilder.removeFormItem(this.databaseComponent);
|
||||
this.formBuilder.addFormItem(this.databaseDropdownComponent, { horizontal: true, componentWidth: 400 });
|
||||
this.model.database = (<sqlops.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(() => {
|
||||
@@ -129,6 +135,11 @@ export class DeployConfigPage extends DacFxConfigPage {
|
||||
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
|
||||
|
||||
296
extensions/import/src/wizard/pages/deployPlanPage.ts
Normal file
296
extensions/import/src/wizard/pages/deployPlanPage.ts
Normal file
@@ -0,0 +1,296 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 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: sqlops.window.modelviewdialog.WizardPage;
|
||||
protected readonly instance: DataTierApplicationWizard;
|
||||
protected readonly model: DacFxDataModel;
|
||||
protected readonly view: sqlops.ModelView;
|
||||
private formBuilder: sqlops.FormBuilder;
|
||||
private form: sqlops.FormContainer;
|
||||
private table: sqlops.TableComponent;
|
||||
private loader: sqlops.LoadingComponent;
|
||||
private dataLossCheckbox: sqlops.CheckBoxComponent;
|
||||
private dataLossText: sqlops.TextComponent;
|
||||
private dataLossComponentGroup: sqlops.FormComponentGroup;
|
||||
private noDataLossTextComponent: sqlops.FormComponent;
|
||||
|
||||
public constructor(instance: DataTierApplicationWizard, wizardPage: sqlops.window.modelviewdialog.WizardPage, model: DacFxDataModel, view: sqlops.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<sqlops.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<sqlops.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<sqlops.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): sqlops.TableColumn[] {
|
||||
let columns: sqlops.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;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@
|
||||
import * as sqlops from 'sqlops';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { DacFxDataModel } from '../api/models';
|
||||
import { DataTierApplicationWizard, Operation } from '../dataTierApplicationWizard';
|
||||
import { DataTierApplicationWizard, Operation, DeployOperationPath, ExtractOperationPath, ImportOperationPath, ExportOperationPath } from '../dataTierApplicationWizard';
|
||||
import { BasePage } from '../api/basePage';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
@@ -73,10 +73,12 @@ export class SelectOperationPage extends BasePage {
|
||||
|
||||
//add deploy pages
|
||||
let configPage = this.instance.pages.get('deployConfig');
|
||||
this.instance.wizard.addPage(configPage.wizardPage, 1);
|
||||
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, 2);
|
||||
this.addSummaryPage(3);
|
||||
this.instance.wizard.addPage(actionPage.wizardPage, DeployOperationPath.deployAction);
|
||||
this.addSummaryPage(DeployOperationPath.summary);
|
||||
|
||||
// change button text and operation
|
||||
this.instance.setDoneButton(Operation.deploy);
|
||||
@@ -100,8 +102,8 @@ export class SelectOperationPage extends BasePage {
|
||||
|
||||
// add the extract page
|
||||
let page = this.instance.pages.get('extractConfig');
|
||||
this.instance.wizard.addPage(page.wizardPage, 1);
|
||||
this.addSummaryPage(2);
|
||||
this.instance.wizard.addPage(page.wizardPage, ExtractOperationPath.options);
|
||||
this.addSummaryPage(ExtractOperationPath.summary);
|
||||
|
||||
// change button text and operation
|
||||
this.instance.setDoneButton(Operation.extract);
|
||||
@@ -125,8 +127,8 @@ export class SelectOperationPage extends BasePage {
|
||||
|
||||
// add the import page
|
||||
let page = this.instance.pages.get('importConfig');
|
||||
this.instance.wizard.addPage(page.wizardPage, 1);
|
||||
this.addSummaryPage(2);
|
||||
this.instance.wizard.addPage(page.wizardPage, ImportOperationPath.options);
|
||||
this.addSummaryPage(ImportOperationPath.summary);
|
||||
|
||||
// change button text and operation
|
||||
this.instance.setDoneButton(Operation.import);
|
||||
@@ -150,8 +152,8 @@ export class SelectOperationPage extends BasePage {
|
||||
|
||||
// add the export pages
|
||||
let page = this.instance.pages.get('exportConfig');
|
||||
this.instance.wizard.addPage(page.wizardPage, 1);
|
||||
this.addSummaryPage(2);
|
||||
this.instance.wizard.addPage(page.wizardPage, ExportOperationPath.options);
|
||||
this.addSummaryPage(ExportOperationPath.summary);
|
||||
|
||||
// change button text and operation
|
||||
this.instance.setDoneButton(Operation.export);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"downloadUrl": "https://github.com/Microsoft/sqltoolsservice/releases/download/v{#version#}/microsoft.sqltools.servicelayer-{#fileName#}",
|
||||
"version": "1.5.0-alpha.70",
|
||||
"version": "1.5.0-alpha.71",
|
||||
"downloadFileNames": {
|
||||
"Windows_86": "win-x86-netcoreapp2.2.zip",
|
||||
"Windows_64": "win-x64-netcoreapp2.2.zip",
|
||||
|
||||
@@ -339,6 +339,13 @@ export interface GenerateDeployScriptParams {
|
||||
taskExecutionMode: TaskExecutionMode;
|
||||
}
|
||||
|
||||
export interface GenerateDeployPlanParams {
|
||||
packageFilePath: string;
|
||||
databaseName: string;
|
||||
ownerUri: string;
|
||||
taskExecutionMode: TaskExecutionMode;
|
||||
}
|
||||
|
||||
export namespace ExportRequest {
|
||||
export const type = new RequestType<ExportParams, sqlops.DacFxResult, void, void>('dacfx/export');
|
||||
}
|
||||
@@ -359,4 +366,7 @@ export namespace GenerateDeployScriptRequest {
|
||||
export const type = new RequestType<GenerateDeployScriptParams, sqlops.DacFxResult, void, void>('dacfx/generateDeploymentScript');
|
||||
}
|
||||
|
||||
export namespace GenerateDeployPlanRequest {
|
||||
export const type = new RequestType<GenerateDeployPlanParams, sqlops.GenerateDeployPlanResult, void, void>('dacfx/generateDeployPlan');
|
||||
}
|
||||
// ------------------------------- < DacFx > ------------------------------------
|
||||
@@ -113,7 +113,20 @@ export class DacFxServicesFeature extends SqlOpsFeature<undefined> {
|
||||
return r;
|
||||
},
|
||||
e => {
|
||||
client.logFailedRequest(contracts.DeployRequest.type, e);
|
||||
client.logFailedRequest(contracts.GenerateDeployScriptRequest.type, e);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
let generateDeployPlan = (packageFilePath: string, targetDatabaseName: string, ownerUri: string, taskExecutionMode: sqlops.TaskExecutionMode): Thenable<sqlops.GenerateDeployPlanResult> => {
|
||||
let params: contracts.GenerateDeployPlanParams = { packageFilePath: packageFilePath, databaseName: targetDatabaseName, ownerUri: ownerUri, taskExecutionMode: taskExecutionMode };
|
||||
return client.sendRequest(contracts.GenerateDeployPlanRequest.type, params).then(
|
||||
r => {
|
||||
return r;
|
||||
},
|
||||
e => {
|
||||
client.logFailedRequest(contracts.GenerateDeployPlanRequest.type, e);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
);
|
||||
@@ -125,7 +138,8 @@ export class DacFxServicesFeature extends SqlOpsFeature<undefined> {
|
||||
importBacpac,
|
||||
extractDacpac,
|
||||
deployDacpac,
|
||||
generateDeployScript
|
||||
generateDeployScript,
|
||||
generateDeployPlan
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ export interface IDacFxService {
|
||||
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;
|
||||
generateDeployScript(packageFilePath: string, targetDatabaseName: string, scriptFilePath: string, ownerUri: string, taskExecutionMode: sqlops.TaskExecutionMode): void;
|
||||
generateDeployPlan(packageFilePath: string, targetDatabaseName: string, ownerUri: string, taskExecutionMode: sqlops.TaskExecutionMode): void;
|
||||
}
|
||||
|
||||
export class DacFxService implements IDacFxService {
|
||||
@@ -68,6 +69,12 @@ export class DacFxService implements IDacFxService {
|
||||
});
|
||||
}
|
||||
|
||||
generateDeployPlan(packageFilePath: string, databaseName: string, ownerUri: string, taskExecutionMode: sqlops.TaskExecutionMode): Thenable<sqlops.GenerateDeployPlanResult> {
|
||||
return this._runAction(ownerUri, (runner) => {
|
||||
return runner.generateDeployPlan(packageFilePath, databaseName, ownerUri, taskExecutionMode);
|
||||
});
|
||||
}
|
||||
|
||||
private _runAction<T>(uri: string, action: (handler: sqlops.DacFxServicesProvider) => Thenable<T>): Thenable<T> {
|
||||
let providerId: string = this._connectionService.getProviderIdFromUri(uri);
|
||||
|
||||
|
||||
12
src/sql/sqlops.d.ts
vendored
12
src/sql/sqlops.d.ts
vendored
@@ -1634,6 +1634,10 @@ declare module 'sqlops' {
|
||||
operationId: string;
|
||||
}
|
||||
|
||||
export interface GenerateDeployPlanResult extends DacFxResult {
|
||||
report: string;
|
||||
}
|
||||
|
||||
export interface ExportParams {
|
||||
databaseName: string;
|
||||
packageFilePath: string;
|
||||
@@ -1673,12 +1677,20 @@ declare module 'sqlops' {
|
||||
taskExecutionMode: TaskExecutionMode;
|
||||
}
|
||||
|
||||
export interface GenerateDeployPlan {
|
||||
packageFilePath: string;
|
||||
databaseName: string;
|
||||
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>;
|
||||
generateDeployScript(packageFilePath: string, databaseName: string, scriptFilePath: string, ownerUri: string, taskExecutionMode: TaskExecutionMode): Thenable<DacFxResult>;
|
||||
generateDeployPlan(packageFilePath: string, databaseName: string, ownerUri: string, taskExecutionMode: TaskExecutionMode): Thenable<GenerateDeployPlanResult>;
|
||||
}
|
||||
|
||||
// Security service interfaces ------------------------------------------------------------------------
|
||||
|
||||
@@ -445,6 +445,9 @@ export class MainThreadDataProtocol implements MainThreadDataProtocolShape {
|
||||
},
|
||||
generateDeployScript(packageFilePath: string, databaseName: string, scriptFilePath: string, ownerUri: string, taskExecutionMode: sqlops.TaskExecutionMode): Thenable<sqlops.DacFxResult> {
|
||||
return self._proxy.$generateDeployScript(handle, packageFilePath, databaseName, scriptFilePath, ownerUri, taskExecutionMode);
|
||||
},
|
||||
generateDeployPlan(packageFilePath: string, databaseName: string, ownerUri: string, taskExecutionMode: sqlops.TaskExecutionMode): Thenable<sqlops.GenerateDeployPlanResult> {
|
||||
return self._proxy.$generateDeployPlan(handle, packageFilePath, databaseName, ownerUri, taskExecutionMode);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -450,6 +450,11 @@ export abstract class ExtHostDataProtocolShape {
|
||||
*/
|
||||
$generateDeployScript(handle: number, packageFilePath: string, databaseName: string, scriptFilePath: string, ownerUri: string, taskExecutionMode: sqlops.TaskExecutionMode): Thenable<sqlops.DacFxResult> { throw ni(); }
|
||||
|
||||
/**
|
||||
* DacFx generate deploy plan
|
||||
*/
|
||||
$generateDeployPlan(handle: number, packageFilePath: string, databaseName: string, ownerUri: string, taskExecutionMode: sqlops.TaskExecutionMode): Thenable<sqlops.GenerateDeployPlanResult> { throw ni(); }
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user