mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-13 03:28:33 -05:00
SKU recommendation page work (#12050)
This commit is contained in:
11
extensions/sql-migration/media/ads.svg
Normal file
11
extensions/sql-migration/media/ads.svg
Normal file
@@ -0,0 +1,11 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="456.51" height="500">
|
||||
<path fill="#6bbcda" d="M396 84.97h-1.67A92.9 92.9 0 0 0 223.08 43.5a87.38 87.38 0 0 0-17.27-1.87 83.1 83.1 0 0 0 0 166.22H396A61.43 61.43 0 0 0 396 85z"/>
|
||||
<path fill="#8acae1" d="M248.52 67.2a92.8 92.8 0 0 1 19.13 2.06 91.78 91.78 0 0 1 93.99-47.41A92.92 92.92 0 0 0 223.12 43.5a87.38 87.38 0 0 0-17.27-1.87 83.1 83.1 0 0 0-35.49 158.15 88.46 88.46 0 0 1-10.96-42.94 89.43 89.43 0 0 1 89.12-89.63z"/>
|
||||
<path fill="#50a7e1" d="M0 180v244.97c0 41.26 64.83 74.68 144.87 74.88V236.02z"/>
|
||||
<path fill="#c0d557" d="M102.9 124.73a97.96 97.96 0 0 1 .56-10.45C43.65 123.24 0 151.07 0 184.1c0 40.34 64.83 73.01 144.87 73.01V208.2a104.02 104.02 0 0 1-41.97-83.46z"/>
|
||||
<path fill="#c0d557" d="M260.78 228.18h-54.97A101.67 101.67 0 0 1 144.86 208v48.96h.2a214.78 214.78 0 0 0 115.72-28.8z"/>
|
||||
<path fill="#1e80c6" d="M252.04 371.76l.75-1.69 6.93-16.62.74-1.69 7.17-17.22 17.07 7.29 1.68.75 3.71 1.48v-115.5h-29.3a216.96 216.96 0 0 1-115.72 28.93h-.2v117.44h105.88zM259.48 458.76l-6.93-16.8-.74-1.67-7.05-17.37 17.27-7.1 1.66-.74 7.07-3c-.2-2.05-.2-3.92-.2-5.97s0-4.29.2-6.53l-7.07-3-1.3-.55H144.87v103.25h.2a208.94 208.94 0 0 0 118.12-31.36l-3.16-7.85z"/>
|
||||
<path fill="#fff" d="M262.08 396.21L245 388.94l5.94-14.38H132.25l-16.71 40.89L65 275.41 27.99 376.24H0v17.74h42.54l23.4-65.1 51.08 143.2 26.93-75.6h118.5z"/>
|
||||
<path fill="#0e4da7" d="M434.07 424.58a72.75 72.75 0 0 0 2.23-18.1 79.83 79.83 0 0 0-2.04-17.75l20.43-8.4 1.68-.74-.75-1.7-6.93-16.8-.75-1.67-1.67.75-20.43 8.4a74.29 74.29 0 0 0-25.07-25.4l8.54-20.54.75-1.67-1.68-.75-16.52-6.93-1.68-.75-.66 1.72-8.55 20.54a72.03 72.03 0 0 0-18-2.24 78.95 78.95 0 0 0-17.68 2L336.98 314l-.79-1.62-1.68.75-16.71 6.92-1.67.75.74 1.68 8.3 20.54a74.27 74.27 0 0 0-25.26 25.2l-9.7-4.16-10.77-4.49-1.68-.74-.73 1.69-6.93 16.62-.74 1.69 1.67.74 20.43 8.6a65.72 65.72 0 0 0-1.3 5.96c-.37 2.05-.55 4.5-.75 6.72v.18a47.18 47.18 0 0 0-.18 5.04 24.52 24.52 0 0 0 .18 3.73 5.04 5.04 0 0 0 .2 1.69c0 .55.18 1.12.18 1.87a43.3 43.3 0 0 0 1.1 7.1 16.14 16.14 0 0 0 .75 3.16l-1.1.38-19.4 8.02-1.67.74.75 1.7 6.92 16.8.75 1.67 1.66-.75 20.43-8.39a74.33 74.33 0 0 0 25.09 25.39l-8.55 20.54-.75 1.68 1.68.75 16.52 6.92 1.68.75.75-1.69 8.53-20.53a72.82 72.82 0 0 0 18 2.23 78.95 78.95 0 0 0 17.65-2.05l8.32 20.54.74 1.68 1.68-.75 16.7-6.92 1.68-.74-.73-1.69-8.31-20.54a74.27 74.27 0 0 0 25.26-25.2l20.43 8.6 1.68.74.73-1.69 6.93-16.62.75-1.7-1.68-.74zm-71.33 33.24a51.56 51.56 0 0 1-40.48-83.1l.19-.19a51.35 51.35 0 1 1 40.3 83.3z"/>
|
||||
<path fill="#fff" d="M322.63 374.56h-.18l-.2.18s0 .2-.17.2z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
@@ -34,26 +34,27 @@ export async function getSubscriptions(account: azdata.Account): Promise<Subscri
|
||||
}
|
||||
|
||||
export type AzureProduct = azureResource.AzureGraphResource;
|
||||
export type SqlManagedInstance = azureResource.AzureGraphResource;
|
||||
|
||||
export type SqlManagedInstance = AzureProduct;
|
||||
export async function getAvailableManagedInstanceProducts(account: azdata.Account, subscription: Subscription): Promise<SqlManagedInstance[]> {
|
||||
const api = await getAzureCoreAPI();
|
||||
|
||||
const result = await api.runGraphQuery<azureResource.AzureGraphResource>(account, subscription, false, 'where type == "microsoft.sql/managedinstances"');
|
||||
const result = await api.runGraphQuery<SqlManagedInstance>(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<SqlServer[]> {
|
||||
const api = await getAzureCoreAPI();
|
||||
|
||||
const result = await api.runGraphQuery<azureResource.AzureGraphResource>(account, subscription, false, 'where type == "microsoft.sql/servers"');
|
||||
const result = await api.runGraphQuery<SqlServer>(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<SqlVMServer[]> {
|
||||
const api = await getAzureCoreAPI();
|
||||
|
||||
const result = await api.runGraphQuery<azureResource.AzureGraphResource>(account, subscription, false, 'where type == "microsoft.compute/virtualmachines" and properties.storageProfile.imageReference.publisher == "microsoftsqlserver"');
|
||||
const result = await api.runGraphQuery<SqlVMServer>(account, subscription, false, 'where type == "microsoft.compute/virtualmachines" and properties.storageProfile.imageReference.publisher == "microsoftsqlserver"');
|
||||
return result.resources;
|
||||
}
|
||||
|
||||
@@ -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'),
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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<azdata.TextComponent> | undefined;
|
||||
private detailsComponent: azdata.FormComponent<azdata.TextComponent> | undefined;
|
||||
private chooseTargetComponent: azdata.FormComponent<azdata.DivContainer> | 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<azdata.TextComponent> {
|
||||
private createStatusComponent(view: azdata.ModelView): azdata.FormComponent<azdata.TextComponent> {
|
||||
const component = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
|
||||
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;
|
||||
|
||||
@@ -17,7 +17,7 @@ export class WizardController {
|
||||
}
|
||||
|
||||
public async openWizard(profile: azdata.connection.Connection): Promise<void> {
|
||||
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<void> {
|
||||
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);
|
||||
|
||||
Reference in New Issue
Block a user