diff --git a/extensions/sql-migration/media/ads.svg b/extensions/sql-migration/media/ads.svg
new file mode 100644
index 0000000000..a9f6620868
--- /dev/null
+++ b/extensions/sql-migration/media/ads.svg
@@ -0,0 +1,11 @@
+
\ No newline at end of file
diff --git a/extensions/sql-migration/src/api/azure.ts b/extensions/sql-migration/src/api/azure.ts
index b912fff4b5..167f7e5ecd 100644
--- a/extensions/sql-migration/src/api/azure.ts
+++ b/extensions/sql-migration/src/api/azure.ts
@@ -34,26 +34,27 @@ export async function getSubscriptions(account: azdata.Account): Promise {
const api = await getAzureCoreAPI();
- const result = await api.runGraphQuery(account, subscription, false, 'where type == "microsoft.sql/managedinstances"');
+ const result = await api.runGraphQuery(account, subscription, false, 'where type == "microsoft.sql/managedinstances"');
return result.resources;
}
-export type SqlServer = azureResource.AzureGraphResource;
+export type SqlServer = AzureProduct;
export async function getAvailableSqlServers(account: azdata.Account, subscription: Subscription): Promise {
const api = await getAzureCoreAPI();
- const result = await api.runGraphQuery(account, subscription, false, 'where type == "microsoft.sql/servers"');
+ const result = await api.runGraphQuery(account, subscription, false, 'where type == "microsoft.sql/servers"');
return result.resources;
}
-export type SqlVMServer = azureResource.AzureGraphResource;
+export type SqlVMServer = AzureProduct;
export async function getAvailableSqlVMs(account: azdata.Account, subscription: Subscription): Promise {
const api = await getAzureCoreAPI();
- const result = await api.runGraphQuery(account, subscription, false, 'where type == "microsoft.compute/virtualmachines" and properties.storageProfile.imageReference.publisher == "microsoftsqlserver"');
+ const result = await api.runGraphQuery(account, subscription, false, 'where type == "microsoft.compute/virtualmachines" and properties.storageProfile.imageReference.publisher == "microsoftsqlserver"');
return result.resources;
}
diff --git a/extensions/sql-migration/src/models/product.ts b/extensions/sql-migration/src/models/product.ts
index 6da2d81fde..6acd610293 100644
--- a/extensions/sql-migration/src/models/product.ts
+++ b/extensions/sql-migration/src/models/product.ts
@@ -5,7 +5,7 @@
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
-export type MigrationProductType = 'AzureSQLMI' | 'AzureSQLVM';
+export type MigrationProductType = 'AzureSQLMI' | 'AzureSQLVM' | 'AzureSQL';
export interface MigrationProduct {
readonly type: MigrationProductType;
}
@@ -22,12 +22,12 @@ export interface Checks {
export interface Product extends MigrationProduct {
readonly name: string;
- readonly icon: string;
readonly learnMoreLink?: string;
+ readonly icon?: string;
}
export class Product implements Product {
- constructor(public readonly type: MigrationProductType, public readonly name: string, public readonly icon: string, public readonly learnMoreLink?: string) {
+ constructor(public readonly type: MigrationProductType, public readonly name: string, public readonly icon?: string, public readonly learnMoreLink?: string) {
}
@@ -45,15 +45,17 @@ export interface SKURecommendation {
}
-const ProductLookupTable: { [key in MigrationProductType]: Product } = {
+export const ProductLookupTable: { [key in MigrationProductType]: Product } = {
'AzureSQLMI': {
type: 'AzureSQLMI',
name: localize('sql.migration.products.azuresqlmi.name', 'Azure Managed Instance (Microsoft managed)'),
- icon: 'TODO',
},
'AzureSQLVM': {
type: 'AzureSQLVM',
name: localize('sql.migration.products.azuresqlvm.name', 'Azure SQL Virtual Machine (Customer managed)'),
- icon: 'TODO',
+ },
+ 'AzureSQL': {
+ type: 'AzureSQL',
+ name: localize('sql.migration.products.azuresql.name', 'Azure SQL'),
}
};
diff --git a/extensions/sql-migration/src/models/stateMachine.ts b/extensions/sql-migration/src/models/stateMachine.ts
index c9c463cea4..360f19bbb7 100644
--- a/extensions/sql-migration/src/models/stateMachine.ts
+++ b/extensions/sql-migration/src/models/stateMachine.ts
@@ -43,7 +43,10 @@ export class MigrationStateModel implements Model, vscode.Disposable {
private _gatheringInformationError: string | undefined;
private _skuRecommendations: SKURecommendations | undefined;
- constructor(private readonly _sourceConnection: azdata.connection.Connection) {
+ constructor(
+ private readonly _extensionContext: vscode.ExtensionContext,
+ private readonly _sourceConnection: azdata.connection.Connection
+ ) {
this._currentState = State.INIT;
}
@@ -86,4 +89,8 @@ export class MigrationStateModel implements Model, vscode.Disposable {
dispose() {
this._stateChangeEventEmitter.dispose();
}
+
+ public getExtensionPath(): string {
+ return this._extensionContext.extensionPath;
+ }
}
diff --git a/extensions/sql-migration/src/models/strings.ts b/extensions/sql-migration/src/models/strings.ts
index 09c4cecd02..5a43acac2d 100644
--- a/extensions/sql-migration/src/models/strings.ts
+++ b/extensions/sql-migration/src/models/strings.ts
@@ -20,11 +20,13 @@ export const COLLECTING_SOURCE_CONFIGURATIONS_ERROR = (error: string = ''): stri
export const SKU_RECOMMENDATION_PAGE_TITLE = localize('sql.migration.wizard.sku.title', "Azure SQL Target Selection");
export const SKU_RECOMMENDATION_ALL_SUCCESSFUL = (databaseCount: number): string => {
- return localize('sql.migration.sku.all', "Based on the results of our source configuration scans, all {0} of your databases can be migrated to Azure SQL.", databaseCount);
+ return localize('sql.migration.wizard.sku.all', "Based on the results of our source configuration scans, all {0} of your databases can be migrated to Azure SQL.", databaseCount);
};
export const SKU_RECOMMENDATION_SOME_SUCCESSFUL = (migratableCount: number, databaseCount: number): string => {
- return localize('sql.migration.sku.some', "Based on the results of our source configuration scans, {0} out of {1} of your databases can be migrated to Azure SQL.", migratableCount, databaseCount);
+ return localize('sql.migration.wizard.sku.some', "Based on the results of our source configuration scans, {0} out of {1} of your databases can be migrated to Azure SQL.", migratableCount, databaseCount);
};
+export const SKU_RECOMMENDATION_CHOOSE_A_TARGET = localize('sql.migration.wizard.sku.choose_a_target', "Choose a target");
+
export const SKU_RECOMMENDATION_NONE_SUCCESSFUL = localize('sql.migration.sku.none', "Based on the results of our source configuration scans, none of your databases can be migrated to Azure SQL.");
export const SUBSCRIPTION_SELECTION_PAGE_TITLE = localize('sql.migration.wizard.subscription.title', "Azure Subscription Selection");
diff --git a/extensions/sql-migration/src/wizard/skuRecommendationPage.ts b/extensions/sql-migration/src/wizard/skuRecommendationPage.ts
index 2941d8039b..c3c1365498 100644
--- a/extensions/sql-migration/src/wizard/skuRecommendationPage.ts
+++ b/extensions/sql-migration/src/wizard/skuRecommendationPage.ts
@@ -4,10 +4,11 @@
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
+import * as path from 'path';
import { MigrationWizardPage } from '../models/migrationWizardPage';
import { MigrationStateModel, StateChangeEvent } from '../models/stateMachine';
-import { Product } from '../models/product';
-import { CONGRATULATIONS, SKU_RECOMMENDATION_PAGE_TITLE, SKU_RECOMMENDATION_ALL_SUCCESSFUL } from '../models/strings';
+import { Product, ProductLookupTable } from '../models/product';
+import { SKU_RECOMMENDATION_PAGE_TITLE, SKU_RECOMMENDATION_CHOOSE_A_TARGET } from '../models/strings';
import { Disposable } from 'vscode';
export class SKURecommendationPage extends MigrationWizardPage {
@@ -22,12 +23,28 @@ export class SKURecommendationPage extends MigrationWizardPage {
private igComponent: azdata.FormComponent | undefined;
private detailsComponent: azdata.FormComponent | undefined;
+ private chooseTargetComponent: azdata.FormComponent | undefined;
+ private view: azdata.ModelView | undefined;
+
private async initialState(view: azdata.ModelView) {
- this.igComponent = this.createIGComponent(view);
- this.detailsComponent = this.createDetailsComponent(view);
+ this.igComponent = this.createStatusComponent(view); // The first component giving basic information
+ this.detailsComponent = this.createDetailsComponent(view); // The details of what can be moved
+ this.chooseTargetComponent = this.createChooseTargetComponent(view);
+ this.view = view;
+
+
+ const form = view.modelBuilder.formContainer().withFormItems(
+ [
+ this.igComponent,
+ this.detailsComponent,
+ this.chooseTargetComponent
+ ]
+ );
+
+ await view.initializeModel(form.component());
}
- private createIGComponent(view: azdata.ModelView): azdata.FormComponent {
+ private createStatusComponent(view: azdata.ModelView): azdata.FormComponent {
const component = view.modelBuilder.text().withProperties({
value: '',
});
@@ -49,37 +66,40 @@ export class SKURecommendationPage extends MigrationWizardPage {
};
}
- private constructDetails(): void {
- const recommendations = this.migrationStateModel.skuRecommendations?.recommendations;
-
- if (!recommendations) {
- return;
- }
-
- const products = recommendations.map(recommendation => {
- return {
- checks: recommendation.checks,
- product: Product.FromMigrationProduct(recommendation.product)
- };
- });
-
- const migratableDatabases: number = products?.length ?? 10; // force it to be used
-
- const allDatabases = 10;
-
- if (allDatabases === migratableDatabases) {
- this.allMigratable(migratableDatabases);
- }
-
- // TODO handle other situations
+ private createChooseTargetComponent(view: azdata.ModelView) {
+ const component = view.modelBuilder.divContainer();
+ return {
+ title: SKU_RECOMMENDATION_CHOOSE_A_TARGET,
+ component: component.component()
+ };
}
- private allMigratable(databaseCount: number): void {
- this.igComponent!.title = CONGRATULATIONS;
- this.igComponent!.component.value = SKU_RECOMMENDATION_ALL_SUCCESSFUL(databaseCount);
- this.detailsComponent!.component.value = ''; // force it to be used
- // fill in some of that information
+ private constructDetails(): void {
+ this.chooseTargetComponent?.component.clearItems();
+
+ this.igComponent!.component.value = 'Test';
+ this.detailsComponent!.component.value = 'Test';
+ this.constructTargets();
+ }
+
+ private constructTargets(): void {
+ const products: Product[] = Object.values(ProductLookupTable);
+
+ const rbg = this.view!.modelBuilder.radioCardGroup();
+ rbg.component().cards = [];
+
+ products.forEach((product) => {
+ const imagePath = path.resolve(this.migrationStateModel.getExtensionPath(), 'media', product.icon ?? 'ads.svg');
+
+ rbg.component().cards.push({
+ id: product.name,
+ icon: imagePath,
+ label: 'Some Label'
+ });
+ });
+
+ this.chooseTargetComponent?.component.addItem(rbg.component());
}
private eventListener: Disposable | undefined;
diff --git a/extensions/sql-migration/src/wizard/wizardController.ts b/extensions/sql-migration/src/wizard/wizardController.ts
index 49be4a40b4..94e5f5fda1 100644
--- a/extensions/sql-migration/src/wizard/wizardController.ts
+++ b/extensions/sql-migration/src/wizard/wizardController.ts
@@ -17,7 +17,7 @@ export class WizardController {
}
public async openWizard(profile: azdata.connection.Connection): Promise {
- const stateModel = new MigrationStateModel(profile);
+ const stateModel = new MigrationStateModel(this.extensionContext, profile);
this.extensionContext.subscriptions.push(stateModel);
this.createWizard(stateModel);
@@ -26,6 +26,8 @@ export class WizardController {
private async createWizard(stateModel: MigrationStateModel): Promise {
const wizard = azdata.window.createWizard(WIZARD_TITLE, 'wide');
wizard.generateScriptButton.enabled = false;
+ wizard.generateScriptButton.hidden = true;
+
const sourceConfigurationPage = new SourceConfigurationPage(wizard, stateModel);
const skuRecommendationPage = new SKURecommendationPage(wizard, stateModel);
const subscriptionSelectionPage = new SubscriptionSelectionPage(wizard, stateModel);
@@ -47,13 +49,13 @@ export class WizardController {
});
wizard.registerNavigationValidator(async validator => {
- const lastPage = validator.lastPage;
+ // const lastPage = validator.lastPage;
- const canLeave = await pages[lastPage]?.canLeave() ?? true;
- const canEnter = await pages[lastPage]?.canEnter() ?? true;
+ // const canLeave = await pages[lastPage]?.canLeave() ?? true;
+ // const canEnter = await pages[lastPage]?.canEnter() ?? true;
- return canEnter && canLeave;
- // return true;
+ // return canEnter && canLeave;
+ return true;
});
await Promise.all(wizardSetupPromises);