mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
Dev/brih/feature/switch ads to portal context (#18963)
* Add CodeQL Analysis workflow (#10195) * Add CodeQL Analysis workflow * Fix path * dashboard refactor * update version, readme, minor ui changes * fix merge issue * Revert "Add CodeQL Analysis workflow (#10195)" This reverts commit fe98d586cd75be4758ac544649bb4983accf4acd. * fix context switching issue * fix resource id parsing error and mi api version * mv refresh btn, rm autorefresh, align cards * remove missed autorefresh code * improve error handling and messages * fix typos * remove duplicate/unnecessary _populate* calls * change clear configuration button text * remove confusing watermark text * add stale account handling Co-authored-by: Justin Hutchings <jhutchings1@users.noreply.github.com>
This commit is contained in:
@@ -7,10 +7,11 @@ import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import { MigrationCutoverDialogModel } from './migrationCutoverDialogModel';
|
||||
import * as constants from '../../constants/strings';
|
||||
import { SqlManagedInstance } from '../../api/azure';
|
||||
import { getMigrationTargetInstance, SqlManagedInstance } from '../../api/azure';
|
||||
import { IconPathHelper } from '../../constants/iconPathHelper';
|
||||
import { convertByteSizeToReadableUnit, get12HourTime } from '../../api/utils';
|
||||
import * as styles from '../../constants/styles';
|
||||
import { isBlobMigration } from '../../constants/helper';
|
||||
export class ConfirmCutoverDialog {
|
||||
private _dialogObject!: azdata.window.Dialog;
|
||||
private _view!: azdata.ModelView;
|
||||
@@ -21,20 +22,17 @@ export class ConfirmCutoverDialog {
|
||||
}
|
||||
|
||||
async initialize(): Promise<void> {
|
||||
|
||||
let tab = azdata.window.createTab('');
|
||||
const tab = azdata.window.createTab('');
|
||||
tab.registerContent(async (view: azdata.ModelView) => {
|
||||
this._view = view;
|
||||
|
||||
const completeCutoverText = view.modelBuilder.text().withProps({
|
||||
value: constants.COMPLETE_CUTOVER,
|
||||
CSSStyles: {
|
||||
...styles.PAGE_TITLE_CSS
|
||||
}
|
||||
CSSStyles: { ...styles.PAGE_TITLE_CSS }
|
||||
}).component();
|
||||
|
||||
const sourceDatabaseText = view.modelBuilder.text().withProps({
|
||||
value: this.migrationCutoverModel._migration.migrationContext.properties.sourceDatabaseName,
|
||||
value: this.migrationCutoverModel._migration.properties.sourceDatabaseName,
|
||||
CSSStyles: {
|
||||
...styles.SMALL_NOTE_CSS,
|
||||
'margin': '4px 0px 8px'
|
||||
@@ -42,12 +40,9 @@ export class ConfirmCutoverDialog {
|
||||
}).component();
|
||||
|
||||
const separator = this._view.modelBuilder.separator().withProps({ width: '800px' }).component();
|
||||
|
||||
const helpMainText = this._view.modelBuilder.text().withProps({
|
||||
value: constants.CUTOVER_HELP_MAIN,
|
||||
CSSStyles: {
|
||||
...styles.BODY_CSS
|
||||
}
|
||||
CSSStyles: { ...styles.BODY_CSS }
|
||||
}).component();
|
||||
|
||||
const helpStepsText = this._view.modelBuilder.text().withProps({
|
||||
@@ -58,8 +53,9 @@ export class ConfirmCutoverDialog {
|
||||
}
|
||||
}).component();
|
||||
|
||||
|
||||
const fileContainer = this.migrationCutoverModel.isBlobMigration() ? this.createBlobFileContainer() : this.createNetworkShareFileContainer();
|
||||
const fileContainer = isBlobMigration(this.migrationCutoverModel.migrationStatus)
|
||||
? this.createBlobFileContainer()
|
||||
: this.createNetworkShareFileContainer();
|
||||
|
||||
const confirmCheckbox = this._view.modelBuilder.checkBox().withProps({
|
||||
CSSStyles: {
|
||||
@@ -76,16 +72,19 @@ export class ConfirmCutoverDialog {
|
||||
const cutoverWarning = this._view.modelBuilder.infoBox().withProps({
|
||||
text: constants.COMPLETING_CUTOVER_WARNING,
|
||||
style: 'warning',
|
||||
CSSStyles: {
|
||||
...styles.BODY_CSS
|
||||
}
|
||||
CSSStyles: { ...styles.BODY_CSS }
|
||||
}).component();
|
||||
|
||||
|
||||
let infoDisplay = 'none';
|
||||
if (this.migrationCutoverModel._migration.targetManagedInstance.id.toLocaleLowerCase().includes('managedinstances')
|
||||
&& (<SqlManagedInstance>this.migrationCutoverModel._migration.targetManagedInstance)?.sku?.tier === 'BusinessCritical') {
|
||||
infoDisplay = 'inline';
|
||||
if (this.migrationCutoverModel._migration.id.toLocaleLowerCase().includes('managedinstances')) {
|
||||
const targetInstance = await getMigrationTargetInstance(
|
||||
this.migrationCutoverModel._serviceConstext.azureAccount!,
|
||||
this.migrationCutoverModel._serviceConstext.subscription!,
|
||||
this.migrationCutoverModel._migration);
|
||||
|
||||
if ((<SqlManagedInstance>targetInstance)?.sku?.tier === 'BusinessCritical') {
|
||||
infoDisplay = 'inline';
|
||||
}
|
||||
}
|
||||
|
||||
const businessCriticalInfoBox = this._view.modelBuilder.infoBox().withProps({
|
||||
@@ -111,23 +110,18 @@ export class ConfirmCutoverDialog {
|
||||
businessCriticalInfoBox
|
||||
]).component();
|
||||
|
||||
|
||||
this._dialogObject.okButton.enabled = false;
|
||||
this._dialogObject.okButton.label = constants.COMPLETE_CUTOVER;
|
||||
this._disposables.push(this._dialogObject.okButton.onClick(async (e) => {
|
||||
await this.migrationCutoverModel.startCutover();
|
||||
void vscode.window.showInformationMessage(constants.CUTOVER_IN_PROGRESS(this.migrationCutoverModel._migration.migrationContext.properties.sourceDatabaseName));
|
||||
void vscode.window.showInformationMessage(
|
||||
constants.CUTOVER_IN_PROGRESS(
|
||||
this.migrationCutoverModel._migration.properties.sourceDatabaseName));
|
||||
}));
|
||||
|
||||
const formBuilder = view.modelBuilder.formContainer().withFormItems(
|
||||
[
|
||||
{
|
||||
component: container
|
||||
}
|
||||
],
|
||||
{
|
||||
horizontal: false
|
||||
}
|
||||
[{ component: container }],
|
||||
{ horizontal: false }
|
||||
);
|
||||
const form = formBuilder.withLayout({ width: '100%' }).component();
|
||||
|
||||
@@ -144,18 +138,14 @@ export class ConfirmCutoverDialog {
|
||||
|
||||
private createBlobFileContainer(): azdata.FlexContainer {
|
||||
const container = this._view.modelBuilder.flexContainer().withProps({
|
||||
CSSStyles: {
|
||||
'margin': '8px 0'
|
||||
}
|
||||
CSSStyles: { 'margin': '8px 0' }
|
||||
}).component();
|
||||
|
||||
const containerHeading = this._view.modelBuilder.text().withProps({
|
||||
value: constants.PENDING_BACKUPS(this.migrationCutoverModel.getPendingLogBackupsCount() ?? 0),
|
||||
width: 250,
|
||||
CSSStyles: {
|
||||
...styles.LABEL_CSS
|
||||
}
|
||||
CSSStyles: { ...styles.LABEL_CSS }
|
||||
}).component();
|
||||
container.addItem(containerHeading, { flex: '0' });
|
||||
|
||||
const refreshButton = this._view.modelBuilder.button().withProps({
|
||||
iconPath: IconPathHelper.refresh,
|
||||
@@ -165,13 +155,7 @@ export class ConfirmCutoverDialog {
|
||||
height: 20,
|
||||
label: constants.REFRESH,
|
||||
}).component();
|
||||
|
||||
|
||||
container.addItem(containerHeading, {
|
||||
flex: '0'
|
||||
});
|
||||
|
||||
refreshButton.onDidClick(async e => {
|
||||
this._disposables.push(refreshButton.onDidClick(async e => {
|
||||
refreshLoader.loading = true;
|
||||
try {
|
||||
await this.migrationCutoverModel.fetchStatus();
|
||||
@@ -184,11 +168,8 @@ export class ConfirmCutoverDialog {
|
||||
} finally {
|
||||
refreshLoader.loading = false;
|
||||
}
|
||||
});
|
||||
|
||||
container.addItem(refreshButton, {
|
||||
flex: '0'
|
||||
});
|
||||
}));
|
||||
container.addItem(refreshButton, { flex: '0' });
|
||||
|
||||
const refreshLoader = this._view.modelBuilder.loadingComponent().withProps({
|
||||
loading: false,
|
||||
@@ -197,10 +178,8 @@ export class ConfirmCutoverDialog {
|
||||
'margin-left': '8px'
|
||||
}
|
||||
}).component();
|
||||
container.addItem(refreshLoader, { flex: '0' });
|
||||
|
||||
container.addItem(refreshLoader, {
|
||||
flex: '0'
|
||||
});
|
||||
return container;
|
||||
}
|
||||
|
||||
@@ -227,23 +206,18 @@ export class ConfirmCutoverDialog {
|
||||
}
|
||||
}).component();
|
||||
|
||||
containerHeading.onDidClick(async e => {
|
||||
this._disposables.push(containerHeading.onDidClick(async e => {
|
||||
if (expanded) {
|
||||
containerHeading.iconPath = IconPathHelper.expandButtonClosed;
|
||||
containerHeading.iconHeight = 12;
|
||||
await fileTable.updateCssStyles({
|
||||
'display': 'none'
|
||||
});
|
||||
|
||||
await fileTable.updateCssStyles({ 'display': 'none' });
|
||||
} else {
|
||||
containerHeading.iconPath = IconPathHelper.expandButtonOpen;
|
||||
containerHeading.iconHeight = 8;
|
||||
await fileTable.updateCssStyles({
|
||||
'display': 'inline'
|
||||
});
|
||||
await fileTable.updateCssStyles({ 'display': 'inline' });
|
||||
}
|
||||
expanded = !expanded;
|
||||
});
|
||||
}));
|
||||
|
||||
const refreshButton = this._view.modelBuilder.button().withProps({
|
||||
iconPath: IconPathHelper.refresh,
|
||||
@@ -252,16 +226,12 @@ export class ConfirmCutoverDialog {
|
||||
width: 70,
|
||||
height: 20,
|
||||
label: constants.REFRESH,
|
||||
CSSStyles: {
|
||||
'margin-top': '13px'
|
||||
}
|
||||
CSSStyles: { 'margin-top': '13px' }
|
||||
}).component();
|
||||
|
||||
headingRow.addItem(containerHeading, {
|
||||
flex: '0'
|
||||
});
|
||||
headingRow.addItem(containerHeading, { flex: '0' });
|
||||
|
||||
refreshButton.onDidClick(async e => {
|
||||
this._disposables.push(refreshButton.onDidClick(async e => {
|
||||
refreshLoader.loading = true;
|
||||
try {
|
||||
await this.migrationCutoverModel.fetchStatus();
|
||||
@@ -276,11 +246,8 @@ export class ConfirmCutoverDialog {
|
||||
} finally {
|
||||
refreshLoader.loading = false;
|
||||
}
|
||||
});
|
||||
|
||||
headingRow.addItem(refreshButton, {
|
||||
flex: '0'
|
||||
});
|
||||
}));
|
||||
headingRow.addItem(refreshButton, { flex: '0' });
|
||||
|
||||
const refreshLoader = this._view.modelBuilder.loadingComponent().withProps({
|
||||
loading: false,
|
||||
@@ -290,20 +257,13 @@ export class ConfirmCutoverDialog {
|
||||
'height': '13px'
|
||||
}
|
||||
}).component();
|
||||
|
||||
headingRow.addItem(refreshLoader, {
|
||||
flex: '0'
|
||||
});
|
||||
|
||||
headingRow.addItem(refreshLoader, { flex: '0' });
|
||||
container.addItem(headingRow);
|
||||
|
||||
const lastScanCompleted = this._view.modelBuilder.text().withProps({
|
||||
value: constants.LAST_SCAN_COMPLETED(get12HourTime(new Date())),
|
||||
CSSStyles: {
|
||||
...styles.NOTE_CSS
|
||||
}
|
||||
CSSStyles: { ...styles.NOTE_CSS }
|
||||
}).component();
|
||||
|
||||
container.addItem(lastScanCompleted);
|
||||
|
||||
const fileTable = this._view.modelBuilder.table().withProps({
|
||||
@@ -327,9 +287,7 @@ export class ConfirmCutoverDialog {
|
||||
data: [],
|
||||
width: 400,
|
||||
height: 150,
|
||||
CSSStyles: {
|
||||
'display': 'none'
|
||||
}
|
||||
CSSStyles: { 'display': 'none' }
|
||||
}).component();
|
||||
container.addItem(fileTable);
|
||||
this.refreshFileTable(fileTable);
|
||||
@@ -347,9 +305,7 @@ export class ConfirmCutoverDialog {
|
||||
];
|
||||
});
|
||||
} else {
|
||||
fileTable.data = [
|
||||
[constants.NO_PENDING_BACKUPS]
|
||||
];
|
||||
fileTable.data = [[constants.NO_PENDING_BACKUPS]];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,26 +6,24 @@
|
||||
import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import { IconPathHelper } from '../../constants/iconPathHelper';
|
||||
import { BackupFileInfoStatus, MigrationContext, MigrationStatus } from '../../models/migrationLocalStorage';
|
||||
import { BackupFileInfoStatus, MigrationServiceContext, MigrationStatus } from '../../models/migrationLocalStorage';
|
||||
import { MigrationCutoverDialogModel } from './migrationCutoverDialogModel';
|
||||
import * as loc from '../../constants/strings';
|
||||
import { convertByteSizeToReadableUnit, convertIsoTimeToLocalTime, getSqlServerName, getMigrationStatusImage, SupportedAutoRefreshIntervals, clearDialogMessage, displayDialogErrorMessage } from '../../api/utils';
|
||||
import { convertByteSizeToReadableUnit, convertIsoTimeToLocalTime, getSqlServerName, getMigrationStatusImage, clearDialogMessage, displayDialogErrorMessage } from '../../api/utils';
|
||||
import { EOL } from 'os';
|
||||
import { ConfirmCutoverDialog } from './confirmCutoverDialog';
|
||||
import { logError, TelemetryViews } from '../../telemtery';
|
||||
import { RetryMigrationDialog } from '../retryMigration/retryMigrationDialog';
|
||||
import * as styles from '../../constants/styles';
|
||||
import { canRetryMigration } from '../../constants/helper';
|
||||
import { canRetryMigration, getMigrationStatus, isBlobMigration, isOfflineMigation } from '../../constants/helper';
|
||||
import { DatabaseMigration, getResourceName } from '../../api/azure';
|
||||
|
||||
const refreshFrequency: SupportedAutoRefreshIntervals = 30000;
|
||||
const statusImageSize: number = 14;
|
||||
|
||||
export class MigrationCutoverDialog {
|
||||
private _context: vscode.ExtensionContext;
|
||||
private _dialogObject!: azdata.window.Dialog;
|
||||
private _view!: azdata.ModelView;
|
||||
private _model: MigrationCutoverDialogModel;
|
||||
private _migration: MigrationContext;
|
||||
|
||||
private _databaseTitleName!: azdata.TextComponent;
|
||||
private _cutoverButton!: azdata.ButtonComponent;
|
||||
@@ -52,7 +50,6 @@ export class MigrationCutoverDialog {
|
||||
|
||||
private _fileCount!: azdata.TextComponent;
|
||||
private _fileTable!: azdata.DeclarativeTableComponent;
|
||||
private _autoRefreshHandle!: any;
|
||||
private _disposables: vscode.Disposable[] = [];
|
||||
private _emptyTableFill!: azdata.FlexContainer;
|
||||
|
||||
@@ -60,10 +57,13 @@ export class MigrationCutoverDialog {
|
||||
|
||||
readonly _infoFieldWidth: string = '250px';
|
||||
|
||||
constructor(context: vscode.ExtensionContext, migration: MigrationContext) {
|
||||
this._context = context;
|
||||
this._migration = migration;
|
||||
this._model = new MigrationCutoverDialogModel(migration);
|
||||
constructor(
|
||||
private readonly _context: vscode.ExtensionContext,
|
||||
private readonly _serviceContext: MigrationServiceContext,
|
||||
private readonly _migration: DatabaseMigration,
|
||||
private readonly _onClosedCallback: () => Promise<void>) {
|
||||
|
||||
this._model = new MigrationCutoverDialogModel(_serviceContext, _migration);
|
||||
this._dialogObject = azdata.window.createModelViewDialog('', 'MigrationCutoverDialog', 'wide');
|
||||
}
|
||||
|
||||
@@ -224,15 +224,14 @@ export class MigrationCutoverDialog {
|
||||
);
|
||||
const form = formBuilder.withLayout({ width: '100%' }).component();
|
||||
|
||||
this._disposables.push(this._view.onClosed(e => {
|
||||
clearInterval(this._autoRefreshHandle);
|
||||
this._disposables.forEach(
|
||||
d => { try { d.dispose(); } catch { } });
|
||||
}));
|
||||
this._disposables.push(
|
||||
this._view.onClosed(e => {
|
||||
this._disposables.forEach(
|
||||
d => { try { d.dispose(); } catch { } });
|
||||
}));
|
||||
|
||||
return view.initializeModel(form).then(async (value) => {
|
||||
await this.refreshStatus();
|
||||
});
|
||||
await view.initializeModel(form);
|
||||
await this.refreshStatus();
|
||||
} catch (e) {
|
||||
logError(TelemetryViews.MigrationCutoverDialog, 'IntializingFailed', e);
|
||||
}
|
||||
@@ -242,9 +241,6 @@ export class MigrationCutoverDialog {
|
||||
this._dialogObject.cancelButton.hidden = true;
|
||||
this._dialogObject.okButton.label = loc.CLOSE;
|
||||
|
||||
this._disposables.push(this._dialogObject.okButton.onClick(e => {
|
||||
clearInterval(this._autoRefreshHandle);
|
||||
}));
|
||||
azdata.window.openDialog(this._dialogObject);
|
||||
}
|
||||
|
||||
@@ -262,7 +258,7 @@ export class MigrationCutoverDialog {
|
||||
...styles.PAGE_TITLE_CSS
|
||||
},
|
||||
width: 950,
|
||||
value: this._model._migration.migrationContext.properties.sourceDatabaseName
|
||||
value: this._model._migration.properties.sourceDatabaseName
|
||||
}).component();
|
||||
|
||||
const databaseSubTitle = this._view.modelBuilder.text().withProps({
|
||||
@@ -282,8 +278,6 @@ export class MigrationCutoverDialog {
|
||||
width: 950
|
||||
}).component();
|
||||
|
||||
this.setAutoRefresh(refreshFrequency);
|
||||
|
||||
const titleLogoContainer = this._view.modelBuilder.flexContainer().withProps({
|
||||
width: 1000
|
||||
}).component();
|
||||
@@ -314,7 +308,7 @@ export class MigrationCutoverDialog {
|
||||
enabled: false,
|
||||
CSSStyles: {
|
||||
...styles.BODY_CSS,
|
||||
'display': this._isOnlineMigration() ? 'block' : 'none'
|
||||
'display': isOfflineMigation(this._model._migration) ? 'none' : 'block'
|
||||
}
|
||||
}).component();
|
||||
|
||||
@@ -322,16 +316,13 @@ export class MigrationCutoverDialog {
|
||||
await this.refreshStatus();
|
||||
const dialog = new ConfirmCutoverDialog(this._model);
|
||||
await dialog.initialize();
|
||||
await this.refreshStatus();
|
||||
|
||||
if (this._model.CutoverError) {
|
||||
displayDialogErrorMessage(this._dialogObject, loc.MIGRATION_CUTOVER_ERROR, this._model.CutoverError);
|
||||
}
|
||||
}));
|
||||
|
||||
headerActions.addItem(this._cutoverButton, {
|
||||
flex: '0'
|
||||
});
|
||||
headerActions.addItem(this._cutoverButton, { flex: '0' });
|
||||
|
||||
this._cancelButton = this._view.modelBuilder.button().withProps({
|
||||
iconPath: IconPathHelper.cancel,
|
||||
@@ -377,7 +368,11 @@ export class MigrationCutoverDialog {
|
||||
this._disposables.push(this._retryButton.onDidClick(
|
||||
async (e) => {
|
||||
await this.refreshStatus();
|
||||
let retryMigrationDialog = new RetryMigrationDialog(this._context, this._migration);
|
||||
const retryMigrationDialog = new RetryMigrationDialog(
|
||||
this._context,
|
||||
this._serviceContext,
|
||||
this._migration,
|
||||
this._onClosedCallback);
|
||||
await retryMigrationDialog.openDialog();
|
||||
}
|
||||
));
|
||||
@@ -397,12 +392,14 @@ export class MigrationCutoverDialog {
|
||||
}
|
||||
}).component();
|
||||
|
||||
this._disposables.push(this._refreshButton.onDidClick(
|
||||
async (e) => await this.refreshStatus()));
|
||||
this._disposables.push(
|
||||
this._refreshButton.onDidClick(async (e) => {
|
||||
this._refreshButton.enabled = false;
|
||||
await this.refreshStatus();
|
||||
this._refreshButton.enabled = true;
|
||||
}));
|
||||
|
||||
headerActions.addItem(this._refreshButton, {
|
||||
flex: '0',
|
||||
});
|
||||
headerActions.addItem(this._refreshButton, { flex: '0' });
|
||||
|
||||
this._copyDatabaseMigrationDetails = this._view.modelBuilder.button().withProps({
|
||||
iconPath: IconPathHelper.copy,
|
||||
@@ -425,9 +422,7 @@ export class MigrationCutoverDialog {
|
||||
|
||||
headerActions.addItem(this._copyDatabaseMigrationDetails, {
|
||||
flex: '0',
|
||||
CSSStyles: {
|
||||
'margin-left': '5px'
|
||||
}
|
||||
CSSStyles: { 'margin-left': '5px' }
|
||||
});
|
||||
|
||||
// create new support request button. Hiding button until sql migration support has been setup.
|
||||
@@ -443,11 +438,11 @@ export class MigrationCutoverDialog {
|
||||
}
|
||||
}).component();
|
||||
|
||||
this._newSupportRequest.onDidClick(async (e) => {
|
||||
const serviceId = this._model._migration.controller.id;
|
||||
this._disposables.push(this._newSupportRequest.onDidClick(async (e) => {
|
||||
const serviceId = this._model._migration.properties.migrationService;
|
||||
const supportUrl = `https://portal.azure.com/#resource${serviceId}/supportrequest`;
|
||||
await vscode.env.openExternal(vscode.Uri.parse(supportUrl));
|
||||
});
|
||||
}));
|
||||
|
||||
headerActions.addItem(this._newSupportRequest, {
|
||||
flex: '0',
|
||||
@@ -519,12 +514,12 @@ export class MigrationCutoverDialog {
|
||||
addInfoFieldToContainer(this._targetServerInfoField, flexTarget);
|
||||
addInfoFieldToContainer(this._targetVersionInfoField, flexTarget);
|
||||
|
||||
const isBlobMigration = this._model.isBlobMigration();
|
||||
const _isBlobMigration = isBlobMigration(this._model._migration);
|
||||
const flexStatus = this._view.modelBuilder.flexContainer().withLayout({
|
||||
flexFlow: 'column'
|
||||
}).component();
|
||||
this._migrationStatusInfoField = await this.createInfoField(loc.MIGRATION_STATUS, '', false, ' ');
|
||||
this._fullBackupFileOnInfoField = await this.createInfoField(loc.FULL_BACKUP_FILES, '', isBlobMigration);
|
||||
this._fullBackupFileOnInfoField = await this.createInfoField(loc.FULL_BACKUP_FILES, '', _isBlobMigration);
|
||||
this._backupLocationInfoField = await this.createInfoField(loc.BACKUP_LOCATION, '');
|
||||
addInfoFieldToContainer(this._migrationStatusInfoField, flexStatus);
|
||||
addInfoFieldToContainer(this._fullBackupFileOnInfoField, flexStatus);
|
||||
@@ -533,10 +528,10 @@ export class MigrationCutoverDialog {
|
||||
const flexFile = this._view.modelBuilder.flexContainer().withLayout({
|
||||
flexFlow: 'column'
|
||||
}).component();
|
||||
this._lastLSNInfoField = await this.createInfoField(loc.LAST_APPLIED_LSN, '', isBlobMigration);
|
||||
this._lastLSNInfoField = await this.createInfoField(loc.LAST_APPLIED_LSN, '', _isBlobMigration);
|
||||
this._lastAppliedBackupInfoField = await this.createInfoField(loc.LAST_APPLIED_BACKUP_FILES, '');
|
||||
this._lastAppliedBackupTakenOnInfoField = await this.createInfoField(loc.LAST_APPLIED_BACKUP_FILES_TAKEN_ON, '', isBlobMigration);
|
||||
this._currentRestoringFileInfoField = await this.createInfoField(loc.CURRENTLY_RESTORING_FILE, '', !isBlobMigration);
|
||||
this._lastAppliedBackupTakenOnInfoField = await this.createInfoField(loc.LAST_APPLIED_BACKUP_FILES_TAKEN_ON, '', _isBlobMigration);
|
||||
this._currentRestoringFileInfoField = await this.createInfoField(loc.CURRENTLY_RESTORING_FILE, '', !_isBlobMigration);
|
||||
addInfoFieldToContainer(this._lastLSNInfoField, flexFile);
|
||||
addInfoFieldToContainer(this._lastAppliedBackupInfoField, flexFile);
|
||||
addInfoFieldToContainer(this._lastAppliedBackupTakenOnInfoField, flexFile);
|
||||
@@ -561,33 +556,8 @@ export class MigrationCutoverDialog {
|
||||
return flexInfo;
|
||||
}
|
||||
|
||||
private setAutoRefresh(interval: SupportedAutoRefreshIntervals): void {
|
||||
const shouldRefresh = (status: string | undefined) => !status
|
||||
|| status === MigrationStatus.InProgress
|
||||
|| status === MigrationStatus.Creating
|
||||
|| status === MigrationStatus.Completing
|
||||
|| status === MigrationStatus.Canceling;
|
||||
|
||||
if (shouldRefresh(this.getMigrationStatus())) {
|
||||
const classVariable = this;
|
||||
clearInterval(this._autoRefreshHandle);
|
||||
if (interval !== -1) {
|
||||
this._autoRefreshHandle = setInterval(async function () { await classVariable.refreshStatus(); }, interval);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private getMigrationDetails(): string {
|
||||
if (this._model.migrationOpStatus) {
|
||||
return (JSON.stringify(
|
||||
{
|
||||
'async-operation-details': this._model.migrationOpStatus,
|
||||
'details': this._model.migrationStatus
|
||||
}
|
||||
, undefined, 2));
|
||||
} else {
|
||||
return (JSON.stringify(this._model.migrationStatus, undefined, 2));
|
||||
}
|
||||
return JSON.stringify(this._model.migrationStatus, undefined, 2);
|
||||
}
|
||||
|
||||
private async refreshStatus(): Promise<void> {
|
||||
@@ -598,18 +568,13 @@ export class MigrationCutoverDialog {
|
||||
try {
|
||||
clearDialogMessage(this._dialogObject);
|
||||
|
||||
if (this._isOnlineMigration()) {
|
||||
await this._cutoverButton.updateCssStyles({
|
||||
'display': 'block'
|
||||
});
|
||||
}
|
||||
await this._cutoverButton.updateCssStyles(
|
||||
{ 'display': isOfflineMigation(this._model._migration) ? 'none' : 'block' });
|
||||
|
||||
this.isRefreshing = true;
|
||||
this._refreshLoader.loading = true;
|
||||
await this._model.fetchStatus();
|
||||
const errors = [];
|
||||
errors.push(this._model.migrationOpStatus.error?.message);
|
||||
errors.push(this._model._migration.asyncOperationResult?.error?.message);
|
||||
errors.push(this._model.migrationStatus.properties.provisioningError);
|
||||
errors.push(this._model.migrationStatus.properties.migrationFailureError?.message);
|
||||
errors.push(this._model.migrationStatus.properties.migrationStatusDetails?.fileUploadBlockingErrors ?? []);
|
||||
@@ -626,12 +591,12 @@ export class MigrationCutoverDialog {
|
||||
description: this.getMigrationDetails()
|
||||
};
|
||||
const sqlServerInfo = await azdata.connection.getServerInfo((await azdata.connection.getCurrentConnection()).connectionId);
|
||||
const sqlServerName = this._model._migration.sourceConnectionProfile.serverName;
|
||||
const sourceDatabaseName = this._model._migration.migrationContext.properties.sourceDatabaseName;
|
||||
const sqlServerName = this._model._migration.properties.sourceServerName;
|
||||
const sourceDatabaseName = this._model._migration.properties.sourceDatabaseName;
|
||||
const versionName = getSqlServerName(sqlServerInfo.serverMajorVersion!);
|
||||
const sqlServerVersion = versionName ? versionName : sqlServerInfo.serverVersion;
|
||||
const targetDatabaseName = this._model._migration.migrationContext.name;
|
||||
const targetServerName = this._model._migration.targetManagedInstance.name;
|
||||
const targetDatabaseName = this._model._migration.name;
|
||||
const targetServerName = getResourceName(this._model._migration.id);
|
||||
let targetServerVersion;
|
||||
if (this._model.migrationStatus.id.includes('managedInstances')) {
|
||||
targetServerVersion = loc.AZURE_SQL_DATABASE_MANAGED_INSTANCE;
|
||||
@@ -644,30 +609,30 @@ export class MigrationCutoverDialog {
|
||||
|
||||
const tableData: ActiveBackupFileSchema[] = [];
|
||||
|
||||
this._model.migrationStatus.properties.migrationStatusDetails?.activeBackupSets?.forEach((activeBackupSet) => {
|
||||
this._model.migrationStatus.properties.migrationStatusDetails?.activeBackupSets?.forEach(
|
||||
(activeBackupSet) => {
|
||||
if (this._shouldDisplayBackupFileTable()) {
|
||||
tableData.push(
|
||||
...activeBackupSet.listOfBackupFiles.map(f => {
|
||||
return {
|
||||
fileName: f.fileName,
|
||||
type: activeBackupSet.backupType,
|
||||
status: f.status,
|
||||
dataUploaded: `${convertByteSizeToReadableUnit(f.dataWritten)} / ${convertByteSizeToReadableUnit(f.totalSize)}`,
|
||||
copyThroughput: (f.copyThroughput) ? (f.copyThroughput / 1024).toFixed(2) : '-',
|
||||
backupStartTime: activeBackupSet.backupStartDate,
|
||||
firstLSN: activeBackupSet.firstLSN,
|
||||
lastLSN: activeBackupSet.lastLSN
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (this._shouldDisplayBackupFileTable()) {
|
||||
tableData.push(
|
||||
...activeBackupSet.listOfBackupFiles.map(f => {
|
||||
return {
|
||||
fileName: f.fileName,
|
||||
type: activeBackupSet.backupType,
|
||||
status: f.status,
|
||||
dataUploaded: `${convertByteSizeToReadableUnit(f.dataWritten)} / ${convertByteSizeToReadableUnit(f.totalSize)}`,
|
||||
copyThroughput: (f.copyThroughput) ? (f.copyThroughput / 1024).toFixed(2) : '-',
|
||||
backupStartTime: activeBackupSet.backupStartDate,
|
||||
firstLSN: activeBackupSet.firstLSN,
|
||||
lastLSN: activeBackupSet.lastLSN
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (activeBackupSet.listOfBackupFiles[0].fileName === this._model.migrationStatus.properties.migrationStatusDetails?.lastRestoredFilename) {
|
||||
lastAppliedSSN = activeBackupSet.lastLSN;
|
||||
lastAppliedBackupFileTakenOn = activeBackupSet.backupFinishDate;
|
||||
}
|
||||
});
|
||||
if (activeBackupSet.listOfBackupFiles[0].fileName === this._model.migrationStatus.properties.migrationStatusDetails?.lastRestoredFilename) {
|
||||
lastAppliedSSN = activeBackupSet.lastLSN;
|
||||
lastAppliedBackupFileTakenOn = activeBackupSet.backupFinishDate;
|
||||
}
|
||||
});
|
||||
|
||||
this._sourceDatabaseInfoField.text.value = sourceDatabaseName;
|
||||
this._sourceDetailsInfoField.text.value = sqlServerName;
|
||||
@@ -677,21 +642,23 @@ export class MigrationCutoverDialog {
|
||||
this._targetServerInfoField.text.value = targetServerName;
|
||||
this._targetVersionInfoField.text.value = targetServerVersion;
|
||||
|
||||
const migrationStatusTextValue = this.getMigrationStatus();
|
||||
const migrationStatusTextValue = this._getMigrationStatus();
|
||||
this._migrationStatusInfoField.text.value = migrationStatusTextValue ?? '-';
|
||||
this._migrationStatusInfoField.icon!.iconPath = getMigrationStatusImage(migrationStatusTextValue);
|
||||
|
||||
this._fullBackupFileOnInfoField.text.value = this._model.migrationStatus?.properties?.migrationStatusDetails?.fullBackupSetInfo?.listOfBackupFiles[0]?.fileName! ?? '-';
|
||||
|
||||
let backupLocation;
|
||||
const isBlobMigration = this._model.isBlobMigration();
|
||||
const _isBlobMigration = isBlobMigration(this._model._migration);
|
||||
// Displaying storage accounts and blob container for azure blob backups.
|
||||
if (isBlobMigration) {
|
||||
const storageAccountResourceId = this._model._migration.migrationContext.properties.backupConfiguration?.sourceLocation?.azureBlob?.storageAccountResourceId;
|
||||
const blobContainerName = this._model._migration.migrationContext.properties.backupConfiguration?.sourceLocation?.azureBlob?.blobContainerName;
|
||||
backupLocation = `${storageAccountResourceId?.split('/').pop()} - ${blobContainerName}`;
|
||||
if (_isBlobMigration) {
|
||||
const storageAccountResourceId = this._model._migration.properties.backupConfiguration?.sourceLocation?.azureBlob?.storageAccountResourceId;
|
||||
const blobContainerName = this._model._migration.properties.backupConfiguration?.sourceLocation?.azureBlob?.blobContainerName;
|
||||
backupLocation = storageAccountResourceId && blobContainerName
|
||||
? `${storageAccountResourceId?.split('/').pop()} - ${blobContainerName}`
|
||||
: undefined;
|
||||
} else {
|
||||
const fileShare = this._model._migration.migrationContext.properties.backupConfiguration?.sourceLocation?.fileShare;
|
||||
const fileShare = this._model._migration.properties.backupConfiguration?.sourceLocation?.fileShare;
|
||||
backupLocation = fileShare?.path! ?? '-';
|
||||
}
|
||||
this._backupLocationInfoField.text.value = backupLocation ?? '-';
|
||||
@@ -700,7 +667,7 @@ export class MigrationCutoverDialog {
|
||||
this._lastAppliedBackupInfoField.text.value = this._model.migrationStatus.properties.migrationStatusDetails?.lastRestoredFilename ?? '-';
|
||||
this._lastAppliedBackupTakenOnInfoField.text.value = lastAppliedBackupFileTakenOn! ? convertIsoTimeToLocalTime(lastAppliedBackupFileTakenOn).toLocaleString() : '-';
|
||||
|
||||
if (isBlobMigration) {
|
||||
if (_isBlobMigration) {
|
||||
if (!this._model.migrationStatus.properties.migrationStatusDetails?.currentRestoringFilename) {
|
||||
this._currentRestoringFileInfoField.text.value = '-';
|
||||
} else if (this._model.migrationStatus.properties.migrationStatusDetails?.lastRestoredFilename === this._model.migrationStatus.properties.migrationStatusDetails?.currentRestoringFilename) {
|
||||
@@ -752,7 +719,7 @@ export class MigrationCutoverDialog {
|
||||
|
||||
this._cutoverButton.enabled = false;
|
||||
if (migrationStatusTextValue === MigrationStatus.InProgress) {
|
||||
if (isBlobMigration) {
|
||||
if (_isBlobMigration) {
|
||||
if (this._model.migrationStatus.properties.migrationStatusDetails?.lastRestoredFilename) {
|
||||
this._cutoverButton.enabled = true;
|
||||
}
|
||||
@@ -856,21 +823,14 @@ export class MigrationCutoverDialog {
|
||||
};
|
||||
}
|
||||
|
||||
private _isOnlineMigration(): boolean {
|
||||
return this._model._migration.migrationContext.properties.offlineConfiguration?.offline?.valueOf() ? false : true;
|
||||
}
|
||||
|
||||
private _shouldDisplayBackupFileTable(): boolean {
|
||||
return !this._model.isBlobMigration();
|
||||
return !isBlobMigration(this._model._migration);
|
||||
}
|
||||
|
||||
private getMigrationStatus(): string {
|
||||
if (this._model.migrationStatus) {
|
||||
return this._model.migrationStatus.properties.migrationStatus
|
||||
?? this._model.migrationStatus.properties.provisioningState;
|
||||
}
|
||||
return this._model._migration.migrationContext.properties.migrationStatus
|
||||
?? this._model._migration.migrationContext.properties.provisioningState;
|
||||
private _getMigrationStatus(): string {
|
||||
return this._model.migrationStatus
|
||||
? getMigrationStatus(this._model.migrationStatus)
|
||||
: getMigrationStatus(this._model._migration);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,43 +3,36 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { getMigrationStatus, DatabaseMigration, startMigrationCutover, stopMigration, getMigrationAsyncOperationDetails, AzureAsyncOperationResource, BackupFileInfo, getResourceGroupFromId } from '../../api/azure';
|
||||
import { BackupFileInfoStatus, MigrationContext } from '../../models/migrationLocalStorage';
|
||||
import { DatabaseMigration, startMigrationCutover, stopMigration, BackupFileInfo, getResourceGroupFromId, getMigrationDetails, getMigrationTargetName } from '../../api/azure';
|
||||
import { BackupFileInfoStatus, MigrationServiceContext } from '../../models/migrationLocalStorage';
|
||||
import { logError, sendSqlMigrationActionEvent, TelemetryAction, TelemetryViews } from '../../telemtery';
|
||||
import * as constants from '../../constants/strings';
|
||||
import { EOL } from 'os';
|
||||
import { getMigrationTargetType, getMigrationMode } from '../../constants/helper';
|
||||
import { getMigrationTargetType, getMigrationMode, isBlobMigration } from '../../constants/helper';
|
||||
|
||||
export class MigrationCutoverDialogModel {
|
||||
public CutoverError?: Error;
|
||||
public CancelMigrationError?: Error;
|
||||
|
||||
public migrationStatus!: DatabaseMigration;
|
||||
public migrationOpStatus!: AzureAsyncOperationResource;
|
||||
|
||||
constructor(public _migration: MigrationContext) {
|
||||
|
||||
constructor(
|
||||
public _serviceConstext: MigrationServiceContext,
|
||||
public _migration: DatabaseMigration
|
||||
) {
|
||||
}
|
||||
|
||||
public async fetchStatus(): Promise<void> {
|
||||
if (this._migration.asyncUrl) {
|
||||
this.migrationOpStatus = await getMigrationAsyncOperationDetails(
|
||||
this._migration.azureAccount,
|
||||
this._migration.subscription,
|
||||
this._migration.asyncUrl,
|
||||
this._migration.sessionId!);
|
||||
}
|
||||
|
||||
this.migrationStatus = await getMigrationStatus(
|
||||
this._migration.azureAccount,
|
||||
this._migration.subscription,
|
||||
this._migration.migrationContext,
|
||||
this._migration.sessionId!);
|
||||
this.migrationStatus = await getMigrationDetails(
|
||||
this._serviceConstext.azureAccount!,
|
||||
this._serviceConstext.subscription!,
|
||||
this._migration.id,
|
||||
this._migration.properties?.migrationOperationId);
|
||||
|
||||
sendSqlMigrationActionEvent(
|
||||
TelemetryViews.MigrationCutoverDialog,
|
||||
TelemetryAction.MigrationStatus,
|
||||
{
|
||||
'sessionId': this._migration.sessionId!,
|
||||
'migrationStatus': this.migrationStatus.properties?.migrationStatus
|
||||
},
|
||||
{}
|
||||
@@ -51,18 +44,16 @@ export class MigrationCutoverDialogModel {
|
||||
public async startCutover(): Promise<DatabaseMigration | undefined> {
|
||||
try {
|
||||
this.CutoverError = undefined;
|
||||
if (this.migrationStatus) {
|
||||
if (this._migration) {
|
||||
const cutover = await startMigrationCutover(
|
||||
this._migration.azureAccount,
|
||||
this._migration.subscription,
|
||||
this.migrationStatus,
|
||||
this._migration.sessionId!
|
||||
);
|
||||
this._serviceConstext.azureAccount!,
|
||||
this._serviceConstext.subscription!,
|
||||
this._migration!);
|
||||
sendSqlMigrationActionEvent(
|
||||
TelemetryViews.MigrationCutoverDialog,
|
||||
TelemetryAction.CutoverMigration,
|
||||
{
|
||||
...this.getTelemetryProps(this._migration),
|
||||
...this.getTelemetryProps(this._serviceConstext, this._migration),
|
||||
'migrationEndTime': new Date().toString(),
|
||||
},
|
||||
{}
|
||||
@@ -79,8 +70,6 @@ export class MigrationCutoverDialogModel {
|
||||
public async fetchErrors(): Promise<string> {
|
||||
const errors = [];
|
||||
await this.fetchStatus();
|
||||
errors.push(this.migrationOpStatus.error?.message);
|
||||
errors.push(this._migration.asyncOperationResult?.error?.message);
|
||||
errors.push(this.migrationStatus.properties.migrationFailureError?.message);
|
||||
return errors
|
||||
.filter((e, i, arr) => e !== undefined && i === arr.indexOf(e))
|
||||
@@ -93,18 +82,16 @@ export class MigrationCutoverDialogModel {
|
||||
if (this.migrationStatus) {
|
||||
const cutoverStartTime = new Date().toString();
|
||||
await stopMigration(
|
||||
this._migration.azureAccount,
|
||||
this._migration.subscription,
|
||||
this.migrationStatus,
|
||||
this._migration.sessionId!
|
||||
);
|
||||
this._serviceConstext.azureAccount!,
|
||||
this._serviceConstext.subscription!,
|
||||
this.migrationStatus);
|
||||
sendSqlMigrationActionEvent(
|
||||
TelemetryViews.MigrationCutoverDialog,
|
||||
TelemetryAction.CancelMigration,
|
||||
{
|
||||
...this.getTelemetryProps(this._migration),
|
||||
...this.getTelemetryProps(this._serviceConstext, this._migration),
|
||||
'migrationMode': getMigrationMode(this._migration),
|
||||
'cutoverStartTime': cutoverStartTime
|
||||
'cutoverStartTime': cutoverStartTime,
|
||||
},
|
||||
{}
|
||||
);
|
||||
@@ -116,12 +103,8 @@ export class MigrationCutoverDialogModel {
|
||||
return undefined!;
|
||||
}
|
||||
|
||||
public isBlobMigration(): boolean {
|
||||
return this._migration.migrationContext.properties.backupConfiguration?.sourceLocation?.azureBlob !== undefined;
|
||||
}
|
||||
|
||||
public confirmCutoverStepsString(): string {
|
||||
if (this.isBlobMigration()) {
|
||||
if (isBlobMigration(this.migrationStatus)) {
|
||||
return `${constants.CUTOVER_HELP_STEP1}
|
||||
${constants.CUTOVER_HELP_STEP2_BLOB_CONTAINER}
|
||||
${constants.CUTOVER_HELP_STEP3_BLOB_CONTAINER}`;
|
||||
@@ -152,16 +135,15 @@ export class MigrationCutoverDialogModel {
|
||||
return files;
|
||||
}
|
||||
|
||||
private getTelemetryProps(migration: MigrationContext) {
|
||||
private getTelemetryProps(serviceContext: MigrationServiceContext, migration: DatabaseMigration) {
|
||||
return {
|
||||
'sessionId': migration.sessionId!,
|
||||
'subscriptionId': migration.subscription.id,
|
||||
'resourceGroup': getResourceGroupFromId(migration.targetManagedInstance.id),
|
||||
'sqlServerName': migration.sourceConnectionProfile.serverName,
|
||||
'sourceDatabaseName': migration.migrationContext.properties.sourceDatabaseName,
|
||||
'subscriptionId': serviceContext.subscription!.id,
|
||||
'resourceGroup': getResourceGroupFromId(migration.id),
|
||||
'sqlServerName': migration.properties.sourceServerName,
|
||||
'sourceDatabaseName': migration.properties.sourceDatabaseName,
|
||||
'targetType': getMigrationTargetType(migration),
|
||||
'targetDatabaseName': migration.migrationContext.name,
|
||||
'targetServerName': migration.targetManagedInstance.name,
|
||||
'targetDatabaseName': migration.name,
|
||||
'targetServerName': getMigrationTargetName(migration),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user