Adding option to create a resource group in sql migration dms page. (#15856)

* Pushing all changes

* Fixing more stuff

* Added new to the newly created resource group

* removing error message when user starts typing

* removing unused import

* Localizing some string
adding aria lables and loading text

* Adding focus to resource group input box

* switching to name instead of display name as it contains (new) param

* changing focus to resource group dropdown after creation of a new resource group

* fixing typo
This commit is contained in:
Aasim Khan
2021-07-01 13:48:10 -07:00
committed by GitHub
parent b1a9d6baa1
commit 225788a121
9 changed files with 270 additions and 5 deletions

View File

@@ -53,6 +53,12 @@ export async function getResourceGroups(account: azdata.Account, subscription: S
return result.resourceGroups;
}
export async function createResourceGroup(account: azdata.Account, subscription: Subscription, resourceGroupName: string, location: string): Promise<azureResource.AzureResourceResourceGroup> {
const api = await getAzureCoreAPI();
const result = await api.createResourceGroup(account, subscription, resourceGroupName, location, false);
return result.resourceGroup;
}
export type SqlManagedInstance = azureResource.AzureSqlManagedInstance;
export async function getAvailableManagedInstanceProducts(account: azdata.Account, subscription: Subscription): Promise<SqlManagedInstance[]> {
const api = await getAzureCoreAPI();

View File

@@ -210,9 +210,18 @@ export const MANAGED_INSTANCE = localize('sql.migration.managed.instance', "Azur
export const NO_MANAGED_INSTANCE_FOUND = localize('sql.migration.no.managedInstance.found', "No managed instance found");
export const NO_VIRTUAL_MACHINE_FOUND = localize('sql.migration.no.virtualMachine.found', "No virtual machine found");
export const TARGET_SELECTION_PAGE_TITLE = localize('sql.migration.target.page.title', "Choose the target Azure SQL");
export const RESOURCE_GROUP_DESCRIPTION = localize('sql.migration.resource.group.description', "A resource group is a container that holds related resources for an Azure solution");
export const OK = localize('sql.migration.ok', "OK");
export function NEW_RESOURCE_GROUP(resourceGroupName: string): string {
return localize('sql.migration.new.resource.group', "(new) {0}", resourceGroupName);
}
export const TEST_CONNECTION = localize('sql.migration.test.connection', "Test connection");
export const DATA_MIGRATION_SERVICE_CREATED_SUCCESSFULLY = localize('sql.migration.database.migration.service.created.successfully', "Database migration service has been created successfully");
export const DMS_PROVISIONING_FAILED = localize('sql.migration.dms.provision.failed', "Database migration service has failed to provision. Please try again after some time.");
export const APPLY = localize('sql.migration.apply', "Apply");
export const CREATING_RESOURCE_GROUP = localize('sql.migration.creating.rg.loading', "Creating resource group");
export const RESOURCE_GROUP_CREATED = localize('sql.migration.rg.created', "Resource group created");
export const NAME_OF_NEW_RESOURCE_GROUP = localize('sql.migration.name.of.new.rg', "Name of new Resource group");
// common strings
export const LEARN_MORE = localize('sql.migration.learn.more', "Learn more");
export const SUBSCRIPTION = localize('sql.migration.subscription', "Subscription");
@@ -237,6 +246,7 @@ export const CLOSE = localize('sql.migration.close', "Close");
export const DATA_UPLOADED = localize('sql.migraiton.data.uploaded.size', "Data Uploaded/Size");
export const COPY_THROUGHPUT = localize('sql.migration.copy.throughput', "Copy Throughput (MBPS)");
//Summary Page
export const SUMMARY_PAGE_TITLE = localize('sql.migration.summary.page.title', "Summary");
export const AZURE_ACCOUNT_LINKED = localize('sql.migration.summary.azure.account.linked', "Azure account linked");

View File

@@ -0,0 +1,192 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import { azureResource } from 'azureResource';
import { EventEmitter } from 'events';
import { createResourceGroup } from '../../api/azure';
import * as constants from '../../constants/strings';
export class CreateResourceGroupDialog {
private _dialogObject!: azdata.window.Dialog;
private _view!: azdata.ModelView;
private _creationEvent: EventEmitter = new EventEmitter;
constructor(private _azureAccount: azdata.Account, private _subscription: azureResource.AzureResourceSubscription, private _location: string) {
this._dialogObject = azdata.window.createModelViewDialog(
'',
'CreateResourceGroupDialog',
550,
'callout',
'below',
false,
true,
<azdata.window.IDialogProperties>{
height: 20,
width: 20,
xPos: 0,
yPos: 0
}
);
}
async initialize(): Promise<azureResource.AzureResourceResourceGroup> {
let tab = azdata.window.createTab('sql.migration.CreateResourceGroupDialog');
await tab.registerContent(async (view: azdata.ModelView) => {
this._view = view;
const resourceGroupDescription = view.modelBuilder.text().withProps({
value: constants.RESOURCE_GROUP_DESCRIPTION,
CSSStyles: {
'font-size': '13px',
'margin-bottom': '10px'
}
}).component();
const nameLabel = view.modelBuilder.text().withProps({
value: constants.NAME,
CSSStyles: {
'font-size': '13px',
'font-weight': 'bold',
}
}).component();
const resourceGroupName = view.modelBuilder.inputBox().withProps({
ariaLabel: constants.NAME_OF_NEW_RESOURCE_GROUP
}).withValidation(c => {
let valid = false;
if (c.value!.length > 0 && c.value!.length <= 90 && /^[-\w\._\(\)]+$/.test(c.value!)) {
valid = true;
}
okButton.enabled = valid;
return valid;
}).component();
resourceGroupName.onTextChanged(e => {
errorBox.updateCssStyles({
'display': 'none'
});
});
const okButton = view.modelBuilder.button().withProps({
label: constants.OK,
width: '80px',
enabled: false
}).component();
okButton.onDidClick(async e => {
errorBox.updateCssStyles({
'display': 'none'
});
okButton.enabled = false;
cancelButton.enabled = false;
loading.loading = true;
try {
const resourceGroup = await createResourceGroup(this._azureAccount, this._subscription, resourceGroupName.value!, this._location);
this._creationEvent.emit('done', resourceGroup);
} catch (e) {
errorBox.updateCssStyles({
'display': 'inline'
});
errorBox.text = e.toString();
cancelButton.enabled = true;
resourceGroupName.validate();
} finally {
loading.loading = false;
}
});
const cancelButton = view.modelBuilder.button().withProps({
label: constants.CANCEL,
width: '80px'
}).component();
cancelButton.onDidClick(e => {
this._creationEvent.emit('done', undefined);
});
const loading = view.modelBuilder.loadingComponent().withProps({
loading: false,
loadingText: constants.CREATING_RESOURCE_GROUP,
loadingCompletedText: constants.RESOURCE_GROUP_CREATED
}).component();
const buttonContainer = view.modelBuilder.flexContainer().withProps({
CSSStyles: {
'margin-top': '5px'
}
}).component();
buttonContainer.addItem(okButton, {
flex: '0',
CSSStyles: {
'width': '80px'
}
});
buttonContainer.addItem(cancelButton, {
flex: '0',
CSSStyles: {
'margin-left': '8px',
'width': '80px'
}
});
buttonContainer.addItem(loading, {
flex: '0',
CSSStyles: {
'margin-left': '8px'
}
});
const errorBox = this._view.modelBuilder.infoBox().withProps({
style: 'error',
text: '',
CSSStyles: {
'display': 'none'
}
}).component();
const container = this._view.modelBuilder.flexContainer().withLayout({
flexFlow: 'column'
}).withItems([
resourceGroupDescription,
nameLabel,
resourceGroupName,
errorBox,
buttonContainer
]).component();
const formBuilder = view.modelBuilder.formContainer().withFormItems(
[
{
component: container
}
],
{
horizontal: false
}
);
const form = formBuilder.withLayout({ width: '100%' }).withProps({
CSSStyles: {
'padding': '0px !important'
}
}).component();
return view.initializeModel(form).then(v => {
resourceGroupName.focus();
});
});
this._dialogObject.okButton.label = constants.APPLY;
this._dialogObject.content = [tab];
azdata.window.openDialog(this._dialogObject);
return new Promise((resolve) => {
this._creationEvent.once('done', async (resourceGroup: azureResource.AzureResourceResourceGroup) => {
azdata.window.closeDialog(this._dialogObject);
resolve(resourceGroup);
});
});
}
}

View File

@@ -11,6 +11,7 @@ import * as constants from '../../constants/strings';
import * as os from 'os';
import { azureResource } from 'azureResource';
import { IconPathHelper } from '../../constants/iconPathHelper';
import { CreateResourceGroupDialog } from '../createResourceGroup/createResourceGroupDialog';
import * as EventEmitter from 'events';
export class CreateSqlMigrationServiceDialog {
@@ -22,6 +23,7 @@ export class CreateSqlMigrationServiceDialog {
private migrationServiceLocation!: azdata.InputBoxComponent;
private migrationServiceNameText!: azdata.InputBoxComponent;
private _formSubmitButton!: azdata.ButtonComponent;
private _createResourceGroupLink!: azdata.HyperlinkComponent;
private _statusLoadingComponent!: azdata.LoadingComponent;
private _refreshLoadingComponent!: azdata.LoadingComponent;
@@ -72,7 +74,7 @@ export class CreateSqlMigrationServiceDialog {
const subscription = this._model._targetSubscription;
const resourceGroup = (this.migrationServiceResourceGroupDropdown.value as azdata.CategoryValue).displayName;
const resourceGroup = (this.migrationServiceResourceGroupDropdown.value as azdata.CategoryValue).name;
const location = this._model._targetServerInstance.location;
const serviceName = this.migrationServiceNameText.value;
@@ -242,6 +244,29 @@ export class CreateSqlMigrationServiceDialog {
}
}).component();
this._createResourceGroupLink = this._view.modelBuilder.hyperlink().withProps({
label: constants.CREATE_NEW,
url: ''
}).component();
this._createResourceGroupLink.onDidClick(async e => {
const createResourceGroupDialog = new CreateResourceGroupDialog(this._model._azureAccount, this._model._targetSubscription, this._model._targetServerInstance.location);
const createdResourceGroup = await createResourceGroupDialog.initialize();
if (createdResourceGroup) {
this.migrationServiceResourceGroupDropdown.loading = true;
(<azdata.CategoryValue[]>this.migrationServiceResourceGroupDropdown.values).unshift({
displayName: constants.NEW_RESOURCE_GROUP(createdResourceGroup.name),
name: createdResourceGroup.name
});
this.migrationServiceResourceGroupDropdown.value = {
displayName: createdResourceGroup.name,
name: createdResourceGroup.name
};
this.migrationServiceResourceGroupDropdown.loading = false;
this.migrationServiceResourceGroupDropdown.focus();
}
});
this.migrationServiceNameText = this._view.modelBuilder.inputBox().component();
const locationDropdownLabel = this._view.modelBuilder.text().withProps({
@@ -279,6 +304,7 @@ export class CreateSqlMigrationServiceDialog {
this.migrationServiceLocation,
resourceGroupDropdownLabel,
this.migrationServiceResourceGroupDropdown,
this._createResourceGroupLink,
migrationServiceNameLabel,
this.migrationServiceNameText,
targetlabel,
@@ -316,7 +342,7 @@ export class CreateSqlMigrationServiceDialog {
this.migrationServiceResourceGroupDropdown.loading = true;
try {
this.migrationServiceResourceGroupDropdown.values = await this._model.getAzureResourceGroupDropdownValues(this._model._targetSubscription);
const selectedResourceGroupValue = this.migrationServiceResourceGroupDropdown.values.find(v => v.displayName.toLowerCase() === this._resourceGroupPreset.toLowerCase());
const selectedResourceGroupValue = this.migrationServiceResourceGroupDropdown.values.find(v => v.name.toLowerCase() === this._resourceGroupPreset.toLowerCase());
this.migrationServiceResourceGroupDropdown.value = (selectedResourceGroupValue) ? selectedResourceGroupValue : this.migrationServiceResourceGroupDropdown.values[0];
} finally {
this.migrationServiceResourceGroupDropdown.loading = false;
@@ -467,7 +493,7 @@ export class CreateSqlMigrationServiceDialog {
private async refreshStatus(): Promise<void> {
const subscription = this._model._targetSubscription;
const resourceGroup = (this.migrationServiceResourceGroupDropdown.value as azdata.CategoryValue).displayName;
const resourceGroup = (this.migrationServiceResourceGroupDropdown.value as azdata.CategoryValue).name;
const location = this._model._targetServerInstance.location;
const maxRetries = 5;
@@ -513,7 +539,7 @@ export class CreateSqlMigrationServiceDialog {
}
private async refreshAuthTable(): Promise<void> {
const subscription = this._model._targetSubscription;
const resourceGroup = (this.migrationServiceResourceGroupDropdown.value as azdata.CategoryValue).displayName;
const resourceGroup = (this.migrationServiceResourceGroupDropdown.value as azdata.CategoryValue).name;
const location = this._model._targetServerInstance.location;
const keys = await getSqlMigrationServiceAuthKeys(this._model._azureAccount, subscription, resourceGroup, location, this._createdMigrationService!.name);
@@ -589,6 +615,7 @@ export class CreateSqlMigrationServiceDialog {
this._formSubmitButton.enabled = enable;
this.migrationServiceResourceGroupDropdown.enabled = enable;
this.migrationServiceNameText.enabled = enable;
this._createResourceGroupLink.enabled = enable;
}
}