Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SQL Migration] Import Assessments #24811

Merged
merged 10 commits into from
Nov 15, 2023
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion extensions/sql-migration/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "sql-migration",
"displayName": "%displayName%",
"description": "%description%",
"version": "1.5.0",
"version": "1.5.1",
"publisher": "Microsoft",
"preview": false,
"license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/main/LICENSE.txt",
Expand Down
17 changes: 17 additions & 0 deletions extensions/sql-migration/src/api/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1243,3 +1243,20 @@ export async function clearDropDownWithLoading(dropDown: DropDownComponent): Pro
await dropDown.updateProperty('value', undefined);
await dropDown.updateProperty('values', []);
}

export async function promptUserForFile(filters: { [name: string]: string[] }): Promise<string> {
const options: vscode.OpenDialogOptions = {
defaultUri: vscode.Uri.file(getUserHome()!),
stuti149 marked this conversation as resolved.
Show resolved Hide resolved
canSelectFiles: true,
canSelectFolders: false,
canSelectMany: false,
filters: filters,
};

const fileUris = await vscode.window.showOpenDialog(options);
if (fileUris && fileUris.length > 0 && fileUris[0]) {
return fileUris[0].fsPath;
}

return '';
}
4 changes: 4 additions & 0 deletions extensions/sql-migration/src/constants/strings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export const providerId = 'SqlMigration';
export const extensionConfigSectionName = 'sqlMigration';
export const sqlConfigSectionName = 'sql';
export const configLogDebugInfo = 'logDebugInfo';
export const importAssessmentKey = "ImportAssessment";
export function serviceCrashMessage(error: string): string {
return localize('serviceCrashMessage', "Migration service component could not start. {0}", error);
}
Expand Down Expand Up @@ -217,6 +218,7 @@ export const ASSESSMENT_MIGRATION_WARNING_SQLDB = localize('sql.migration.assess
export const ASSESSMENT_MIGRATION_WARNING_SQLMI = localize('sql.migration.assessment.migration.warning.sqlmi', "Databases that are not ready for migration to Azure SQL Managed Instance can be migrated to SQL Server on Azure Virtual Machines. Alternatively, review assessment results for Azure SQL Database migration readiness.");
export const DATABASES_TABLE_TILE = localize('sql.migration.databases.table.title', "Databases");
export const SQL_SERVER_INSTANCE = localize('sql.migration.sql.server.instance', "SQL Server instance");
export const LOAD_ASSESSMENT_REPORT = localize('sql.migration.load.assessment.report', "Load assessment report");
export const SAVE_ASSESSMENT_REPORT = localize('sql.migration.save.assessment.report', "Save assessment report");
export const SAVE_RECOMMENDATION_REPORT = localize('sql.migration.save.recommendation.report', "Save recommendation report");
export function SAVE_ASSESSMENT_REPORT_SUCCESS(filePath: string): string {
Expand Down Expand Up @@ -1517,6 +1519,8 @@ export const MIGRATION_SERVICE_DESCRIPTION = localize('sql.migration.select.serv

// Desktop tabs
export const DESKTOP_MIGRATION_BUTTON_LABEL = localize('sql.migration.tab.button.migration.label', 'New migration');
export const DESKTOP_IMPORT_MIGRATION_BUTTON_LABEL = localize('sql.migration.tab.import.migration.label', 'Import assessment');
export const DESKTOP_IMPORT_MIGRATION_BUTTON_DESCRIPTION = localize('sql.migration.tab.import.migration.description', 'Import assessment to Azure SQL');
export const DESKTOP_MIGRATION_BUTTON_DESCRIPTION = localize('sql.migration.tab.button.migration.description', 'Migrate to Azure SQL');
export const DESKTOP_LOGIN_MIGRATION_BUTTON_LABEL = localize('sql.migration.tab.button.login.migration.label', 'New login migration (PREVIEW)');
export const DESKTOP_LOGIN_MIGRATION_BUTTON_DESCRIPTION = localize('sql.migration.tab.button.login.migration.description', 'Migrate logins to Azure SQL');
Expand Down
3 changes: 3 additions & 0 deletions extensions/sql-migration/src/dashboard/dashboardTab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,13 @@ export class DashboardTab extends TabBase<DashboardTab> {
}

public async create(
context: vscode.ExtensionContext,
view: azdata.ModelView,
openMigrationsFcn: (status: AdsMigrationStatus) => Promise<void>,
serviceContextChangedEvent: vscode.EventEmitter<ServiceContextChangeEvent>,
statusBar: DashboardStatusBar): Promise<DashboardTab> {

this.context = context;
stuti149 marked this conversation as resolved.
Show resolved Hide resolved
this.view = view;
this.openMigrationsFcn = openMigrationsFcn;
this.serviceContextChangedEvent = serviceContextChangedEvent;
Expand Down Expand Up @@ -143,6 +145,7 @@ export class DashboardTab extends TabBase<DashboardTab> {
toolbar.addToolbarItems([
<azdata.ToolbarComponent>{ component: this.createNewMigrationButton() },
<azdata.ToolbarComponent>{ component: this.createNewLoginMigrationButton() },
<azdata.ToolbarComponent>{ component: this.createImportMigrationButton() },
<azdata.ToolbarComponent>{ component: this.createNewHelpAndSupportButton() },
<azdata.ToolbarComponent>{ component: this.createFeedbackButton() },
]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ export class MigrationsListTab extends TabBase<MigrationsListTab> {
toolbar.addToolbarItems([
<azdata.ToolbarComponent>{ component: this.createNewMigrationButton(), toolbarSeparatorAfter: true },
<azdata.ToolbarComponent>{ component: this.createNewLoginMigrationButton(), toolbarSeparatorAfter: true },
<azdata.ToolbarComponent>{ component: this.createImportMigrationButton(), toolbarSeparatorAfter: true },
<azdata.ToolbarComponent>{ component: this.createNewHelpAndSupportButton() },
<azdata.ToolbarComponent>{ component: this.createFeedbackButton(), toolbarSeparatorAfter: true },
<azdata.ToolbarComponent>{ component: this._refreshLoader },
Expand Down
45 changes: 35 additions & 10 deletions extensions/sql-migration/src/dashboard/sqlServerDashboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { MigrationCutoverDialogModel } from '../dialog/migrationCutover/migratio
import { RestartMigrationDialog } from '../dialog/restartMigration/restartMigrationDialog';
import { SqlMigrationServiceDetailsDialog } from '../dialog/sqlMigrationService/sqlMigrationServiceDetailsDialog';
import { MigrationLocalStorage } from '../models/migrationLocalStorage';
import { MigrationStateModel, SavedInfo } from '../models/stateMachine';
import { MigrationStateModel, Page, SavedInfo } from '../models/stateMachine';
import { logError, TelemetryViews } from '../telemetry';
import { WizardController } from '../wizard/wizardController';
import { DashboardStatusBar, ErrorEvent } from './DashboardStatusBar';
Expand All @@ -29,6 +29,7 @@ import { migrationServiceProvider } from '../service/provider';
import { ApiType, SqlMigrationService } from '../service/features';
import { getSourceConnectionId, getSourceConnectionProfile } from '../api/sqlUtils';
import { openRetryMigrationDialog } from '../dialog/retryMigration/retryMigrationDialog';
import { ImportAssessmentDialog } from '../dialog/assessment/importAssessmentDialog';

export interface MenuCommandArgs {
connectionId: string,
Expand Down Expand Up @@ -107,6 +108,7 @@ export class DashboardWidget {
};

const dashboardTab = await new DashboardTab().create(
this._context,
view,
async (filter: AdsMigrationStatus) => await openMigrationFcn(filter),
this._onServiceContextChanged,
Expand Down Expand Up @@ -531,20 +533,39 @@ export class DashboardWidget {
if (migrationService) {
this.stateModel = new MigrationStateModel(this._context, migrationService);
this._context.subscriptions.push(this.stateModel);

const wizardController = new WizardController(
this._context,
this.stateModel,
this._onServiceContextChanged);

const savedInfo = this.checkSavedInfo(serverName);
if (savedInfo) {
this.stateModel.savedInfo = savedInfo;
this.stateModel.serverName = serverName;
const savedAssessmentDialog = new SavedAssessmentDialog(
this._context,
this.stateModel,
this._onServiceContextChanged);
await savedAssessmentDialog.openDialog();

const importSavedInfo = this.checkSavedInfo(loc.importAssessmentKey);
if (importSavedInfo && importSavedInfo.closedPage === Page.ImportAssessment) {
await this.clearSavedInfo(loc.importAssessmentKey);
if (importSavedInfo.serverAssessment !== null) {
this.stateModel._assessmentResults = importSavedInfo.serverAssessment;
await this.stateModel.loadSavedInfo();

serverName = importSavedInfo.serverAssessment?.issues[0]?.serverName ??
importSavedInfo.serverAssessment?.databaseAssessments[0]?.issues[0]?.serverName;
this.stateModel.serverName = serverName;
}

const importAssessmentDialog = new ImportAssessmentDialog('ownerUri', this.stateModel, serverName);
await importAssessmentDialog.openDialog();
} else {
const savedAssessmentDialog = new SavedAssessmentDialog(
this._context,
this.stateModel,
this._onServiceContextChanged);
await savedAssessmentDialog.openDialog();
}
} else {
const wizardController = new WizardController(
this._context,
this.stateModel,
this._onServiceContextChanged);
await wizardController.openWizard();
}
}
Expand Down Expand Up @@ -580,6 +601,10 @@ export class DashboardWidget {
return this._context.globalState.get<SavedInfo>(`${this.stateModel.mementoString}.${serverName}`);
}

private async clearSavedInfo(serverName: string) {
await this._context.globalState.update(`${this.stateModel.mementoString}.${serverName}`, {});
}

public async launchNewSupportRequest(): Promise<void> {
await vscode.env.openExternal(vscode.Uri.parse(
`https://portal.azure.com/#blade/Microsoft_Azure_Support/HelpAndSupportBlade/newsupportrequest`));
Expand Down
42 changes: 42 additions & 0 deletions extensions/sql-migration/src/dashboard/tabBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ import { getSelectedServiceStatus } from '../models/migrationLocalStorage';
import { MenuCommands, SqlMigrationExtensionId } from '../api/utils';
import { DashboardStatusBar } from './DashboardStatusBar';
import { ShowStatusMessageDialog } from '../dialog/generic/genericDialogs';
import * as utils from '../api/utils';
import * as fs from 'fs';
import { getSourceConnectionProfile } from '../api/sqlUtils';
import { parseAssessmentReport } from '../dialog/assessment/assessmentUtils';
import { HelpAndSupportDialog } from '../dialog/help/helpAndSupportDialog';

export const EmptySettingValue = '-';
Expand Down Expand Up @@ -47,6 +51,8 @@ export abstract class TabBase<T> implements azdata.Tab, vscode.Disposable {
protected serviceContextChangedEvent!: vscode.EventEmitter<ServiceContextChangeEvent>;
protected statusBar!: DashboardStatusBar;

private mementoToken: string = 'sqlMigration.assessmentResults';

protected abstract initialize(view: azdata.ModelView): Promise<void>;

public abstract refresh(initialize?: boolean): Promise<void>;
Expand Down Expand Up @@ -137,6 +143,42 @@ export abstract class TabBase<T> implements azdata.Tab, vscode.Disposable {
return newMigrationButton;
}

protected createImportMigrationButton(): azdata.ButtonComponent {
const importMigrationButton = this.view.modelBuilder.button()
.withProps({
buttonType: azdata.ButtonType.Normal,
label: loc.DESKTOP_IMPORT_MIGRATION_BUTTON_LABEL,
description: loc.DESKTOP_IMPORT_MIGRATION_BUTTON_DESCRIPTION,
height: 24,
iconHeight: 24,
iconWidth: 24,
iconPath: IconPathHelper.addNew,
}).component();
this.disposables.push(
importMigrationButton.onDidClick(async () => {
const filepath = await utils.promptUserForFile({ 'Json (*.json)': ['json'] });
if (filepath) {
try {
const assessmentReportJson = fs.readFileSync(filepath, 'utf-8');
const assessmentReport = JSON.parse(assessmentReportJson);

const saveInfo = parseAssessmentReport(assessmentReport);
await this.context.globalState.update(`${this.mementoToken}.${loc.importAssessmentKey}`, saveInfo);
} catch (err) {
void vscode.window.showInformationMessage(`Selected invalid format import file: ${filepath}`);
stuti149 marked this conversation as resolved.
Show resolved Hide resolved
}

const actionId = MenuCommands.StartMigration;
const args = {
extensionId: SqlMigrationExtensionId,
issueTitle: loc.DASHBOARD_MIGRATE_TASK_BUTTON_TITLE,
};
await vscode.commands.executeCommand(actionId, args);
}
}));
return importMigrationButton;
}

protected createNewHelpAndSupportButton(): azdata.ButtonComponent {
const newHelpAndSupportButton = this.view.modelBuilder.button()
.withProps({
Expand Down
Loading
Loading