mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-14 01:25:37 -05:00
SQL-Migration: add new migration monitoring data to migration details (#22460)
* add new migration details * move migraiton target type enum to utils * address review feedback, refectore, text update * fix variable name * limit and filter migrations list to mi/vm/db
This commit is contained in:
@@ -911,6 +911,7 @@ export enum AzureResourceKind {
|
||||
SQLDB = 'SqlDb',
|
||||
SQLMI = 'SqlMi',
|
||||
SQLVM = 'SqlVm',
|
||||
ORACLETOSQLDB = "OracleToSqlDb",
|
||||
}
|
||||
|
||||
export interface ValidateIrSqlDatabaseMigrationRequest {
|
||||
@@ -1008,6 +1009,7 @@ export interface DatabaseMigration {
|
||||
|
||||
export interface DatabaseMigrationProperties {
|
||||
scope: string;
|
||||
kind: string;
|
||||
provisioningState: 'Succeeded' | 'Failed' | 'Creating';
|
||||
provisioningError: string;
|
||||
migrationStatus: 'Canceled' | 'Canceling' | 'Completing' | 'Creating' | 'Failed' | 'InProgress' | 'ReadyForCutover' | 'Restoring' | 'Retriable' | 'Succeeded' | 'UploadingFullBackup' | 'UploadingLogBackup';
|
||||
@@ -1043,6 +1045,22 @@ export interface MigrationStatusDetails {
|
||||
invalidFiles: string[];
|
||||
listOfCopyProgressDetails: CopyProgressDetail[];
|
||||
sqlDataCopyErrors: string[];
|
||||
|
||||
// new fields
|
||||
pendingDiffBackupsCount: number;
|
||||
restorePercentCompleted: number;
|
||||
currentRestoredSize: number;
|
||||
currentRestorePlanSize: number;
|
||||
lastUploadedFileName: string;
|
||||
lastUploadedFileTime: string;
|
||||
lastRestoredFileTime: string;
|
||||
miRestoreState: "None" | "Initializing" | "NotStarted" | "SearchingBackups" | "Restoring" | "RestorePaused" | "RestoreCompleted" | "Waiting" | "CompletingMigration" | "Cancelled" | "Failed" | "Completed" | "Blocked";
|
||||
detectedFiles: number;
|
||||
queuedFiles: number;
|
||||
skippedFiles: number;
|
||||
restoringFiles: number;
|
||||
restoredFiles: number;
|
||||
unrestorableFiles: number;
|
||||
}
|
||||
|
||||
export interface MigrationStatusWarnings {
|
||||
@@ -1053,7 +1071,7 @@ export interface MigrationStatusWarnings {
|
||||
|
||||
export interface CopyProgressDetail {
|
||||
tableName: string;
|
||||
status: 'PreparingForCopy' | 'Copying' | 'CopyFinished' | 'RebuildingIndexes' | 'Succeeded' | 'Failed' | 'Canceled',
|
||||
status: 'PreparingForCopy' | 'Copying' | 'CopyFinished' | 'RebuildingIndexes' | 'Succeeded' | 'Failed' | 'Canceled';
|
||||
parallelCopyType: string;
|
||||
usedParallelCopies: number;
|
||||
dataRead: number;
|
||||
@@ -1061,7 +1079,7 @@ export interface CopyProgressDetail {
|
||||
rowsRead: number;
|
||||
rowsCopied: number;
|
||||
copyStart: string;
|
||||
copyThroughput: number,
|
||||
copyThroughput: number;
|
||||
copyDuration: number;
|
||||
errors: string[];
|
||||
}
|
||||
@@ -1083,22 +1101,30 @@ export interface ErrorInfo {
|
||||
|
||||
export interface BackupSetInfo {
|
||||
backupSetId: string;
|
||||
firstLSN: string;
|
||||
lastLSN: string;
|
||||
backupType: string;
|
||||
firstLSN: string; // SHIR scenario only
|
||||
lastLSN: string; // SHIR scenario only
|
||||
backupType: "Unknown" | "Database" | "TransactionLog" | "File" | "DifferentialDatabase" | "DifferentialFile" | "Partial" | "DifferentialPartial";
|
||||
listOfBackupFiles: BackupFileInfo[];
|
||||
backupStartDate: string;
|
||||
backupFinishDate: string;
|
||||
backupStartDate: string; // SHIR scenario only
|
||||
backupFinishDate: string; // SHIR scenario only
|
||||
isBackupRestored: boolean;
|
||||
backupSize: number;
|
||||
compressedBackupSize: number;
|
||||
hasBackupChecksums: boolean;
|
||||
familyCount: number;
|
||||
|
||||
// new fields
|
||||
restoreStartDate: string;
|
||||
restoreFinishDate: string;
|
||||
restoreStatus: "None" | "Skipped" | "Queued" | "Restoring" | "Restored";
|
||||
backupSizeMB: number;
|
||||
numberOfStripes: number;
|
||||
}
|
||||
|
||||
export interface SourceLocation {
|
||||
fileShare?: DatabaseMigrationFileShare;
|
||||
azureBlob?: DatabaseMigrationAzureBlob;
|
||||
testConnectivity?: boolean;
|
||||
fileStorageType: 'FileShare' | 'AzureBlob' | 'None';
|
||||
}
|
||||
|
||||
@@ -1109,6 +1135,7 @@ export interface TargetLocation {
|
||||
|
||||
export interface BackupFileInfo {
|
||||
fileName: string;
|
||||
// fields below are only returned by SHIR scenarios
|
||||
status: 'Arrived' | 'Uploading' | 'Uploaded' | 'Restoring' | 'Restored' | 'Canceled' | 'Ignored';
|
||||
totalSize: number;
|
||||
dataRead: number;
|
||||
|
||||
@@ -37,6 +37,12 @@ export const MenuCommands = {
|
||||
SendFeedback: 'sqlmigration.sendfeedback',
|
||||
};
|
||||
|
||||
export enum MigrationTargetType {
|
||||
SQLVM = 'AzureSqlVirtualMachine',
|
||||
SQLMI = 'AzureSqlManagedInstance',
|
||||
SQLDB = 'AzureSqlDatabase'
|
||||
}
|
||||
|
||||
export function deepClone<T>(obj: T): T {
|
||||
if (!obj || typeof obj !== 'object') {
|
||||
return obj;
|
||||
@@ -152,7 +158,16 @@ export function getMigrationDuration(startDate: string, endDate: string): string
|
||||
}
|
||||
|
||||
export function filterMigrations(databaseMigrations: azure.DatabaseMigration[], statusFilter: string, columnTextFilter?: string): azure.DatabaseMigration[] {
|
||||
let filteredMigration: azure.DatabaseMigration[] = databaseMigrations || [];
|
||||
const supportedKind: string[] = [
|
||||
azure.AzureResourceKind.SQLDB,
|
||||
azure.AzureResourceKind.SQLMI,
|
||||
azure.AzureResourceKind.SQLVM,
|
||||
];
|
||||
|
||||
let filteredMigration: azure.DatabaseMigration[] =
|
||||
databaseMigrations.filter(m => supportedKind.includes(m.properties?.kind)) ||
|
||||
[];
|
||||
|
||||
if (columnTextFilter) {
|
||||
const filter = columnTextFilter.toLowerCase();
|
||||
filteredMigration = filteredMigration.filter(
|
||||
@@ -196,6 +211,7 @@ export function filterMigrations(databaseMigrations: azure.DatabaseMigration[],
|
||||
return filteredMigration.filter(
|
||||
value => getMigrationStatus(value) === constants.MigrationState.Completing);
|
||||
}
|
||||
|
||||
return filteredMigration;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import { DatabaseMigration } from '../api/azure';
|
||||
import { DefaultSettingValue } from '../api/utils';
|
||||
import { FileStorageType, MigrationMode, MigrationTargetType } from '../models/stateMachine';
|
||||
import { AzureResourceKind, DatabaseMigration } from '../api/azure';
|
||||
import { DefaultSettingValue, MigrationTargetType } from '../api/utils';
|
||||
import { FileStorageType, MigrationMode } from '../models/stateMachine';
|
||||
import * as loc from './strings';
|
||||
|
||||
export enum SQLTargetAssetType {
|
||||
@@ -15,6 +15,48 @@ export enum SQLTargetAssetType {
|
||||
SQLDB = 'Microsoft.Sql/servers',
|
||||
}
|
||||
|
||||
export const FileStorageTypeCodes = {
|
||||
FileShare: "FileShare",
|
||||
AzureBlob: "AzureBlob",
|
||||
None: "None",
|
||||
};
|
||||
|
||||
export const BackupTypeCodes = {
|
||||
// Type of backup. The values match the output of a RESTORE HEADERONLY query.
|
||||
Unknown: "Unknown",
|
||||
Database: "Database",
|
||||
TransactionLog: "TransactionLog",
|
||||
File: "File",
|
||||
DifferentialDatabase: "DifferentialDatabase",
|
||||
DifferentialFile: "DifferentialFile",
|
||||
Partial: "Partial",
|
||||
DifferentialPartial: "DifferentialPartial",
|
||||
};
|
||||
|
||||
export const InternalManagedDatabaseRestoreDetailsBackupSetStatusCodes = {
|
||||
None: "None",
|
||||
Skipped: "Skipped",
|
||||
Queued: "Queued",
|
||||
Restoring: "Restoring",
|
||||
Restored: "Restored",
|
||||
};
|
||||
|
||||
export const InternalManagedDatabaseRestoreDetailsStatusCodes = {
|
||||
None: "None", // Something went wrong most likely.
|
||||
Initializing: "Initializing", // Restore is initializing.
|
||||
NotStarted: "NotStarted", // Restore not started
|
||||
SearchingBackups: "SearchingBackups", // Searching for backups
|
||||
Restoring: "Restoring", // Restore is in progress
|
||||
RestorePaused: "RestorePaused", // Restore is paused
|
||||
RestoreCompleted: "RestoreCompleted", // Restore completed for all found log, but there may have more logs coming
|
||||
Waiting: "Waiting", // Waiting for new files to be uploaded or for Complete restore signal.
|
||||
CompletingMigration: "CompletingMigration", // Completing migration
|
||||
Cancelled: "Cancelled", // Restore cancelled.
|
||||
Failed: "Failed", // Restore failed.
|
||||
Completed: "Completed", // Database is restored and recovery is complete.
|
||||
Blocked: "Blocked", // Restore is temporarily blocked: "", awaiting for user to mitigate the issue.
|
||||
};
|
||||
|
||||
export const ParallelCopyTypeCodes = {
|
||||
None: 'None',
|
||||
DynamicRange: 'DynamicRange',
|
||||
@@ -78,7 +120,9 @@ export function formatTime(miliseconds: number): string {
|
||||
if (miliseconds > 0) {
|
||||
// hh:mm:ss
|
||||
const matches = (new Date(miliseconds))?.toUTCString()?.match(/(\d\d:\d\d:\d\d)/) || [];
|
||||
return matches?.length > 0 ? matches[0] : '';
|
||||
return matches?.length > 0
|
||||
? matches[0] ?? ''
|
||||
: '';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
@@ -118,12 +162,12 @@ export function getMigrationTargetType(migration: DatabaseMigration | undefined)
|
||||
}
|
||||
|
||||
export function getMigrationTargetTypeEnum(migration: DatabaseMigration | undefined): MigrationTargetType | undefined {
|
||||
switch (migration?.type) {
|
||||
case SQLTargetAssetType.SQLMI:
|
||||
switch (migration?.properties?.kind) {
|
||||
case AzureResourceKind.SQLMI:
|
||||
return MigrationTargetType.SQLMI;
|
||||
case SQLTargetAssetType.SQLVM:
|
||||
case AzureResourceKind.SQLVM:
|
||||
return MigrationTargetType.SQLVM;
|
||||
case SQLTargetAssetType.SQLDB:
|
||||
case AzureResourceKind.SQLDB:
|
||||
return MigrationTargetType.SQLDB;
|
||||
default:
|
||||
return undefined;
|
||||
@@ -150,6 +194,24 @@ export function isBlobMigration(migration: DatabaseMigration | undefined): boole
|
||||
return migration?.properties?.backupConfiguration?.sourceLocation?.fileStorageType === FileStorageType.AzureBlob;
|
||||
}
|
||||
|
||||
export function isShirMigration(migration?: DatabaseMigration): boolean {
|
||||
return isLogicalMigration(migration)
|
||||
|| isFileShareMigration(migration);
|
||||
}
|
||||
|
||||
export function isLogicalMigration(migration?: DatabaseMigration): boolean {
|
||||
return migration?.properties?.kind === AzureResourceKind.ORACLETOSQLDB
|
||||
|| migration?.properties?.kind === AzureResourceKind.SQLDB;
|
||||
}
|
||||
|
||||
export function isFileShareMigration(migration?: DatabaseMigration): boolean {
|
||||
return migration?.properties?.backupConfiguration?.sourceLocation?.fileStorageType === FileStorageTypeCodes.FileShare;
|
||||
}
|
||||
|
||||
export function isTargetType(migration?: DatabaseMigration, kind?: string): boolean {
|
||||
return migration?.properties?.kind === kind;
|
||||
}
|
||||
|
||||
export function getMigrationStatus(migration: DatabaseMigration | undefined): string | undefined {
|
||||
return migration?.properties.migrationStatus
|
||||
?? migration?.properties.provisioningState;
|
||||
@@ -167,6 +229,16 @@ export function hasMigrationOperationId(migration: DatabaseMigration | undefined
|
||||
&& migationOperationId.length > 0;
|
||||
}
|
||||
|
||||
export function getMigrationBackupLocation(migration: DatabaseMigration): string | undefined {
|
||||
return migration?.properties?.backupConfiguration?.sourceLocation?.fileShare?.path
|
||||
?? migration?.properties?.backupConfiguration?.sourceLocation?.azureBlob?.blobContainerName
|
||||
?? migration?.properties?.migrationStatusDetails?.blobContainerName;
|
||||
}
|
||||
|
||||
export function getMigrationFullBackupFiles(migration: DatabaseMigration): string | undefined {
|
||||
return migration?.properties?.migrationStatusDetails?.fullBackupSetInfo?.listOfBackupFiles?.map(file => file.fileName).join(',');
|
||||
}
|
||||
|
||||
export function hasRestoreBlockingReason(migration: DatabaseMigration | undefined): boolean {
|
||||
return (migration?.properties.migrationStatusWarnings?.restoreBlockingReason ?? '').length > 0;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { AzureAccount } from 'azurecore';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { EOL } from 'os';
|
||||
import { MigrationSourceAuthenticationType } from '../models/stateMachine';
|
||||
import { formatNumber, ParallelCopyTypeCodes, PipelineStatusCodes } from './helper';
|
||||
import { BackupTypeCodes, formatNumber, InternalManagedDatabaseRestoreDetailsBackupSetStatusCodes, InternalManagedDatabaseRestoreDetailsStatusCodes, ParallelCopyTypeCodes, PipelineStatusCodes } from './helper';
|
||||
import { ValidationError } from '../api/azure';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
@@ -982,10 +982,10 @@ export const TARGET_SERVER = localize('sql.migration.target.server', "Target ser
|
||||
export const TARGET_VERSION = localize('sql.migration.target.version', "Target version");
|
||||
export const MIGRATION_STATUS = localize('sql.migration.migration.status', "Migration status");
|
||||
export const MIGRATION_STATUS_FILTER = localize('sql.migration.migration.status.filter', "Migration status filter");
|
||||
export const FULL_BACKUP_FILES = localize('sql.migration.full.backup.files', "Full backup files");
|
||||
export const FULL_BACKUP_FILES = localize('sql.migration.full.backup.files', "Full backup file(s)");
|
||||
export const LAST_APPLIED_LSN = localize('sql.migration.last.applied.lsn', "Last applied LSN");
|
||||
export const LAST_APPLIED_BACKUP_FILES = localize('sql.migration.last.applied.backup.files', "Last applied backup files");
|
||||
export const LAST_APPLIED_BACKUP_FILES_TAKEN_ON = localize('sql.migration.last.applied.files.taken.on', "Last applied backup files taken on");
|
||||
export const LAST_APPLIED_BACKUP_FILES = localize('sql.migration.last.applied.backup.files', "Last applied backup file(s)");
|
||||
export const LAST_APPLIED_BACKUP_FILES_TAKEN_ON = localize('sql.migration.last.applied.files.taken.on', "Last applied backup taken on");
|
||||
export const CURRENTLY_RESTORING_FILE = localize('sql.migration.currently.restoring.file', "Currently restoring file");
|
||||
export const ALL_BACKUPS_RESTORED = localize('sql.migration.all.backups.restored', "All backups restored");
|
||||
export const ACTIVE_BACKUP_FILES = localize('sql.migration.active.backup.files', "Active backup files");
|
||||
@@ -993,6 +993,29 @@ export const MIGRATION_STATUS_REFRESH_ERROR = localize('sql.migration.cutover.st
|
||||
export const MIGRATION_CANCELLATION_ERROR = localize('sql.migration.cancel.error', 'An error occurred while canceling the migration.');
|
||||
export const MIGRATION_DELETE_ERROR = localize('sql.migration.delete.error', 'An error occurred while deleting the migration.');
|
||||
|
||||
export const FIELD_LABEL_LAST_UPLOADED_FILE = localize('sql.migration.field.label.last.uploaded.file', 'Last uploaded file');
|
||||
export const FIELD_LABEL_LAST_UPLOADED_FILE_TIME = localize('sql.migration.field.label.last.uloaded.file.time', 'Last uploaded file time');
|
||||
export const FIELD_LABEL_PENDING_DIFF_BACKUPS = localize('sql.migration.field.label.pending.differential.backups', 'Pending differential backups');
|
||||
export const FIELD_LABEL_DETECTED_FILES = localize('sql.migration.field.label.deteected.files', 'Detected files');
|
||||
export const FIELD_LABEL_QUEUED_FILES = localize('sql.migration.field.label.queued.files', 'Queued files');
|
||||
export const FIELD_LABEL_SKIPPED_FILES = localize('sql.migration.field.label.skipped.files', 'Skipped files');
|
||||
export const FIELD_LABEL_UNRESTORABLE_FILES = localize('sql.migration.field.label.unrestorable.files', 'Unrestorable files');
|
||||
export const FIELD_LABEL_LAST_RESTORED_FILE_TIME = localize('sql.migration.field.label.last.restored.file.time', 'Last restored file time');
|
||||
export const FIELD_LABEL_RESTORED_FILES = localize('sql.migration.field.label.restored.files', 'Restored files');
|
||||
export const FIELD_LABEL_RESTORING_FILES = localize('sql.migration.field.label.restoring.files', 'Restoring files');
|
||||
export const FIELD_LABEL_RESTORED_SIZE = localize('sql.migration.field.label.restored.size', 'Restored size (MB)');
|
||||
export const FIELD_LABEL_RESTORE_PLAN_SIZE = localize('sql.migration.field.label.restore.plan.size', 'Restore plan size (MB)');
|
||||
export const FIELD_LABEL_RESTORE_PERCENT_COMPLETED = localize('sql.migration.field.label.restore.percent.completed', 'Restore percent completed');
|
||||
export const FIELD_LABEL_MI_RESTORE_STATE = localize('sql.migration.field.label.mi.restore.state', 'Managed instance restore state');
|
||||
|
||||
export const BACKUP_FILE_COLUMN_FILE_NAME = localize('sql.migration.backup.file.name', 'File name');
|
||||
export const BACKUP_FILE_COLUMN_FILE_STATUS = localize('sql.migration.backup.file.status', 'File status');
|
||||
export const BACKUP_FILE_COLUMN_RESTORE_STATUS = localize('sql.migration.backup.file.restore.status', 'Restore status');
|
||||
export const BACKUP_FILE_COLUMN_BACKUP_SIZE_MB = localize('sql.migration.backup.file.backup.size', 'Backup size (MB)');
|
||||
export const BACKUP_FILE_COLUMN_NUMBER_OF_STRIPES = localize('sql.migration.backup.file.number.of.stripes', 'Number of stripes');
|
||||
export const BACKUP_FILE_COLUMN_RESTORE_START_DATE = localize('sql.migration.backup.file.restore.start.date', 'Restore start date');
|
||||
export const BACKUP_FILE_COLUMN_RESTORE_FINISH_DATE = localize('sql.migration.backup.file.restore.finish.date', 'Restore finish date');
|
||||
|
||||
export const STATUS = localize('sql.migration.status', "Status");
|
||||
export const BACKUP_START_TIME = localize('sql.migration.backup.start.time', "Backup start time");
|
||||
export const FIRST_LSN = localize('sql.migration.first.lsn', "First LSN");
|
||||
@@ -1149,6 +1172,41 @@ export const ParallelCopyType: LookupTable<string | undefined> = {
|
||||
[ParallelCopyTypeCodes.DynamicRange]: localize('sql.migration.parallel.copy.type.dynamic', 'Dynamic range'),
|
||||
};
|
||||
|
||||
export const BackupTypeLookup: LookupTable<string | undefined> = {
|
||||
[BackupTypeCodes.Unknown]: localize('sql.migration.restore.backuptype.unknown', 'Unknown'),
|
||||
[BackupTypeCodes.Database]: localize('sql.migration.restore.backuptype.database', 'Database'),
|
||||
[BackupTypeCodes.TransactionLog]: localize('sql.migration.restore.backuptype.transactionlog', 'Transaction log'),
|
||||
[BackupTypeCodes.File]: localize('sql.migration.restore.backuptype.file', 'File'),
|
||||
[BackupTypeCodes.DifferentialDatabase]: localize('sql.migration.restore.backuptype.differentialdatabase', 'Differential database'),
|
||||
[BackupTypeCodes.DifferentialFile]: localize('sql.migration.restore.backuptype.differentialfile', 'Differential file'),
|
||||
[BackupTypeCodes.Partial]: localize('sql.migration.restore.backuptype.partial', 'Partial'),
|
||||
[BackupTypeCodes.DifferentialPartial]: localize('sql.migration.restore.backuptype.differentialpartial', 'Differential partial'),
|
||||
};
|
||||
|
||||
export const BackupSetRestoreStatusLookup: LookupTable<string | undefined> = {
|
||||
[InternalManagedDatabaseRestoreDetailsBackupSetStatusCodes.None]: localize('sql.migration.restore.backupset.status.none', 'None'),
|
||||
[InternalManagedDatabaseRestoreDetailsBackupSetStatusCodes.Queued]: localize('sql.migration.restore.backupset.status.queued', 'Queued'),
|
||||
[InternalManagedDatabaseRestoreDetailsBackupSetStatusCodes.Restored]: localize('sql.migration.restore.backupset.status.restored', 'Restored'),
|
||||
[InternalManagedDatabaseRestoreDetailsBackupSetStatusCodes.Restoring]: localize('sql.migration.restore.backupset.status.restoring', 'Restoring'),
|
||||
[InternalManagedDatabaseRestoreDetailsBackupSetStatusCodes.Skipped]: localize('sql.migration.restore.backupset.status.skipped', 'Skipped'),
|
||||
};
|
||||
|
||||
export const InternalManagedDatabaseRestoreDetailsStatusLookup: LookupTable<string | undefined> = {
|
||||
[InternalManagedDatabaseRestoreDetailsStatusCodes.None]: localize('sql.migration.restore.status.none', 'None'),
|
||||
[InternalManagedDatabaseRestoreDetailsStatusCodes.Initializing]: localize('sql.migration.restore.status.initializing', 'Initializing'),
|
||||
[InternalManagedDatabaseRestoreDetailsStatusCodes.NotStarted]: localize('sql.migration.restore.status.not.started', 'Not started'),
|
||||
[InternalManagedDatabaseRestoreDetailsStatusCodes.SearchingBackups]: localize('sql.migration.restore.status.searching.backups', 'Searching backups'),
|
||||
[InternalManagedDatabaseRestoreDetailsStatusCodes.Restoring]: localize('sql.migration.restore.status.Restoring', 'Restoring'),
|
||||
[InternalManagedDatabaseRestoreDetailsStatusCodes.RestorePaused]: localize('sql.migration.restore.status.restore.paused', 'Restore paused'),
|
||||
[InternalManagedDatabaseRestoreDetailsStatusCodes.RestoreCompleted]: localize('sql.migration.restore.status.restore.completed', 'Restore completed'),
|
||||
[InternalManagedDatabaseRestoreDetailsStatusCodes.Waiting]: localize('sql.migration.restore.status.waiting', 'Waiting'),
|
||||
[InternalManagedDatabaseRestoreDetailsStatusCodes.CompletingMigration]: localize('sql.migration.restore.status.completing.migration', 'Completing migration'),
|
||||
[InternalManagedDatabaseRestoreDetailsStatusCodes.Cancelled]: localize('sql.migration.restore.status.cancelled', 'Cancelled'),
|
||||
[InternalManagedDatabaseRestoreDetailsStatusCodes.Failed]: localize('sql.migration.restore.status.failed', 'Failed'),
|
||||
[InternalManagedDatabaseRestoreDetailsStatusCodes.Completed]: localize('sql.migration.restore.status.completed', 'Completed'),
|
||||
[InternalManagedDatabaseRestoreDetailsStatusCodes.Blocked]: localize('sql.migration.restore.status.blocked', 'Blocked'),
|
||||
};
|
||||
|
||||
export function STATUS_WARNING_COUNT(status: string, count: number): string | undefined {
|
||||
if (status === MigrationState.InProgress ||
|
||||
status === MigrationState.ReadyForCutover ||
|
||||
@@ -1199,8 +1257,8 @@ export const sizeFormatter = new Intl.NumberFormat(
|
||||
maximumFractionDigits: 2,
|
||||
});
|
||||
|
||||
export function formatSizeMb(sizeMb: number): string {
|
||||
if (isNaN(sizeMb) || sizeMb < 0) {
|
||||
export function formatSizeMb(sizeMb: number | undefined): string {
|
||||
if (sizeMb === undefined || isNaN(sizeMb) || sizeMb < 0) {
|
||||
return '';
|
||||
} else if (sizeMb < 1024) {
|
||||
return localize('sql.migration.size.mb', "{0} MB", sizeFormatter.format(sizeMb));
|
||||
|
||||
@@ -765,6 +765,13 @@ export class DashboardTab extends TabBase<DashboardTab> {
|
||||
})
|
||||
.component();
|
||||
|
||||
this.disposables.push(
|
||||
this._serviceContextButton.onDidClick(
|
||||
async () => {
|
||||
const dialog = new SelectMigrationServiceDialog(this.serviceContextChangedEvent);
|
||||
await dialog.initialize();
|
||||
}));
|
||||
|
||||
this.disposables.push(
|
||||
this.serviceContextChangedEvent.event(
|
||||
async (e) => {
|
||||
@@ -776,12 +783,6 @@ export class DashboardTab extends TabBase<DashboardTab> {
|
||||
));
|
||||
await this.updateServiceContext(this._serviceContextButton);
|
||||
|
||||
this.disposables.push(
|
||||
this._serviceContextButton.onDidClick(async () => {
|
||||
const dialog = new SelectMigrationServiceDialog(this.serviceContextChangedEvent);
|
||||
await dialog.initialize();
|
||||
}));
|
||||
|
||||
return this._serviceContextButton;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,208 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 * as vscode from 'vscode';
|
||||
import * as loc from '../constants/strings';
|
||||
import { getSqlServerName, getMigrationStatusImage } from '../api/utils';
|
||||
import { logError, TelemetryViews } from '../telemetry';
|
||||
import { canCancelMigration, canCutoverMigration, canDeleteMigration, canRetryMigration, getMigrationStatusString, getMigrationTargetTypeEnum, isOfflineMigation } from '../constants/helper';
|
||||
import { getResourceName } from '../api/azure';
|
||||
import { InfoFieldSchema, infoFieldWidth, MigrationDetailsTabBase, MigrationTargetTypeName } from './migrationDetailsTabBase';
|
||||
import { EmptySettingValue } from './tabBase';
|
||||
import { DashboardStatusBar } from './DashboardStatusBar';
|
||||
import { getSourceConnectionServerInfo } from '../api/sqlUtils';
|
||||
|
||||
const MigrationDetailsBlobContainerTabId = 'MigrationDetailsBlobContainerTab';
|
||||
|
||||
export class MigrationDetailsBlobContainerTab extends MigrationDetailsTabBase<MigrationDetailsBlobContainerTab> {
|
||||
private _sourceDatabaseInfoField!: InfoFieldSchema;
|
||||
private _sourceDetailsInfoField!: InfoFieldSchema;
|
||||
private _sourceVersionInfoField!: InfoFieldSchema;
|
||||
private _targetDatabaseInfoField!: InfoFieldSchema;
|
||||
private _targetServerInfoField!: InfoFieldSchema;
|
||||
private _targetVersionInfoField!: InfoFieldSchema;
|
||||
private _migrationStatusInfoField!: InfoFieldSchema;
|
||||
private _backupLocationInfoField!: InfoFieldSchema;
|
||||
private _lastAppliedBackupInfoField!: InfoFieldSchema;
|
||||
private _currentRestoringFileInfoField!: InfoFieldSchema;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.id = MigrationDetailsBlobContainerTabId;
|
||||
}
|
||||
|
||||
public async create(
|
||||
context: vscode.ExtensionContext,
|
||||
view: azdata.ModelView,
|
||||
openMigrationsListFcn: (refresh?: boolean) => Promise<void>,
|
||||
statusBar: DashboardStatusBar,
|
||||
): Promise<MigrationDetailsBlobContainerTab> {
|
||||
|
||||
this.view = view;
|
||||
this.context = context;
|
||||
this.openMigrationsListFcn = openMigrationsListFcn;
|
||||
this.statusBar = statusBar;
|
||||
|
||||
await this.initialize(this.view);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public async refresh(): Promise<void> {
|
||||
if (this.isRefreshing ||
|
||||
this.refreshLoader === undefined ||
|
||||
this.model?.migration === undefined) {
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.isRefreshing = true;
|
||||
this.refreshLoader.loading = true;
|
||||
await this.statusBar.clearError();
|
||||
|
||||
try {
|
||||
await this.model.fetchStatus();
|
||||
} catch (e) {
|
||||
await this.statusBar.showError(
|
||||
loc.MIGRATION_STATUS_REFRESH_ERROR,
|
||||
loc.MIGRATION_STATUS_REFRESH_ERROR,
|
||||
e.message);
|
||||
}
|
||||
|
||||
const migration = this.model?.migration;
|
||||
await this.cutoverButton.updateCssStyles(
|
||||
{ 'display': isOfflineMigation(migration) ? 'none' : 'block' });
|
||||
|
||||
await this.showMigrationErrors(migration);
|
||||
|
||||
const sqlServerName = migration.properties.sourceServerName;
|
||||
const sourceDatabaseName = migration.properties.sourceDatabaseName;
|
||||
const sqlServerInfo = await getSourceConnectionServerInfo();
|
||||
const versionName = getSqlServerName(sqlServerInfo.serverMajorVersion!);
|
||||
const sqlServerVersion = versionName ? versionName : sqlServerInfo.serverVersion;
|
||||
const targetDatabaseName = migration.name;
|
||||
const targetServerName = getResourceName(migration.properties.scope);
|
||||
|
||||
const targetType = getMigrationTargetTypeEnum(migration);
|
||||
const targetServerVersion = MigrationTargetTypeName[targetType ?? ''];
|
||||
|
||||
this.databaseLabel.value = sourceDatabaseName;
|
||||
this._sourceDatabaseInfoField.text.value = sourceDatabaseName;
|
||||
this._sourceDetailsInfoField.text.value = sqlServerName;
|
||||
this._sourceVersionInfoField.text.value = `${sqlServerVersion} ${sqlServerInfo.serverVersion}`;
|
||||
|
||||
this._targetDatabaseInfoField.text.value = targetDatabaseName;
|
||||
this._targetServerInfoField.text.value = targetServerName;
|
||||
this._targetVersionInfoField.text.value = targetServerVersion;
|
||||
|
||||
this._migrationStatusInfoField.text.value = getMigrationStatusString(migration);
|
||||
this._migrationStatusInfoField.icon!.iconPath = getMigrationStatusImage(migration);
|
||||
|
||||
const storageAccountResourceId = migration.properties.backupConfiguration?.sourceLocation?.azureBlob?.storageAccountResourceId;
|
||||
const blobContainerName
|
||||
= migration.properties.backupConfiguration?.sourceLocation?.azureBlob?.blobContainerName
|
||||
?? migration.properties.migrationStatusDetails?.blobContainerName;
|
||||
|
||||
const backupLocation = storageAccountResourceId && blobContainerName
|
||||
? `${storageAccountResourceId?.split('/').pop()} - ${blobContainerName}`
|
||||
: blobContainerName;
|
||||
this._backupLocationInfoField.text.value = backupLocation ?? EmptySettingValue;
|
||||
this._lastAppliedBackupInfoField.text.value = migration.properties.migrationStatusDetails?.lastRestoredFilename ?? EmptySettingValue;
|
||||
this._currentRestoringFileInfoField.text.value = this.getMigrationCurrentlyRestoringFile(migration) ?? EmptySettingValue;
|
||||
|
||||
this.cutoverButton.enabled = canCutoverMigration(migration);
|
||||
this.cancelButton.enabled = canCancelMigration(migration);
|
||||
this.deleteButton.enabled = canDeleteMigration(migration);
|
||||
this.retryButton.enabled = canRetryMigration(migration);
|
||||
|
||||
this.refreshLoader.loading = false;
|
||||
this.isRefreshing = false;
|
||||
}
|
||||
|
||||
protected async initialize(view: azdata.ModelView): Promise<void> {
|
||||
try {
|
||||
const formItems: azdata.FormComponent<azdata.Component>[] = [
|
||||
{ component: this.createMigrationToolbarContainer() },
|
||||
{ component: await this.migrationInfoGrid() },
|
||||
{
|
||||
component: this.view.modelBuilder.separator()
|
||||
.withProps({ width: '100%', CSSStyles: { 'padding': '0' } })
|
||||
.component()
|
||||
},
|
||||
];
|
||||
|
||||
this.content = this.view.modelBuilder.formContainer()
|
||||
.withFormItems(
|
||||
formItems,
|
||||
{ horizontal: false })
|
||||
.withLayout({ width: '100%', padding: '0 0 0 15px' })
|
||||
.withProps({ width: '100%', CSSStyles: { padding: '0 0 0 15px' } })
|
||||
.component();
|
||||
} catch (e) {
|
||||
logError(TelemetryViews.MigrationCutoverDialog, 'IntializingFailed', e);
|
||||
}
|
||||
}
|
||||
|
||||
protected override async migrationInfoGrid(): Promise<azdata.FlexContainer> {
|
||||
const addInfoFieldToContainer = (infoField: InfoFieldSchema, container: azdata.FlexContainer): void => {
|
||||
container.addItem(
|
||||
infoField.flexContainer,
|
||||
{ CSSStyles: { width: infoFieldWidth } });
|
||||
};
|
||||
|
||||
const flexServer = this.view.modelBuilder.flexContainer()
|
||||
.withLayout({ flexFlow: 'column' })
|
||||
.component();
|
||||
this._sourceDatabaseInfoField = await this.createInfoField(loc.SOURCE_DATABASE, '');
|
||||
this._sourceDetailsInfoField = await this.createInfoField(loc.SOURCE_SERVER, '');
|
||||
this._sourceVersionInfoField = await this.createInfoField(loc.SOURCE_VERSION, '');
|
||||
addInfoFieldToContainer(this._sourceDatabaseInfoField, flexServer);
|
||||
addInfoFieldToContainer(this._sourceDetailsInfoField, flexServer);
|
||||
addInfoFieldToContainer(this._sourceVersionInfoField, flexServer);
|
||||
|
||||
const flexTarget = this.view.modelBuilder.flexContainer()
|
||||
.withLayout({ flexFlow: 'column' })
|
||||
.component();
|
||||
this._targetDatabaseInfoField = await this.createInfoField(loc.TARGET_DATABASE_NAME, '');
|
||||
this._targetServerInfoField = await this.createInfoField(loc.TARGET_SERVER, '');
|
||||
this._targetVersionInfoField = await this.createInfoField(loc.TARGET_VERSION, '');
|
||||
addInfoFieldToContainer(this._targetDatabaseInfoField, flexTarget);
|
||||
addInfoFieldToContainer(this._targetServerInfoField, flexTarget);
|
||||
addInfoFieldToContainer(this._targetVersionInfoField, flexTarget);
|
||||
|
||||
const flexStatus = this.view.modelBuilder.flexContainer()
|
||||
.withLayout({ flexFlow: 'column' })
|
||||
.component();
|
||||
this._migrationStatusInfoField = await this.createInfoField(loc.MIGRATION_STATUS, '', false, ' ');
|
||||
this._backupLocationInfoField = await this.createInfoField(loc.BACKUP_LOCATION, '');
|
||||
addInfoFieldToContainer(this._migrationStatusInfoField, flexStatus);
|
||||
addInfoFieldToContainer(this._backupLocationInfoField, flexStatus);
|
||||
|
||||
const flexFile = this.view.modelBuilder.flexContainer()
|
||||
.withLayout({ flexFlow: 'column' })
|
||||
.component();
|
||||
this._lastAppliedBackupInfoField = await this.createInfoField(loc.LAST_APPLIED_BACKUP_FILES, '');
|
||||
this._currentRestoringFileInfoField = await this.createInfoField(loc.CURRENTLY_RESTORING_FILE, '', false);
|
||||
addInfoFieldToContainer(this._lastAppliedBackupInfoField, flexFile);
|
||||
addInfoFieldToContainer(this._currentRestoringFileInfoField, flexFile);
|
||||
|
||||
const flexInfoProps = {
|
||||
flex: '0',
|
||||
CSSStyles: { 'flex': '0', 'width': infoFieldWidth }
|
||||
};
|
||||
|
||||
const flexInfo = this.view.modelBuilder.flexContainer()
|
||||
.withLayout({ flexWrap: 'wrap' })
|
||||
.withProps({ width: '100%' })
|
||||
.component();
|
||||
flexInfo.addItem(flexServer, flexInfoProps);
|
||||
flexInfo.addItem(flexTarget, flexInfoProps);
|
||||
flexInfo.addItem(flexStatus, flexInfoProps);
|
||||
flexInfo.addItem(flexFile, flexInfoProps);
|
||||
|
||||
return flexInfo;
|
||||
}
|
||||
}
|
||||
@@ -1,392 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 * as vscode from 'vscode';
|
||||
import { IconPathHelper } from '../constants/iconPathHelper';
|
||||
import * as loc from '../constants/strings';
|
||||
import { convertByteSizeToReadableUnit, convertIsoTimeToLocalTime, getSqlServerName, getMigrationStatusImage } from '../api/utils';
|
||||
import { logError, TelemetryViews } from '../telemetry';
|
||||
import * as styles from '../constants/styles';
|
||||
import { canCancelMigration, canCutoverMigration, canDeleteMigration, canRetryMigration, getMigrationStatusString, getMigrationTargetTypeEnum, isOfflineMigation } from '../constants/helper';
|
||||
import { getResourceName } from '../api/azure';
|
||||
import { EmptySettingValue } from './tabBase';
|
||||
import { InfoFieldSchema, infoFieldWidth, MigrationDetailsTabBase, MigrationTargetTypeName } from './migrationDetailsTabBase';
|
||||
import { DashboardStatusBar } from './DashboardStatusBar';
|
||||
import { getSourceConnectionServerInfo } from '../api/sqlUtils';
|
||||
|
||||
const MigrationDetailsFileShareTabId = 'MigrationDetailsFileShareTab';
|
||||
|
||||
interface ActiveBackupFileSchema {
|
||||
fileName: string,
|
||||
type: string,
|
||||
status: string,
|
||||
dataUploaded: string,
|
||||
copyThroughput: string,
|
||||
backupStartTime: string,
|
||||
firstLSN: string,
|
||||
lastLSN: string
|
||||
}
|
||||
|
||||
export class MigrationDetailsFileShareTab extends MigrationDetailsTabBase<MigrationDetailsFileShareTab> {
|
||||
private _sourceDatabaseInfoField!: InfoFieldSchema;
|
||||
private _sourceDetailsInfoField!: InfoFieldSchema;
|
||||
private _sourceVersionInfoField!: InfoFieldSchema;
|
||||
private _targetDatabaseInfoField!: InfoFieldSchema;
|
||||
private _targetServerInfoField!: InfoFieldSchema;
|
||||
private _targetVersionInfoField!: InfoFieldSchema;
|
||||
private _migrationStatusInfoField!: InfoFieldSchema;
|
||||
private _fullBackupFileOnInfoField!: InfoFieldSchema;
|
||||
private _backupLocationInfoField!: InfoFieldSchema;
|
||||
private _lastLSNInfoField!: InfoFieldSchema;
|
||||
private _lastAppliedBackupInfoField!: InfoFieldSchema;
|
||||
private _lastAppliedBackupTakenOnInfoField!: InfoFieldSchema;
|
||||
private _currentRestoringFileInfoField!: InfoFieldSchema;
|
||||
private _fileCount!: azdata.TextComponent;
|
||||
private _fileTable!: azdata.TableComponent;
|
||||
private _emptyTableFill!: azdata.FlexContainer;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.id = MigrationDetailsFileShareTabId;
|
||||
}
|
||||
|
||||
public async create(
|
||||
context: vscode.ExtensionContext,
|
||||
view: azdata.ModelView,
|
||||
openMigrationsListFcn: (refresh?: boolean) => Promise<void>,
|
||||
statusBar: DashboardStatusBar): Promise<MigrationDetailsFileShareTab> {
|
||||
|
||||
this.view = view;
|
||||
this.context = context;
|
||||
this.openMigrationsListFcn = openMigrationsListFcn;
|
||||
this.statusBar = statusBar;
|
||||
|
||||
await this.initialize(this.view);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public async refresh(): Promise<void> {
|
||||
if (this.isRefreshing ||
|
||||
this.refreshLoader === undefined ||
|
||||
this.model?.migration === undefined) {
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.isRefreshing = true;
|
||||
this.refreshLoader.loading = true;
|
||||
await this.statusBar.clearError();
|
||||
await this._fileTable.updateProperty('data', []);
|
||||
|
||||
await this.model.fetchStatus();
|
||||
|
||||
const migration = this.model?.migration;
|
||||
await this.cutoverButton.updateCssStyles(
|
||||
{ 'display': isOfflineMigation(migration) ? 'none' : 'block' });
|
||||
|
||||
await this.showMigrationErrors(migration);
|
||||
|
||||
const sqlServerName = migration.properties.sourceServerName;
|
||||
const sourceDatabaseName = migration.properties.sourceDatabaseName;
|
||||
const sqlServerInfo = await getSourceConnectionServerInfo();
|
||||
const versionName = getSqlServerName(sqlServerInfo.serverMajorVersion!);
|
||||
const sqlServerVersion = versionName ? versionName : sqlServerInfo.serverVersion;
|
||||
const targetDatabaseName = migration.name;
|
||||
const targetServerName = getResourceName(migration.properties.scope);
|
||||
|
||||
const targetType = getMigrationTargetTypeEnum(migration);
|
||||
const targetServerVersion = MigrationTargetTypeName[targetType ?? ''];
|
||||
|
||||
let lastAppliedSSN: string;
|
||||
let lastAppliedBackupFileTakenOn: string;
|
||||
|
||||
const tableData: ActiveBackupFileSchema[] = [];
|
||||
migration.properties.migrationStatusDetails?.activeBackupSets?.forEach(
|
||||
(activeBackupSet) => {
|
||||
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) : EmptySettingValue,
|
||||
backupStartTime: activeBackupSet.backupStartDate,
|
||||
firstLSN: activeBackupSet.firstLSN,
|
||||
lastLSN: activeBackupSet.lastLSN
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
if (activeBackupSet.listOfBackupFiles[0].fileName === migration.properties.migrationStatusDetails?.lastRestoredFilename) {
|
||||
lastAppliedSSN = activeBackupSet.lastLSN;
|
||||
lastAppliedBackupFileTakenOn = activeBackupSet.backupFinishDate;
|
||||
}
|
||||
});
|
||||
|
||||
this.databaseLabel.value = sourceDatabaseName;
|
||||
this._sourceDatabaseInfoField.text.value = sourceDatabaseName;
|
||||
this._sourceDetailsInfoField.text.value = sqlServerName;
|
||||
this._sourceVersionInfoField.text.value = `${sqlServerVersion} ${sqlServerInfo.serverVersion}`;
|
||||
|
||||
this._targetDatabaseInfoField.text.value = targetDatabaseName;
|
||||
this._targetServerInfoField.text.value = targetServerName;
|
||||
this._targetVersionInfoField.text.value = targetServerVersion;
|
||||
|
||||
this._migrationStatusInfoField.text.value = getMigrationStatusString(migration);
|
||||
this._migrationStatusInfoField.icon!.iconPath = getMigrationStatusImage(migration);
|
||||
|
||||
this._fullBackupFileOnInfoField.text.value = migration?.properties?.migrationStatusDetails?.fullBackupSetInfo?.listOfBackupFiles[0]?.fileName! ?? EmptySettingValue;
|
||||
|
||||
const fileShare = migration.properties.backupConfiguration?.sourceLocation?.fileShare;
|
||||
const backupLocation = fileShare?.path! ?? EmptySettingValue;
|
||||
this._backupLocationInfoField.text.value = backupLocation ?? EmptySettingValue;
|
||||
|
||||
this._lastLSNInfoField.text.value = lastAppliedSSN! ?? EmptySettingValue;
|
||||
this._lastAppliedBackupInfoField.text.value = migration.properties.migrationStatusDetails?.lastRestoredFilename ?? EmptySettingValue;
|
||||
this._lastAppliedBackupTakenOnInfoField.text.value = lastAppliedBackupFileTakenOn! ? convertIsoTimeToLocalTime(lastAppliedBackupFileTakenOn).toLocaleString() : EmptySettingValue;
|
||||
this._currentRestoringFileInfoField.text.value = this.getMigrationCurrentlyRestoringFile(migration) ?? EmptySettingValue;
|
||||
|
||||
await this._fileCount.updateCssStyles({ ...styles.SECTION_HEADER_CSS, display: 'inline' });
|
||||
|
||||
this._fileCount.value = loc.ACTIVE_BACKUP_FILES_ITEMS(tableData.length);
|
||||
if (tableData.length === 0) {
|
||||
await this._emptyTableFill.updateCssStyles({ 'display': 'flex' });
|
||||
this._fileTable.height = '50px';
|
||||
await this._fileTable.updateProperty('data', []);
|
||||
} else {
|
||||
await this._emptyTableFill.updateCssStyles({ 'display': 'none' });
|
||||
this._fileTable.height = '300px';
|
||||
|
||||
// Sorting files in descending order of backupStartTime
|
||||
tableData.sort((file1, file2) => new Date(file1.backupStartTime) > new Date(file2.backupStartTime) ? - 1 : 1);
|
||||
}
|
||||
|
||||
const data = tableData.map(row => [
|
||||
row.fileName,
|
||||
row.type,
|
||||
row.status,
|
||||
row.dataUploaded,
|
||||
row.copyThroughput,
|
||||
convertIsoTimeToLocalTime(row.backupStartTime).toLocaleString(),
|
||||
row.firstLSN,
|
||||
row.lastLSN
|
||||
]) || [];
|
||||
|
||||
await this._fileTable.updateProperty('data', data);
|
||||
|
||||
this.cutoverButton.enabled = canCutoverMigration(migration);
|
||||
this.cancelButton.enabled = canCancelMigration(migration);
|
||||
this.deleteButton.enabled = canDeleteMigration(migration);
|
||||
this.retryButton.enabled = canRetryMigration(migration);
|
||||
} catch (e) {
|
||||
await this.statusBar.showError(
|
||||
loc.MIGRATION_STATUS_REFRESH_ERROR,
|
||||
loc.MIGRATION_STATUS_REFRESH_ERROR,
|
||||
e.message);
|
||||
} finally {
|
||||
this.refreshLoader.loading = false;
|
||||
this.isRefreshing = false;
|
||||
}
|
||||
}
|
||||
|
||||
protected async initialize(view: azdata.ModelView): Promise<void> {
|
||||
try {
|
||||
this._fileCount = this.view.modelBuilder.text()
|
||||
.withProps({
|
||||
width: '500px',
|
||||
CSSStyles: { ...styles.BODY_CSS }
|
||||
}).component();
|
||||
|
||||
this._fileTable = this.view.modelBuilder.table()
|
||||
.withProps({
|
||||
ariaLabel: loc.ACTIVE_BACKUP_FILES,
|
||||
CSSStyles: { 'padding-left': '0px', 'max-width': '1020px' },
|
||||
data: [],
|
||||
height: '300px',
|
||||
columns: [
|
||||
{
|
||||
value: 'files',
|
||||
name: loc.ACTIVE_BACKUP_FILES,
|
||||
type: azdata.ColumnType.text,
|
||||
width: 230,
|
||||
},
|
||||
{
|
||||
value: 'type',
|
||||
name: loc.TYPE,
|
||||
width: 90,
|
||||
type: azdata.ColumnType.text,
|
||||
},
|
||||
{
|
||||
value: 'status',
|
||||
name: loc.STATUS,
|
||||
width: 60,
|
||||
type: azdata.ColumnType.text,
|
||||
},
|
||||
{
|
||||
value: 'uploaded',
|
||||
name: loc.DATA_UPLOADED,
|
||||
width: 120,
|
||||
type: azdata.ColumnType.text,
|
||||
},
|
||||
{
|
||||
value: 'throughput',
|
||||
name: loc.COPY_THROUGHPUT,
|
||||
width: 150,
|
||||
type: azdata.ColumnType.text,
|
||||
},
|
||||
{
|
||||
value: 'starttime',
|
||||
name: loc.BACKUP_START_TIME,
|
||||
width: 130,
|
||||
type: azdata.ColumnType.text,
|
||||
},
|
||||
{
|
||||
value: 'firstlsn',
|
||||
name: loc.FIRST_LSN,
|
||||
width: 120,
|
||||
type: azdata.ColumnType.text,
|
||||
},
|
||||
{
|
||||
value: 'lastlsn',
|
||||
name: loc.LAST_LSN,
|
||||
width: 120,
|
||||
type: azdata.ColumnType.text,
|
||||
}
|
||||
],
|
||||
}).component();
|
||||
|
||||
const emptyTableImage = this.view.modelBuilder.image()
|
||||
.withProps({
|
||||
iconPath: IconPathHelper.emptyTable,
|
||||
iconHeight: '100px',
|
||||
iconWidth: '100px',
|
||||
height: '100px',
|
||||
width: '100px',
|
||||
CSSStyles: { 'text-align': 'center' }
|
||||
}).component();
|
||||
|
||||
const emptyTableText = this.view.modelBuilder.text()
|
||||
.withProps({
|
||||
value: loc.EMPTY_TABLE_TEXT,
|
||||
CSSStyles: {
|
||||
...styles.NOTE_CSS,
|
||||
'margin-top': '8px',
|
||||
'text-align': 'center',
|
||||
'width': '300px'
|
||||
}
|
||||
}).component();
|
||||
|
||||
this._emptyTableFill = this.view.modelBuilder.flexContainer()
|
||||
.withLayout({
|
||||
flexFlow: 'column',
|
||||
alignItems: 'center'
|
||||
}).withItems([
|
||||
emptyTableImage,
|
||||
emptyTableText,
|
||||
]).withProps({
|
||||
width: '100%',
|
||||
display: 'none'
|
||||
}).component();
|
||||
|
||||
const formItems: azdata.FormComponent<azdata.Component>[] = [
|
||||
{ component: this.createMigrationToolbarContainer() },
|
||||
{ component: await this.migrationInfoGrid() },
|
||||
{
|
||||
component: this.view.modelBuilder.separator()
|
||||
.withProps({ width: '100%', CSSStyles: { 'padding': '0' } })
|
||||
.component()
|
||||
},
|
||||
{ component: this._fileCount },
|
||||
{ component: this._fileTable },
|
||||
{ component: this._emptyTableFill }
|
||||
];
|
||||
|
||||
const formContainer = this.view.modelBuilder.formContainer()
|
||||
.withFormItems(
|
||||
formItems,
|
||||
{ horizontal: false })
|
||||
.withLayout({ width: '100%', padding: '0 0 0 15px' })
|
||||
.withProps({ width: '100%', CSSStyles: { padding: '0 0 0 15px' } })
|
||||
.component();
|
||||
|
||||
this.content = formContainer;
|
||||
} catch (e) {
|
||||
logError(TelemetryViews.MigrationCutoverDialog, 'IntializingFailed', e);
|
||||
}
|
||||
}
|
||||
|
||||
protected async migrationInfoGrid(): Promise<azdata.FlexContainer> {
|
||||
const addInfoFieldToContainer = (infoField: InfoFieldSchema, container: azdata.FlexContainer): void => {
|
||||
container.addItem(
|
||||
infoField.flexContainer,
|
||||
{ CSSStyles: { width: infoFieldWidth } });
|
||||
};
|
||||
|
||||
const flexServer = this.view.modelBuilder.flexContainer()
|
||||
.withLayout({ flexFlow: 'column' })
|
||||
.component();
|
||||
this._sourceDatabaseInfoField = await this.createInfoField(loc.SOURCE_DATABASE, '');
|
||||
this._sourceDetailsInfoField = await this.createInfoField(loc.SOURCE_SERVER, '');
|
||||
this._sourceVersionInfoField = await this.createInfoField(loc.SOURCE_VERSION, '');
|
||||
addInfoFieldToContainer(this._sourceDatabaseInfoField, flexServer);
|
||||
addInfoFieldToContainer(this._sourceDetailsInfoField, flexServer);
|
||||
addInfoFieldToContainer(this._sourceVersionInfoField, flexServer);
|
||||
|
||||
const flexTarget = this.view.modelBuilder.flexContainer()
|
||||
.withLayout({ flexFlow: 'column' })
|
||||
.component();
|
||||
this._targetDatabaseInfoField = await this.createInfoField(loc.TARGET_DATABASE_NAME, '');
|
||||
this._targetServerInfoField = await this.createInfoField(loc.TARGET_SERVER, '');
|
||||
this._targetVersionInfoField = await this.createInfoField(loc.TARGET_VERSION, '');
|
||||
addInfoFieldToContainer(this._targetDatabaseInfoField, flexTarget);
|
||||
addInfoFieldToContainer(this._targetServerInfoField, flexTarget);
|
||||
addInfoFieldToContainer(this._targetVersionInfoField, flexTarget);
|
||||
|
||||
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, '', false);
|
||||
this._backupLocationInfoField = await this.createInfoField(loc.BACKUP_LOCATION, '');
|
||||
addInfoFieldToContainer(this._migrationStatusInfoField, flexStatus);
|
||||
addInfoFieldToContainer(this._fullBackupFileOnInfoField, flexStatus);
|
||||
addInfoFieldToContainer(this._backupLocationInfoField, flexStatus);
|
||||
|
||||
const flexFile = this.view.modelBuilder.flexContainer()
|
||||
.withLayout({ flexFlow: 'column' })
|
||||
.component();
|
||||
this._lastLSNInfoField = await this.createInfoField(loc.LAST_APPLIED_LSN, '', false);
|
||||
this._lastAppliedBackupInfoField = await this.createInfoField(loc.LAST_APPLIED_BACKUP_FILES, '');
|
||||
this._lastAppliedBackupTakenOnInfoField = await this.createInfoField(loc.LAST_APPLIED_BACKUP_FILES_TAKEN_ON, '', false);
|
||||
this._currentRestoringFileInfoField = await this.createInfoField(loc.CURRENTLY_RESTORING_FILE, '', false);
|
||||
addInfoFieldToContainer(this._lastLSNInfoField, flexFile);
|
||||
addInfoFieldToContainer(this._lastAppliedBackupInfoField, flexFile);
|
||||
addInfoFieldToContainer(this._lastAppliedBackupTakenOnInfoField, flexFile);
|
||||
addInfoFieldToContainer(this._currentRestoringFileInfoField, flexFile);
|
||||
|
||||
const flexInfoProps = {
|
||||
flex: '0',
|
||||
CSSStyles: {
|
||||
'flex': '0',
|
||||
'width': infoFieldWidth
|
||||
}
|
||||
};
|
||||
|
||||
const flexInfo = this.view.modelBuilder.flexContainer()
|
||||
.withLayout({ flexWrap: 'wrap' })
|
||||
.withProps({ width: '100%' })
|
||||
.component();
|
||||
flexInfo.addItem(flexServer, flexInfoProps);
|
||||
flexInfo.addItem(flexTarget, flexInfoProps);
|
||||
flexInfo.addItem(flexStatus, flexInfoProps);
|
||||
flexInfo.addItem(flexFile, flexInfoProps);
|
||||
|
||||
return flexInfo;
|
||||
}
|
||||
}
|
||||
573
extensions/sql-migration/src/dashboard/migrationDetailsTab.ts
Normal file
573
extensions/sql-migration/src/dashboard/migrationDetailsTab.ts
Normal file
@@ -0,0 +1,573 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 * as vscode from 'vscode';
|
||||
import { IconPathHelper } from '../constants/iconPathHelper';
|
||||
import * as loc from '../constants/strings';
|
||||
import { convertByteSizeToReadableUnit, convertIsoTimeToLocalTime, getMigrationStatusImage } from '../api/utils';
|
||||
import { logError, TelemetryViews } from '../telemetry';
|
||||
import * as styles from '../constants/styles';
|
||||
import { canCancelMigration, canCutoverMigration, canDeleteMigration, canRetryMigration, getMigrationStatusString, getMigrationTargetTypeEnum, isOfflineMigation, isShirMigration } from '../constants/helper';
|
||||
import { AzureResourceKind, DatabaseMigration, getResourceName } from '../api/azure';
|
||||
import * as utils from '../api/utils';
|
||||
import * as helper from '../constants/helper';
|
||||
import { EmptySettingValue } from './tabBase';
|
||||
import { InfoFieldSchema, MigrationDetailsTabBase, MigrationTargetTypeName } from './migrationDetailsTabBase';
|
||||
import { DashboardStatusBar } from './DashboardStatusBar';
|
||||
|
||||
const MigrationDetailsTabId = 'MigrationDetailsTab';
|
||||
|
||||
const enum BackupFileColumnIndex {
|
||||
backupFileName = 0,
|
||||
backupType = 1,
|
||||
backupStatus = 2,
|
||||
restoreStatus = 3,
|
||||
backupSizeMB = 4,
|
||||
numberOfStripes = 5,
|
||||
dataUploadRate = 6,
|
||||
throughput = 7,
|
||||
backupStartTime = 8,
|
||||
restoreStartDate = 9,
|
||||
restoreFinishDate = 10,
|
||||
firstLsn = 11,
|
||||
lastLsn = 12,
|
||||
}
|
||||
|
||||
interface ActiveBackupFileSchema {
|
||||
backupFileName: string,
|
||||
backupType: string,
|
||||
|
||||
// SHIR provided
|
||||
backupStatus: string,
|
||||
dataUploaded: string, // SHIR provided
|
||||
dataUploadRate: string, // SHIR provided
|
||||
backupStartTime: string, // SHIR provided
|
||||
firstLSN: string, // SHIR provided
|
||||
lastLSN: string, // SHIR provided
|
||||
|
||||
// SQLMI provided
|
||||
restoreStartDate: string, // SQLMI provided
|
||||
restoreFinishDate: string, // SQLMI provided
|
||||
restoreStatus: string, // SQLMI provided
|
||||
backupSizeMB: string, // SQLMI provided
|
||||
numberOfStripes: number, // SQLMI provided
|
||||
}
|
||||
|
||||
export class MigrationDetailsTab extends MigrationDetailsTabBase<MigrationDetailsTab> {
|
||||
private _sourceDatabaseInfoField!: InfoFieldSchema;
|
||||
private _sourceDetailsInfoField!: InfoFieldSchema;
|
||||
private _targetDatabaseInfoField!: InfoFieldSchema;
|
||||
private _targetServerInfoField!: InfoFieldSchema;
|
||||
private _targetVersionInfoField!: InfoFieldSchema;
|
||||
private _migrationStatusInfoField!: InfoFieldSchema;
|
||||
private _fullBackupFileOnInfoField!: InfoFieldSchema;
|
||||
private _backupLocationInfoField!: InfoFieldSchema;
|
||||
private _lastUploadedFileNameField!: InfoFieldSchema;
|
||||
private _lastUploadedFileTimeField!: InfoFieldSchema;
|
||||
private _pendingDiffBackupsCountField!: InfoFieldSchema;
|
||||
private _detectedFilesField!: InfoFieldSchema;
|
||||
private _queuedFilesField!: InfoFieldSchema;
|
||||
private _skippedFilesField!: InfoFieldSchema;
|
||||
private _unrestorableFilesField!: InfoFieldSchema;
|
||||
|
||||
private _lastLSNInfoField!: InfoFieldSchema;
|
||||
private _lastAppliedBackupInfoField!: InfoFieldSchema;
|
||||
private _lastAppliedBackupTakenOnInfoField!: InfoFieldSchema;
|
||||
private _currentRestoringFileInfoField!: InfoFieldSchema;
|
||||
private _lastRestoredFileTimeInfoField!: InfoFieldSchema;
|
||||
private _restoredFilesInfoField!: InfoFieldSchema;
|
||||
private _restoringFilesInfoField!: InfoFieldSchema;
|
||||
private _currentRestoredSizeInfoField!: InfoFieldSchema;
|
||||
private _currentRestorePlanSizeInfoField!: InfoFieldSchema;
|
||||
private _restorePercentCompletedInfoField!: InfoFieldSchema;
|
||||
private _miRestoreStateInfoField!: InfoFieldSchema;
|
||||
|
||||
private _fileCount!: azdata.TextComponent;
|
||||
private _fileTable!: azdata.TableComponent;
|
||||
private _emptyTableFill!: azdata.FlexContainer;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.id = MigrationDetailsTabId;
|
||||
}
|
||||
|
||||
public async create(
|
||||
context: vscode.ExtensionContext,
|
||||
view: azdata.ModelView,
|
||||
openMigrationsListFcn: (refresh?: boolean) => Promise<void>,
|
||||
statusBar: DashboardStatusBar): Promise<MigrationDetailsTab> {
|
||||
|
||||
this.view = view;
|
||||
this.context = context;
|
||||
this.openMigrationsListFcn = openMigrationsListFcn;
|
||||
this.statusBar = statusBar;
|
||||
|
||||
await this.initialize(this.view);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public async refresh(initialize?: boolean): Promise<void> {
|
||||
if (this.isRefreshing ||
|
||||
this.refreshLoader === undefined ||
|
||||
this.model?.migration === undefined) {
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.isRefreshing = true;
|
||||
this.refreshLoader.loading = true;
|
||||
await this.statusBar.clearError();
|
||||
|
||||
if (initialize) {
|
||||
await this._clearControlsValue();
|
||||
await utils.updateControlDisplay(this._fileTable, false);
|
||||
await this._fileTable.updateProperty('columns', this._getTableColumns(this.model?.migration));
|
||||
await this._showControls(this.model?.migration);
|
||||
}
|
||||
await this._fileTable.updateProperty('data', []);
|
||||
await this.model.fetchStatus();
|
||||
|
||||
const migration = this.model?.migration;
|
||||
await this.cutoverButton.updateCssStyles(
|
||||
{ 'display': isOfflineMigation(migration) ? 'none' : 'block' });
|
||||
|
||||
await this.showMigrationErrors(migration);
|
||||
|
||||
let lastAppliedLSN: string = '';
|
||||
let lastAppliedBackupFileTakenOn: string = '';
|
||||
|
||||
const tableData: ActiveBackupFileSchema[] = [];
|
||||
migration?.properties?.migrationStatusDetails?.activeBackupSets?.forEach(
|
||||
(activeBackupSet) => {
|
||||
tableData.push(
|
||||
...activeBackupSet?.listOfBackupFiles?.map(f => {
|
||||
return {
|
||||
backupFileName: f.fileName,
|
||||
backupType: loc.BackupTypeLookup[activeBackupSet.backupType] ?? activeBackupSet.backupType,
|
||||
backupStatus: loc.BackupSetRestoreStatusLookup[f.status] ?? f.status, // SHIR provided
|
||||
restoreStatus: loc.InternalManagedDatabaseRestoreDetailsStatusLookup[activeBackupSet.restoreStatus] ?? activeBackupSet.restoreStatus,
|
||||
backupSizeMB: loc.formatSizeMb(activeBackupSet.backupSizeMB),
|
||||
numberOfStripes: activeBackupSet.numberOfStripes,
|
||||
dataUploadRate: `${convertByteSizeToReadableUnit(f.dataWritten ?? 0)} / ${convertByteSizeToReadableUnit(f.totalSize)}`,
|
||||
// SHIR provided
|
||||
dataUploaded: f.copyThroughput
|
||||
? (f.copyThroughput / 1024).toFixed(2)
|
||||
: EmptySettingValue, // SHIR provided
|
||||
backupStartTime: helper.formatDateTimeString(activeBackupSet.backupStartDate), // SHIR provided
|
||||
restoreStartDate: helper.formatDateTimeString(activeBackupSet.restoreStartDate),
|
||||
restoreFinishDate: helper.formatDateTimeString(activeBackupSet.restoreFinishDate),
|
||||
firstLSN: activeBackupSet.firstLSN, // SHIR provided
|
||||
lastLSN: activeBackupSet.lastLSN, // SHIR provided
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
if (activeBackupSet.listOfBackupFiles?.length > 0 &&
|
||||
activeBackupSet.listOfBackupFiles[0].fileName === migration?.properties?.migrationStatusDetails?.lastRestoredFilename) {
|
||||
lastAppliedLSN = activeBackupSet.lastLSN;
|
||||
lastAppliedBackupFileTakenOn = activeBackupSet.backupFinishDate;
|
||||
}
|
||||
});
|
||||
|
||||
this.databaseLabel.value = migration?.properties?.sourceDatabaseName;
|
||||
|
||||
// Left side
|
||||
this._updateInfoFieldValue(this._sourceDatabaseInfoField, migration?.properties?.sourceDatabaseName);
|
||||
this._updateInfoFieldValue(this._sourceDetailsInfoField, migration?.properties?.sourceServerName);
|
||||
this._updateInfoFieldValue(this._migrationStatusInfoField, getMigrationStatusString(migration));
|
||||
this._migrationStatusInfoField.icon!.iconPath = getMigrationStatusImage(migration);
|
||||
|
||||
this._updateInfoFieldValue(this._fullBackupFileOnInfoField, helper.getMigrationFullBackupFiles(migration) ?? EmptySettingValue);
|
||||
this._updateInfoFieldValue(this._backupLocationInfoField, helper.getMigrationBackupLocation(migration) ?? EmptySettingValue);
|
||||
|
||||
// SQL MI supplied
|
||||
const details = migration?.properties?.migrationStatusDetails;
|
||||
this._updateInfoFieldValue(this._lastUploadedFileNameField, details?.lastUploadedFileName ?? EmptySettingValue);
|
||||
this._updateInfoFieldValue(
|
||||
this._lastUploadedFileTimeField,
|
||||
details?.lastUploadedFileTime
|
||||
? helper.formatDateTimeString(lastAppliedBackupFileTakenOn)
|
||||
: EmptySettingValue);
|
||||
this._updateInfoFieldValue(this._pendingDiffBackupsCountField, details?.pendingDiffBackupsCount?.toLocaleString() ?? EmptySettingValue);
|
||||
this._updateInfoFieldValue(this._detectedFilesField, details?.detectedFiles?.toLocaleString() ?? EmptySettingValue);
|
||||
this._updateInfoFieldValue(this._queuedFilesField, details?.queuedFiles?.toLocaleString() ?? EmptySettingValue);
|
||||
this._updateInfoFieldValue(this._skippedFilesField, details?.skippedFiles?.toLocaleString() ?? EmptySettingValue);
|
||||
this._updateInfoFieldValue(this._unrestorableFilesField, details?.unrestorableFiles?.toLocaleString() ?? EmptySettingValue);
|
||||
|
||||
// Right side
|
||||
this._updateInfoFieldValue(this._targetDatabaseInfoField, migration.name ?? EmptySettingValue);
|
||||
this._updateInfoFieldValue(this._targetServerInfoField, getResourceName(migration?.properties?.scope) ?? EmptySettingValue);
|
||||
this._updateInfoFieldValue(this._targetVersionInfoField, MigrationTargetTypeName[getMigrationTargetTypeEnum(migration) ?? ''] ?? EmptySettingValue);
|
||||
this._updateInfoFieldValue(
|
||||
this._lastLSNInfoField,
|
||||
lastAppliedLSN?.length > 0
|
||||
? lastAppliedLSN
|
||||
: EmptySettingValue);
|
||||
this._updateInfoFieldValue(this._lastAppliedBackupInfoField, migration?.properties?.migrationStatusDetails?.lastRestoredFilename ?? EmptySettingValue);
|
||||
|
||||
// FileShare
|
||||
this._updateInfoFieldValue(
|
||||
this._lastAppliedBackupTakenOnInfoField,
|
||||
lastAppliedBackupFileTakenOn?.length > 0
|
||||
? helper.formatDateTimeString(lastAppliedBackupFileTakenOn)
|
||||
: EmptySettingValue);
|
||||
|
||||
// AzureBlob
|
||||
this._updateInfoFieldValue(this._currentRestoringFileInfoField, this.getMigrationCurrentlyRestoringFile(migration) ?? EmptySettingValue);
|
||||
|
||||
// SQL MI supplied
|
||||
const lastRestoredFileTime = migration?.properties?.migrationStatusDetails?.lastRestoredFileTime ?? '';
|
||||
this._updateInfoFieldValue(
|
||||
this._lastRestoredFileTimeInfoField,
|
||||
lastRestoredFileTime.length > 0
|
||||
? convertIsoTimeToLocalTime(lastRestoredFileTime).toLocaleString()
|
||||
: EmptySettingValue);
|
||||
this._updateInfoFieldValue(this._restoredFilesInfoField, migration?.properties?.migrationStatusDetails?.restoredFiles?.toLocaleString() ?? EmptySettingValue);
|
||||
this._updateInfoFieldValue(this._restoringFilesInfoField, migration?.properties?.migrationStatusDetails?.restoringFiles?.toLocaleString() ?? EmptySettingValue);
|
||||
this._updateInfoFieldValue(
|
||||
this._currentRestoredSizeInfoField,
|
||||
migration?.properties?.migrationStatusDetails?.currentRestoredSize
|
||||
? loc.formatSizeMb(migration?.properties?.migrationStatusDetails?.currentRestoredSize)
|
||||
: EmptySettingValue);
|
||||
|
||||
this._updateInfoFieldValue(
|
||||
this._currentRestorePlanSizeInfoField,
|
||||
migration?.properties?.migrationStatusDetails?.currentRestorePlanSize
|
||||
? loc.formatSizeMb(migration?.properties?.migrationStatusDetails?.currentRestorePlanSize)
|
||||
: EmptySettingValue);
|
||||
|
||||
this._updateInfoFieldValue(this._restorePercentCompletedInfoField, migration?.properties?.migrationStatusDetails?.restorePercentCompleted?.toLocaleString() ?? EmptySettingValue);
|
||||
this._updateInfoFieldValue(
|
||||
this._miRestoreStateInfoField,
|
||||
loc.InternalManagedDatabaseRestoreDetailsStatusLookup[migration?.properties?.migrationStatusDetails?.miRestoreState ?? '']
|
||||
?? migration?.properties?.migrationStatusDetails?.miRestoreState
|
||||
?? EmptySettingValue);
|
||||
|
||||
const isBlobMigration = helper.isBlobMigration(migration);
|
||||
const isSqlVmTarget = helper.isTargetType(migration, AzureResourceKind.SQLVM);
|
||||
if (!isBlobMigration && !isSqlVmTarget) {
|
||||
await this._fileCount.updateCssStyles({ ...styles.SECTION_HEADER_CSS, display: 'inline' });
|
||||
this._fileCount.value = loc.ACTIVE_BACKUP_FILES_ITEMS(tableData.length);
|
||||
}
|
||||
|
||||
if (tableData.length === 0) {
|
||||
if (!isBlobMigration && !isSqlVmTarget) {
|
||||
await this._emptyTableFill.updateCssStyles({ 'display': 'flex' });
|
||||
}
|
||||
this._fileTable.height = '50px';
|
||||
await this._fileTable.updateProperty('data', []);
|
||||
} else {
|
||||
await this._emptyTableFill.updateCssStyles({ 'display': 'none' });
|
||||
this._fileTable.height = '340px';
|
||||
|
||||
// Sorting files in descending order of backupStartTime
|
||||
tableData.sort((file1, file2) => new Date(file1.backupStartTime) > new Date(file2.backupStartTime) ? - 1 : 1);
|
||||
}
|
||||
|
||||
const data = tableData.map(row => [
|
||||
row.backupFileName, // 0
|
||||
row.backupType, // 1
|
||||
row.backupStatus, // 2
|
||||
row.restoreStatus, // 3
|
||||
row.backupSizeMB, // 4
|
||||
row.numberOfStripes, // 5
|
||||
row.dataUploadRate, // 6
|
||||
row.dataUploaded, // 7
|
||||
row.backupStartTime, // 8
|
||||
row.restoreStartDate, // 9
|
||||
row.restoreFinishDate, // 10
|
||||
row.firstLSN, // 11
|
||||
row.lastLSN, // 12
|
||||
]) || [];
|
||||
|
||||
const filteredData = this._getTableData(migration, data);
|
||||
await this._fileTable.updateProperty('data', filteredData);
|
||||
|
||||
this.cutoverButton.enabled = canCutoverMigration(migration);
|
||||
this.cancelButton.enabled = canCancelMigration(migration);
|
||||
this.deleteButton.enabled = canDeleteMigration(migration);
|
||||
this.retryButton.enabled = canRetryMigration(migration);
|
||||
} catch (e) {
|
||||
await this.statusBar.showError(
|
||||
loc.MIGRATION_STATUS_REFRESH_ERROR,
|
||||
loc.MIGRATION_STATUS_REFRESH_ERROR,
|
||||
e.message);
|
||||
} finally {
|
||||
this.refreshLoader.loading = false;
|
||||
this.isRefreshing = false;
|
||||
}
|
||||
}
|
||||
|
||||
private _clearControlsValue(): void {
|
||||
this._updateInfoFieldValue(this._sourceDatabaseInfoField, '');
|
||||
this._updateInfoFieldValue(this._sourceDetailsInfoField, '');
|
||||
this._updateInfoFieldValue(this._targetDatabaseInfoField, '');
|
||||
this._updateInfoFieldValue(this._targetServerInfoField, '');
|
||||
this._updateInfoFieldValue(this._targetVersionInfoField, '');
|
||||
this._updateInfoFieldValue(this._migrationStatusInfoField, '');
|
||||
this._updateInfoFieldValue(this._fullBackupFileOnInfoField, '');
|
||||
this._updateInfoFieldValue(this._backupLocationInfoField, '');
|
||||
this._updateInfoFieldValue(this._lastUploadedFileNameField, '');
|
||||
this._updateInfoFieldValue(this._lastUploadedFileTimeField, '');
|
||||
this._updateInfoFieldValue(this._pendingDiffBackupsCountField, '');
|
||||
this._updateInfoFieldValue(this._detectedFilesField, '');
|
||||
this._updateInfoFieldValue(this._queuedFilesField, '');
|
||||
this._updateInfoFieldValue(this._skippedFilesField, '');
|
||||
this._updateInfoFieldValue(this._unrestorableFilesField, '');
|
||||
|
||||
this._updateInfoFieldValue(this._lastLSNInfoField, '');
|
||||
this._updateInfoFieldValue(this._lastAppliedBackupInfoField, '');
|
||||
this._updateInfoFieldValue(this._currentRestoringFileInfoField, '');
|
||||
this._updateInfoFieldValue(this._lastAppliedBackupTakenOnInfoField, '');
|
||||
this._updateInfoFieldValue(this._lastRestoredFileTimeInfoField, '');
|
||||
this._updateInfoFieldValue(this._restoredFilesInfoField, '');
|
||||
this._updateInfoFieldValue(this._restoringFilesInfoField, '');
|
||||
this._updateInfoFieldValue(this._currentRestoredSizeInfoField, '');
|
||||
this._updateInfoFieldValue(this._currentRestorePlanSizeInfoField, '');
|
||||
this._updateInfoFieldValue(this._restorePercentCompletedInfoField, '');
|
||||
this._updateInfoFieldValue(this._miRestoreStateInfoField, '');
|
||||
}
|
||||
|
||||
private async _showControls(migration: DatabaseMigration): Promise<void> {
|
||||
const isSHIR = helper.isShirMigration(migration);
|
||||
const isSqlMiTarget = helper.isTargetType(migration, AzureResourceKind.SQLMI);
|
||||
const isSqlVmTarget = helper.isTargetType(migration, AzureResourceKind.SQLVM);
|
||||
const isBlobMigration = helper.isBlobMigration(migration);
|
||||
|
||||
await utils.updateControlDisplay(this._fullBackupFileOnInfoField.flexContainer, isSHIR);
|
||||
await utils.updateControlDisplay(this._lastUploadedFileNameField.flexContainer, isSqlMiTarget);
|
||||
await utils.updateControlDisplay(this._lastUploadedFileTimeField.flexContainer, isSqlMiTarget);
|
||||
await utils.updateControlDisplay(this._pendingDiffBackupsCountField.flexContainer, isSqlMiTarget);
|
||||
await utils.updateControlDisplay(this._detectedFilesField.flexContainer, isSqlMiTarget);
|
||||
await utils.updateControlDisplay(this._queuedFilesField.flexContainer, isSqlMiTarget);
|
||||
await utils.updateControlDisplay(this._skippedFilesField.flexContainer, isSqlMiTarget);
|
||||
await utils.updateControlDisplay(this._unrestorableFilesField.flexContainer, isSqlMiTarget);
|
||||
await utils.updateControlDisplay(this._lastLSNInfoField.flexContainer, isSHIR);
|
||||
await utils.updateControlDisplay(this._currentRestoringFileInfoField.flexContainer, isBlobMigration);
|
||||
await utils.updateControlDisplay(this._lastAppliedBackupTakenOnInfoField.flexContainer, isSHIR);
|
||||
await utils.updateControlDisplay(this._lastRestoredFileTimeInfoField.flexContainer, isSqlMiTarget);
|
||||
await utils.updateControlDisplay(this._restoredFilesInfoField.flexContainer, isSqlMiTarget);
|
||||
await utils.updateControlDisplay(this._restoringFilesInfoField.flexContainer, isSqlMiTarget);
|
||||
await utils.updateControlDisplay(this._currentRestoredSizeInfoField.flexContainer, isSqlMiTarget);
|
||||
await utils.updateControlDisplay(this._currentRestorePlanSizeInfoField.flexContainer, isSqlMiTarget);
|
||||
await utils.updateControlDisplay(this._restorePercentCompletedInfoField.flexContainer, isSqlMiTarget);
|
||||
await utils.updateControlDisplay(this._miRestoreStateInfoField.flexContainer, isSqlMiTarget);
|
||||
|
||||
const showGrid = !(isBlobMigration && isSqlVmTarget);
|
||||
await utils.updateControlDisplay(this._emptyTableFill, showGrid, 'flex');
|
||||
await utils.updateControlDisplay(this._fileCount, showGrid);
|
||||
await utils.updateControlDisplay(this._fileTable, showGrid);
|
||||
}
|
||||
|
||||
private _addItemIfTrue(array: any[], value: any, add: boolean): void {
|
||||
if (add) {
|
||||
array.push(value);
|
||||
}
|
||||
}
|
||||
|
||||
private _getTableColumns(migration?: DatabaseMigration): azdata.TableColumn[] {
|
||||
const columns: azdata.TableColumn[] = [];
|
||||
const isSHIR = isShirMigration(migration);
|
||||
const isSqlMiTarget = helper.isTargetType(migration, AzureResourceKind.SQLMI);
|
||||
|
||||
this._addItemIfTrue(columns, { value: 'backupFileName', name: loc.BACKUP_FILE_COLUMN_FILE_NAME, tooltip: loc.BACKUP_FILE_COLUMN_FILE_NAME, type: azdata.ColumnType.text, }, true);
|
||||
this._addItemIfTrue(columns, { value: 'backupType', name: loc.TYPE, tooltip: loc.TYPE, type: azdata.ColumnType.text, }, true);
|
||||
this._addItemIfTrue(columns, { value: 'backupStatus', name: loc.BACKUP_FILE_COLUMN_FILE_STATUS, tooltip: loc.BACKUP_FILE_COLUMN_FILE_STATUS, type: azdata.ColumnType.text }, isSHIR);
|
||||
this._addItemIfTrue(columns, { value: 'restoreStatus', name: loc.BACKUP_FILE_COLUMN_RESTORE_STATUS, tooltip: loc.BACKUP_FILE_COLUMN_RESTORE_STATUS, type: azdata.ColumnType.text, }, isSqlMiTarget);
|
||||
this._addItemIfTrue(columns, { value: 'backupSizeMB', name: loc.BACKUP_FILE_COLUMN_BACKUP_SIZE_MB, tooltip: loc.BACKUP_FILE_COLUMN_BACKUP_SIZE_MB, type: azdata.ColumnType.text, }, isSqlMiTarget);
|
||||
this._addItemIfTrue(columns, { value: 'numberOfStripes', name: loc.BACKUP_FILE_COLUMN_NUMBER_OF_STRIPES, tooltip: loc.BACKUP_FILE_COLUMN_NUMBER_OF_STRIPES, type: azdata.ColumnType.text, }, isSqlMiTarget);
|
||||
this._addItemIfTrue(columns, { value: 'dataUploadRate', name: loc.DATA_UPLOADED, tooltip: loc.DATA_UPLOADED, type: azdata.ColumnType.text, }, isSHIR);
|
||||
this._addItemIfTrue(columns, { value: 'throughput', name: loc.COPY_THROUGHPUT, tooltip: loc.COPY_THROUGHPUT, type: azdata.ColumnType.text, }, isSHIR);
|
||||
this._addItemIfTrue(columns, { value: 'backupStartTime', name: loc.BACKUP_START_TIME, tooltip: loc.BACKUP_START_TIME, type: azdata.ColumnType.text, }, isSHIR);
|
||||
this._addItemIfTrue(columns, { value: 'restoreStartDate', name: loc.BACKUP_FILE_COLUMN_RESTORE_START_DATE, tooltip: loc.BACKUP_FILE_COLUMN_RESTORE_START_DATE, type: azdata.ColumnType.text, }, isSqlMiTarget);
|
||||
this._addItemIfTrue(columns, { value: 'restoreFinishDate', name: loc.BACKUP_FILE_COLUMN_RESTORE_FINISH_DATE, tooltip: loc.BACKUP_FILE_COLUMN_RESTORE_FINISH_DATE, type: azdata.ColumnType.text, }, isSqlMiTarget);
|
||||
this._addItemIfTrue(columns, { value: 'firstLsn', name: loc.FIRST_LSN, tooltip: loc.FIRST_LSN, type: azdata.ColumnType.text, }, isSHIR);
|
||||
this._addItemIfTrue(columns, { value: 'lastLsn', name: loc.LAST_LSN, tooltip: loc.LAST_LSN, type: azdata.ColumnType.text, }, isSHIR);
|
||||
|
||||
return columns;
|
||||
}
|
||||
|
||||
private _getTableData(migration?: DatabaseMigration, data?: (string | number)[][]): any[] {
|
||||
const isSHIR = isShirMigration(migration);
|
||||
const isSqlMiTarget = helper.isTargetType(migration, AzureResourceKind.SQLMI);
|
||||
|
||||
return data?.map(row => {
|
||||
const rec: any[] = [];
|
||||
this._addItemIfTrue(rec, row[BackupFileColumnIndex.backupFileName], true);
|
||||
this._addItemIfTrue(rec, row[BackupFileColumnIndex.backupType], true);
|
||||
this._addItemIfTrue(rec, row[BackupFileColumnIndex.backupStatus], isSHIR);
|
||||
this._addItemIfTrue(rec, row[BackupFileColumnIndex.restoreStatus], isSqlMiTarget);
|
||||
this._addItemIfTrue(rec, row[BackupFileColumnIndex.backupSizeMB], isSqlMiTarget);
|
||||
this._addItemIfTrue(rec, row[BackupFileColumnIndex.numberOfStripes], isSqlMiTarget);
|
||||
this._addItemIfTrue(rec, row[BackupFileColumnIndex.dataUploadRate], isSHIR);
|
||||
this._addItemIfTrue(rec, row[BackupFileColumnIndex.throughput], isSHIR);
|
||||
this._addItemIfTrue(rec, row[BackupFileColumnIndex.backupStartTime], isSHIR);
|
||||
this._addItemIfTrue(rec, row[BackupFileColumnIndex.restoreStartDate], isSqlMiTarget);
|
||||
this._addItemIfTrue(rec, row[BackupFileColumnIndex.restoreFinishDate], isSqlMiTarget);
|
||||
this._addItemIfTrue(rec, row[BackupFileColumnIndex.firstLsn], isSHIR);
|
||||
this._addItemIfTrue(rec, row[BackupFileColumnIndex.lastLsn], isSHIR);
|
||||
return rec;
|
||||
}) || [];
|
||||
}
|
||||
|
||||
protected async initialize(view: azdata.ModelView): Promise<void> {
|
||||
try {
|
||||
this._fileCount = this.view.modelBuilder.text()
|
||||
.withProps({
|
||||
width: '500px',
|
||||
CSSStyles: { ...styles.BODY_CSS }
|
||||
}).component();
|
||||
|
||||
this._fileTable = this.view.modelBuilder.table()
|
||||
.withProps({
|
||||
ariaLabel: loc.ACTIVE_BACKUP_FILES,
|
||||
CSSStyles: { 'padding': '0px' },
|
||||
forceFitColumns: azdata.ColumnSizingMode.ForceFit,
|
||||
display: 'inline-flex',
|
||||
data: [],
|
||||
height: '340px',
|
||||
columns: [],
|
||||
}).component();
|
||||
|
||||
const emptyTableImage = this.view.modelBuilder.image()
|
||||
.withProps({
|
||||
iconPath: IconPathHelper.emptyTable,
|
||||
iconHeight: '100px',
|
||||
iconWidth: '100px',
|
||||
height: '100px',
|
||||
width: '100px',
|
||||
CSSStyles: { 'text-align': 'center' }
|
||||
}).component();
|
||||
|
||||
const emptyTableText = this.view.modelBuilder.text()
|
||||
.withProps({
|
||||
value: loc.EMPTY_TABLE_TEXT,
|
||||
CSSStyles: {
|
||||
...styles.NOTE_CSS,
|
||||
'margin-top': '8px',
|
||||
'text-align': 'center',
|
||||
'width': '300px'
|
||||
}
|
||||
}).component();
|
||||
|
||||
this._emptyTableFill = this.view.modelBuilder.flexContainer()
|
||||
.withLayout({
|
||||
flexFlow: 'column',
|
||||
alignItems: 'center'
|
||||
}).withItems([
|
||||
emptyTableImage,
|
||||
emptyTableText,
|
||||
]).withProps({
|
||||
width: '100%',
|
||||
display: 'none'
|
||||
}).component();
|
||||
|
||||
const container = this.view.modelBuilder.flexContainer()
|
||||
.withItems([
|
||||
this.createMigrationToolbarContainer(),
|
||||
await this.migrationInfoGrid(),
|
||||
this.view.modelBuilder.separator()
|
||||
.withProps({
|
||||
width: '100%',
|
||||
CSSStyles: { 'padding': '0' }
|
||||
})
|
||||
.component(),
|
||||
this._fileCount,
|
||||
this._fileTable,
|
||||
this._emptyTableFill,
|
||||
], { CSSStyles: { 'padding': '0 15px 0 15px' } })
|
||||
.withLayout({ flexFlow: 'column', })
|
||||
.withProps({ width: '100%' })
|
||||
.component();
|
||||
|
||||
this.content = container;
|
||||
} catch (e) {
|
||||
logError(TelemetryViews.MigrationCutoverDialog, 'IntializingFailed', e);
|
||||
}
|
||||
}
|
||||
|
||||
protected override async migrationInfoGrid(): Promise<azdata.FlexContainer> {
|
||||
// left side
|
||||
this._sourceDatabaseInfoField = await this.createInfoField(loc.SOURCE_DATABASE, '');
|
||||
this._sourceDetailsInfoField = await this.createInfoField(loc.SOURCE_SERVER, '');
|
||||
this._migrationStatusInfoField = await this.createInfoField(loc.MIGRATION_STATUS, '', false, ' ');
|
||||
this._fullBackupFileOnInfoField = await this.createInfoField(loc.FULL_BACKUP_FILES, '', false);
|
||||
this._backupLocationInfoField = await this.createInfoField(loc.BACKUP_LOCATION, '');
|
||||
|
||||
// SQL MI provided
|
||||
this._lastUploadedFileNameField = await this.createInfoField(loc.FIELD_LABEL_LAST_UPLOADED_FILE, '');
|
||||
this._lastUploadedFileTimeField = await this.createInfoField(loc.FIELD_LABEL_LAST_UPLOADED_FILE_TIME, '');
|
||||
this._pendingDiffBackupsCountField = await this.createInfoField(loc.FIELD_LABEL_PENDING_DIFF_BACKUPS, '');
|
||||
this._detectedFilesField = await this.createInfoField(loc.FIELD_LABEL_DETECTED_FILES, '');
|
||||
this._queuedFilesField = await this.createInfoField(loc.FIELD_LABEL_QUEUED_FILES, '');
|
||||
this._skippedFilesField = await this.createInfoField(loc.FIELD_LABEL_SKIPPED_FILES, '');
|
||||
this._unrestorableFilesField = await this.createInfoField(loc.FIELD_LABEL_UNRESTORABLE_FILES, '');
|
||||
|
||||
// right side
|
||||
this._targetDatabaseInfoField = await this.createInfoField(loc.TARGET_DATABASE_NAME, '');
|
||||
this._targetServerInfoField = await this.createInfoField(loc.TARGET_SERVER, '');
|
||||
this._targetVersionInfoField = await this.createInfoField(loc.TARGET_VERSION, '');
|
||||
this._lastLSNInfoField = await this.createInfoField(loc.LAST_APPLIED_LSN, '', false);
|
||||
this._lastAppliedBackupInfoField = await this.createInfoField(loc.LAST_APPLIED_BACKUP_FILES, '');
|
||||
this._lastAppliedBackupTakenOnInfoField = await this.createInfoField(loc.LAST_APPLIED_BACKUP_FILES_TAKEN_ON, '', false);
|
||||
this._currentRestoringFileInfoField = await this.createInfoField(loc.CURRENTLY_RESTORING_FILE, '', false);
|
||||
|
||||
this._lastRestoredFileTimeInfoField = await this.createInfoField(loc.FIELD_LABEL_LAST_RESTORED_FILE_TIME, '', false);
|
||||
this._restoredFilesInfoField = await this.createInfoField(loc.FIELD_LABEL_RESTORED_FILES, '', false);
|
||||
this._restoringFilesInfoField = await this.createInfoField(loc.FIELD_LABEL_RESTORING_FILES, '', false);
|
||||
this._currentRestoredSizeInfoField = await this.createInfoField(loc.FIELD_LABEL_RESTORED_SIZE, '', false);
|
||||
this._currentRestorePlanSizeInfoField = await this.createInfoField(loc.FIELD_LABEL_RESTORE_PLAN_SIZE, '', false);
|
||||
this._restorePercentCompletedInfoField = await this.createInfoField(loc.FIELD_LABEL_RESTORE_PERCENT_COMPLETED, '', false);
|
||||
this._miRestoreStateInfoField = await this.createInfoField(loc.FIELD_LABEL_MI_RESTORE_STATE, '', false);
|
||||
|
||||
const leftSide = this.view.modelBuilder.flexContainer()
|
||||
.withLayout({ flexFlow: 'column' })
|
||||
.withProps({ width: '50%' })
|
||||
.withItems([
|
||||
this._sourceDatabaseInfoField.flexContainer,
|
||||
this._sourceDetailsInfoField.flexContainer,
|
||||
this._migrationStatusInfoField.flexContainer,
|
||||
this._fullBackupFileOnInfoField.flexContainer,
|
||||
this._backupLocationInfoField.flexContainer,
|
||||
this._lastUploadedFileNameField.flexContainer,
|
||||
this._lastUploadedFileTimeField.flexContainer,
|
||||
this._pendingDiffBackupsCountField.flexContainer,
|
||||
this._detectedFilesField.flexContainer,
|
||||
this._queuedFilesField.flexContainer,
|
||||
this._skippedFilesField.flexContainer,
|
||||
this._unrestorableFilesField.flexContainer,
|
||||
])
|
||||
.component();
|
||||
|
||||
const rightSide = this.view.modelBuilder.flexContainer()
|
||||
.withLayout({ flexFlow: 'column' })
|
||||
.withProps({ width: '50%' })
|
||||
.withItems([
|
||||
this._targetDatabaseInfoField.flexContainer,
|
||||
this._targetServerInfoField.flexContainer,
|
||||
this._targetVersionInfoField.flexContainer,
|
||||
this._lastLSNInfoField.flexContainer,
|
||||
this._lastAppliedBackupInfoField.flexContainer,
|
||||
this._lastAppliedBackupTakenOnInfoField.flexContainer,
|
||||
this._currentRestoringFileInfoField.flexContainer,
|
||||
this._lastRestoredFileTimeInfoField.flexContainer,
|
||||
this._restoredFilesInfoField.flexContainer,
|
||||
this._restoringFilesInfoField.flexContainer,
|
||||
this._currentRestoredSizeInfoField.flexContainer,
|
||||
this._currentRestorePlanSizeInfoField.flexContainer,
|
||||
this._restorePercentCompletedInfoField.flexContainer,
|
||||
this._miRestoreStateInfoField.flexContainer,
|
||||
])
|
||||
.component();
|
||||
|
||||
return this.view.modelBuilder.flexContainer()
|
||||
.withItems([leftSide, rightSide], { flex: '0 0 auto' })
|
||||
.withLayout({ flexFlow: 'row', flexWrap: 'wrap' })
|
||||
.component();
|
||||
}
|
||||
}
|
||||
@@ -8,17 +8,15 @@ import * as vscode from 'vscode';
|
||||
import { IconPathHelper } from '../constants/iconPathHelper';
|
||||
import { MigrationServiceContext } from '../models/migrationLocalStorage';
|
||||
import * as loc from '../constants/strings';
|
||||
import * as styles from '../constants/styles';
|
||||
import { DatabaseMigration, deleteMigration } from '../api/azure';
|
||||
import { TabBase } from './tabBase';
|
||||
import { MigrationCutoverDialogModel } from '../dialog/migrationCutover/migrationCutoverDialogModel';
|
||||
import { ConfirmCutoverDialog } from '../dialog/migrationCutover/confirmCutoverDialog';
|
||||
import { RetryMigrationDialog } from '../dialog/retryMigration/retryMigrationDialog';
|
||||
import { MigrationTargetType } from '../models/stateMachine';
|
||||
import { DashboardStatusBar } from './DashboardStatusBar';
|
||||
import { canDeleteMigration } from '../constants/helper';
|
||||
import { logError, TelemetryViews } from '../telemetry';
|
||||
import { MenuCommands } from '../api/utils';
|
||||
import { MenuCommands, MigrationTargetType } from '../api/utils';
|
||||
|
||||
export const infoFieldLgWidth: string = '330px';
|
||||
export const infoFieldWidth: string = '250px';
|
||||
@@ -26,9 +24,9 @@ export const infoFieldWidth: string = '250px';
|
||||
const statusImageSize: number = 14;
|
||||
|
||||
export const MigrationTargetTypeName: loc.LookupTable<string> = {
|
||||
[MigrationTargetType.SQLMI]: loc.AZURE_SQL_DATABASE_MANAGED_INSTANCE,
|
||||
[MigrationTargetType.SQLVM]: loc.AZURE_SQL_DATABASE_VIRTUAL_MACHINE,
|
||||
[MigrationTargetType.SQLDB]: loc.AZURE_SQL_DATABASE,
|
||||
[MigrationTargetType.SQLMI]: loc.SQL_MANAGED_INSTANCE,
|
||||
[MigrationTargetType.SQLVM]: loc.SQL_VIRTUAL_MACHINE,
|
||||
[MigrationTargetType.SQLDB]: loc.SQL_DATABASE,
|
||||
};
|
||||
|
||||
export interface InfoFieldSchema {
|
||||
@@ -70,7 +68,7 @@ export abstract class MigrationDetailsTabBase<T> extends TabBase<T> {
|
||||
migration: DatabaseMigration): Promise<void> {
|
||||
this.serviceContext = serviceContext;
|
||||
this.model = new MigrationCutoverDialogModel(serviceContext, migration);
|
||||
await this.refresh();
|
||||
await this.refresh(true);
|
||||
}
|
||||
|
||||
protected createBreadcrumbContainer(): azdata.FlexContainer {
|
||||
@@ -404,37 +402,55 @@ export abstract class MigrationDetailsTabBase<T> extends TabBase<T> {
|
||||
icon?: azdata.ImageComponent
|
||||
}> {
|
||||
const flexContainer = this.view.modelBuilder.flexContainer()
|
||||
.withProps({
|
||||
CSSStyles: {
|
||||
'flex-direction': 'column',
|
||||
'padding-right': '12px'
|
||||
}
|
||||
}).component();
|
||||
.withProps({ CSSStyles: { 'flex-direction': 'row', } })
|
||||
.component();
|
||||
|
||||
const labelComponent = this.view.modelBuilder.text()
|
||||
.withProps({
|
||||
value: label,
|
||||
title: label,
|
||||
width: '170px',
|
||||
CSSStyles: {
|
||||
...styles.LIGHT_LABEL_CSS,
|
||||
'margin-bottom': '0',
|
||||
'font-size': '13px',
|
||||
'line-height': '18px',
|
||||
'margin': '2px 0 2px 0',
|
||||
'float': 'left',
|
||||
'overflow': 'hidden',
|
||||
'text-overflow': 'ellipsis',
|
||||
'display': 'inline-block',
|
||||
'white-space': 'nowrap',
|
||||
}
|
||||
}).component();
|
||||
flexContainer.addItem(labelComponent);
|
||||
flexContainer.addItem(labelComponent, { flex: '0 0 auto' });
|
||||
|
||||
const separatorComponent = this.view.modelBuilder.text()
|
||||
.withProps({
|
||||
value: ':',
|
||||
title: ':',
|
||||
width: '15px',
|
||||
CSSStyles: {
|
||||
'font-size': '13px',
|
||||
'line-height': '18px',
|
||||
'margin': '2px 0 2px 0',
|
||||
'float': 'left',
|
||||
}
|
||||
}).component();
|
||||
flexContainer.addItem(separatorComponent, { flex: '0 0 auto' });
|
||||
|
||||
const textComponent = this.view.modelBuilder.text()
|
||||
.withProps({
|
||||
value: value,
|
||||
title: value,
|
||||
description: value,
|
||||
width: '100%',
|
||||
width: '300px',
|
||||
CSSStyles: {
|
||||
'font-size': '13px',
|
||||
'line-height': '18px',
|
||||
'margin': '4px 0 12px',
|
||||
'margin': '2px 15px 2px 0',
|
||||
'overflow': 'hidden',
|
||||
'text-overflow': 'ellipsis',
|
||||
'max-width': '230px',
|
||||
'display': 'inline-block',
|
||||
'float': 'left',
|
||||
'white-space': 'nowrap',
|
||||
}
|
||||
}).component();
|
||||
|
||||
@@ -449,25 +465,24 @@ export abstract class MigrationDetailsTabBase<T> extends TabBase<T> {
|
||||
width: statusImageSize,
|
||||
title: value,
|
||||
CSSStyles: {
|
||||
'margin': '7px 3px 0 0',
|
||||
'margin': '4px 4px 0 0',
|
||||
'padding': '0'
|
||||
}
|
||||
}).component();
|
||||
|
||||
const iconTextComponent = this.view.modelBuilder.flexContainer()
|
||||
.withItems([
|
||||
iconComponent,
|
||||
textComponent
|
||||
]).withProps({
|
||||
.withItems([iconComponent, textComponent])
|
||||
.withProps({
|
||||
CSSStyles: {
|
||||
'margin': '0',
|
||||
'padding': '0'
|
||||
'padding': '0',
|
||||
'height': '18px',
|
||||
},
|
||||
display: 'inline-flex'
|
||||
}).component();
|
||||
flexContainer.addItem(iconTextComponent);
|
||||
flexContainer.addItem(iconTextComponent, { flex: '0 0 auto' });
|
||||
} else {
|
||||
flexContainer.addItem(textComponent);
|
||||
flexContainer.addItem(textComponent, { flex: '0 0 auto' });
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -505,4 +520,13 @@ export abstract class MigrationDetailsTabBase<T> extends TabBase<T> {
|
||||
private _getMigrationDetails(): string {
|
||||
return JSON.stringify(this.model.migration, undefined, 2);
|
||||
}
|
||||
|
||||
protected _updateInfoFieldValue(info: InfoFieldSchema, value: string) {
|
||||
info.text.value = value;
|
||||
info.text.title = value;
|
||||
info.text.description = value;
|
||||
if (info.icon) {
|
||||
info.icon.title = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,15 +6,15 @@
|
||||
import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import * as loc from '../constants/strings';
|
||||
import { getSqlServerName, getMigrationStatusImage, getPipelineStatusImage, debounce } from '../api/utils';
|
||||
import { getMigrationStatusImage, getPipelineStatusImage } from '../api/utils';
|
||||
import { logError, TelemetryViews } from '../telemetry';
|
||||
import { canCancelMigration, canCutoverMigration, canDeleteMigration, canRetryMigration, formatDateTimeString, formatNumber, formatSizeBytes, formatSizeKb, formatTime, getMigrationStatusString, getMigrationTargetTypeEnum, isOfflineMigation, PipelineStatusCodes } from '../constants/helper';
|
||||
import { CopyProgressDetail, getResourceName } from '../api/azure';
|
||||
import { InfoFieldSchema, infoFieldLgWidth, MigrationDetailsTabBase, MigrationTargetTypeName } from './migrationDetailsTabBase';
|
||||
import { InfoFieldSchema, MigrationDetailsTabBase, MigrationTargetTypeName } from './migrationDetailsTabBase';
|
||||
import { IconPathHelper } from '../constants/iconPathHelper';
|
||||
import { EOL } from 'os';
|
||||
import { DashboardStatusBar } from './DashboardStatusBar';
|
||||
import { getSourceConnectionServerInfo } from '../api/sqlUtils';
|
||||
import { EmptySettingValue } from './tabBase';
|
||||
|
||||
const MigrationDetailsTableTabId = 'MigrationDetailsTableTab';
|
||||
|
||||
@@ -43,12 +43,12 @@ enum SummaryCardIndex {
|
||||
export class MigrationDetailsTableTab extends MigrationDetailsTabBase<MigrationDetailsTableTab> {
|
||||
private _sourceDatabaseInfoField!: InfoFieldSchema;
|
||||
private _sourceDetailsInfoField!: InfoFieldSchema;
|
||||
private _sourceVersionInfoField!: InfoFieldSchema;
|
||||
private _migrationStatusInfoField!: InfoFieldSchema;
|
||||
private _targetDatabaseInfoField!: InfoFieldSchema;
|
||||
private _targetServerInfoField!: InfoFieldSchema;
|
||||
private _targetVersionInfoField!: InfoFieldSchema;
|
||||
private _migrationStatusInfoField!: InfoFieldSchema;
|
||||
private _serverObjectsInfoField!: InfoFieldSchema;
|
||||
|
||||
private _tableFilterInputBox!: azdata.InputBoxComponent;
|
||||
private _columnSortDropdown!: azdata.DropDownComponent;
|
||||
private _columnSortCheckbox!: azdata.CheckBoxComponent;
|
||||
@@ -76,10 +76,10 @@ export class MigrationDetailsTableTab extends MigrationDetailsTabBase<MigrationD
|
||||
return this;
|
||||
}
|
||||
|
||||
@debounce(500)
|
||||
public async refresh(): Promise<void> {
|
||||
public async refresh(initialize?: boolean): Promise<void> {
|
||||
if (this.isRefreshing ||
|
||||
this.refreshLoader === undefined) {
|
||||
this.refreshLoader === undefined ||
|
||||
this.model?.migration === undefined) {
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -88,7 +88,9 @@ export class MigrationDetailsTableTab extends MigrationDetailsTabBase<MigrationD
|
||||
this.isRefreshing = true;
|
||||
this.refreshLoader.loading = true;
|
||||
await this.statusBar.clearError();
|
||||
|
||||
if (initialize) {
|
||||
await this._clearControlsValue();
|
||||
}
|
||||
await this.model.fetchStatus();
|
||||
await this._loadData();
|
||||
} catch (e) {
|
||||
@@ -102,118 +104,16 @@ export class MigrationDetailsTableTab extends MigrationDetailsTabBase<MigrationD
|
||||
}
|
||||
}
|
||||
|
||||
private async _loadData(): Promise<void> {
|
||||
const migration = this.model?.migration;
|
||||
await this.showMigrationErrors(this.model?.migration);
|
||||
|
||||
await this.cutoverButton.updateCssStyles(
|
||||
{ 'display': isOfflineMigation(migration) ? 'none' : 'block' });
|
||||
|
||||
const sqlServerName = migration?.properties.sourceServerName;
|
||||
const sourceDatabaseName = migration?.properties.sourceDatabaseName;
|
||||
const sqlServerInfo = await getSourceConnectionServerInfo();
|
||||
const versionName = getSqlServerName(sqlServerInfo.serverMajorVersion!);
|
||||
const sqlServerVersion = versionName ? versionName : sqlServerInfo.serverVersion;
|
||||
const targetDatabaseName = migration?.name;
|
||||
const targetServerName = getResourceName(migration?.properties.scope);
|
||||
|
||||
const targetType = getMigrationTargetTypeEnum(migration);
|
||||
const targetServerVersion = MigrationTargetTypeName[targetType ?? ''];
|
||||
|
||||
this._progressDetail = migration?.properties.migrationStatusDetails?.listOfCopyProgressDetails ?? [];
|
||||
|
||||
const hashSet: loc.LookupTable<number> = {};
|
||||
await this._populateTableData(hashSet);
|
||||
|
||||
const successCount = hashSet[PipelineStatusCodes.Succeeded] ?? 0;
|
||||
const cancelledCount =
|
||||
(hashSet[PipelineStatusCodes.Canceled] ?? 0) +
|
||||
(hashSet[PipelineStatusCodes.Cancelled] ?? 0);
|
||||
|
||||
const failedCount = hashSet[PipelineStatusCodes.Failed] ?? 0;
|
||||
const inProgressCount =
|
||||
(hashSet[PipelineStatusCodes.Queued] ?? 0) +
|
||||
(hashSet[PipelineStatusCodes.CopyFinished] ?? 0) +
|
||||
(hashSet[PipelineStatusCodes.Copying] ?? 0) +
|
||||
(hashSet[PipelineStatusCodes.PreparingForCopy] ?? 0) +
|
||||
(hashSet[PipelineStatusCodes.RebuildingIndexes] ?? 0) +
|
||||
(hashSet[PipelineStatusCodes.InProgress] ?? 0);
|
||||
|
||||
const totalCount = this._progressDetail.length;
|
||||
|
||||
this._updateSummaryComponent(SummaryCardIndex.TotalTables, totalCount);
|
||||
this._updateSummaryComponent(SummaryCardIndex.InProgressTables, inProgressCount);
|
||||
this._updateSummaryComponent(SummaryCardIndex.SuccessfulTables, successCount);
|
||||
this._updateSummaryComponent(SummaryCardIndex.FailedTables, failedCount);
|
||||
this._updateSummaryComponent(SummaryCardIndex.CanceledTables, cancelledCount);
|
||||
|
||||
this.databaseLabel.value = sourceDatabaseName;
|
||||
this._sourceDatabaseInfoField.text.value = sourceDatabaseName;
|
||||
this._sourceDetailsInfoField.text.value = sqlServerName;
|
||||
this._sourceVersionInfoField.text.value = `${sqlServerVersion} ${sqlServerInfo.serverVersion}`;
|
||||
|
||||
this._targetDatabaseInfoField.text.value = targetDatabaseName;
|
||||
this._targetServerInfoField.text.value = targetServerName;
|
||||
this._targetVersionInfoField.text.value = targetServerVersion;
|
||||
|
||||
this._migrationStatusInfoField.text.value = getMigrationStatusString(migration);
|
||||
this._migrationStatusInfoField.icon!.iconPath = getMigrationStatusImage(migration);
|
||||
this._serverObjectsInfoField.text.value = totalCount.toLocaleString();
|
||||
|
||||
this.cutoverButton.enabled = canCutoverMigration(migration);
|
||||
this.cancelButton.enabled = canCancelMigration(migration);
|
||||
this.deleteButton.enabled = canDeleteMigration(migration);
|
||||
this.retryButton.enabled = canRetryMigration(migration);
|
||||
}
|
||||
|
||||
private async _populateTableData(hashSet: loc.LookupTable<number> = {}): Promise<void> {
|
||||
if (this._progressTable.data.length > 0) {
|
||||
await this._progressTable.updateProperty('data', []);
|
||||
}
|
||||
|
||||
// Sort table data
|
||||
this._sortTableMigrations(
|
||||
this._progressDetail,
|
||||
(<azdata.CategoryValue>this._columnSortDropdown.value).name,
|
||||
this._columnSortCheckbox.checked === true);
|
||||
|
||||
const data = this._progressDetail.map((d) => {
|
||||
hashSet[d.status] = (hashSet[d.status] ?? 0) + 1;
|
||||
return [
|
||||
d.tableName,
|
||||
<azdata.HyperlinkColumnCellValue>{
|
||||
icon: getPipelineStatusImage(d.status),
|
||||
title: loc.PipelineRunStatus[d.status] ?? d.status?.toUpperCase(),
|
||||
},
|
||||
formatSizeBytes(d.dataRead),
|
||||
formatSizeBytes(d.dataWritten),
|
||||
formatNumber(d.rowsRead),
|
||||
formatNumber(d.rowsCopied),
|
||||
formatSizeKb(d.copyThroughput),
|
||||
formatTime((d.copyDuration ?? 0) * 1000),
|
||||
loc.ParallelCopyType[d.parallelCopyType] ?? d.parallelCopyType,
|
||||
d.usedParallelCopies,
|
||||
formatDateTimeString(d.copyStart),
|
||||
];
|
||||
}) ?? [];
|
||||
|
||||
// Filter tableData
|
||||
const filteredData = this._filterTables(data, this._tableFilterInputBox.value);
|
||||
|
||||
await this._progressTable.updateProperty('data', filteredData);
|
||||
}
|
||||
|
||||
protected async initialize(view: azdata.ModelView): Promise<void> {
|
||||
try {
|
||||
this._progressTable = this.view.modelBuilder.table()
|
||||
.withProps({
|
||||
ariaLabel: loc.ACTIVE_BACKUP_FILES,
|
||||
CSSStyles: {
|
||||
'padding-left': '0px',
|
||||
'max-width': '1111px'
|
||||
},
|
||||
CSSStyles: { 'padding-left': '0px' },
|
||||
forceFitColumns: azdata.ColumnSizingMode.ForceFit,
|
||||
display: 'inline-flex',
|
||||
data: [],
|
||||
height: '300px',
|
||||
height: '340px',
|
||||
columns: [
|
||||
{
|
||||
value: TableColumns.tableName,
|
||||
@@ -286,18 +186,23 @@ export class MigrationDetailsTableTab extends MigrationDetailsTabBase<MigrationD
|
||||
],
|
||||
}).component();
|
||||
|
||||
const formItems: azdata.FormComponent<azdata.Component>[] = [
|
||||
{ component: this.createMigrationToolbarContainer() },
|
||||
{ component: await this.migrationInfoGrid() },
|
||||
{
|
||||
component: this.view.modelBuilder.separator()
|
||||
.withProps({ width: '100%', CSSStyles: { 'padding': '0' } })
|
||||
.component()
|
||||
},
|
||||
{ component: await this._createStatusBar() },
|
||||
{ component: await this._createTableFilter() },
|
||||
{ component: this._progressTable },
|
||||
];
|
||||
const container = this.view.modelBuilder.flexContainer()
|
||||
.withItems([
|
||||
this.createMigrationToolbarContainer(),
|
||||
await this.migrationInfoGrid(),
|
||||
this.view.modelBuilder.separator()
|
||||
.withProps({
|
||||
width: '100%',
|
||||
CSSStyles: { 'padding': '0' }
|
||||
})
|
||||
.component(),
|
||||
await this._createStatusBar(),
|
||||
await this._createTableFilter(),
|
||||
this._progressTable,
|
||||
], { CSSStyles: { 'padding': '15px 15px 0 15px' } })
|
||||
.withLayout({ flexFlow: 'column', })
|
||||
.withProps({ width: '100%' })
|
||||
.component();
|
||||
|
||||
this.disposables.push(
|
||||
this._progressTable.onCellAction!(
|
||||
@@ -319,19 +224,159 @@ export class MigrationDetailsTableTab extends MigrationDetailsTabBase<MigrationD
|
||||
}
|
||||
}));
|
||||
|
||||
const formContainer = this.view.modelBuilder.formContainer()
|
||||
.withFormItems(
|
||||
formItems,
|
||||
{ horizontal: false })
|
||||
.withProps({ width: '100%', CSSStyles: { margin: '0 0 0 5px', padding: '0 15px 0 15px' } })
|
||||
.component();
|
||||
|
||||
this.content = formContainer;
|
||||
this.content = container;
|
||||
} catch (e) {
|
||||
logError(TelemetryViews.MigrationCutoverDialog, 'IntializingFailed', e);
|
||||
}
|
||||
}
|
||||
|
||||
protected override async migrationInfoGrid(): Promise<azdata.FlexContainer> {
|
||||
// left side
|
||||
this._sourceDatabaseInfoField = await this.createInfoField(loc.SOURCE_DATABASE, '');
|
||||
this._sourceDetailsInfoField = await this.createInfoField(loc.SOURCE_SERVER, '');
|
||||
this._migrationStatusInfoField = await this.createInfoField(loc.MIGRATION_STATUS, '', false, ' ');
|
||||
|
||||
// right side
|
||||
this._targetDatabaseInfoField = await this.createInfoField(loc.TARGET_DATABASE_NAME, '');
|
||||
this._targetServerInfoField = await this.createInfoField(loc.TARGET_SERVER, '');
|
||||
this._targetVersionInfoField = await this.createInfoField(loc.TARGET_VERSION, '');
|
||||
this._serverObjectsInfoField = await this.createInfoField(loc.SERVER_OBJECTS_FIELD_LABEL, '');
|
||||
|
||||
const leftSide = this.view.modelBuilder.flexContainer()
|
||||
.withLayout({ flexFlow: 'column' })
|
||||
.withProps({ width: '50%' })
|
||||
.withItems([
|
||||
this._sourceDatabaseInfoField.flexContainer,
|
||||
this._sourceDetailsInfoField.flexContainer,
|
||||
this._migrationStatusInfoField.flexContainer,
|
||||
])
|
||||
.component();
|
||||
|
||||
const rightSide = this.view.modelBuilder.flexContainer()
|
||||
.withLayout({ flexFlow: 'column' })
|
||||
.withProps({ width: '50%' })
|
||||
.withItems([
|
||||
this._targetDatabaseInfoField.flexContainer,
|
||||
this._targetServerInfoField.flexContainer,
|
||||
this._targetVersionInfoField.flexContainer,
|
||||
this._serverObjectsInfoField.flexContainer,
|
||||
])
|
||||
.component();
|
||||
|
||||
return this.view.modelBuilder.flexContainer()
|
||||
.withItems([leftSide, rightSide], { flex: '0 0 auto' })
|
||||
.withLayout({ flexFlow: 'row', flexWrap: 'wrap' })
|
||||
.component();
|
||||
}
|
||||
|
||||
private async _loadData(): Promise<void> {
|
||||
const migration = this.model?.migration;
|
||||
await this.showMigrationErrors(this.model?.migration);
|
||||
|
||||
await this.cutoverButton.updateCssStyles(
|
||||
{ 'display': isOfflineMigation(migration) ? 'none' : 'block' });
|
||||
|
||||
const sqlServerName = migration?.properties.sourceServerName;
|
||||
const sourceDatabaseName = migration?.properties.sourceDatabaseName;
|
||||
const targetDatabaseName = migration?.name;
|
||||
const targetServerName = getResourceName(migration?.properties.scope);
|
||||
|
||||
const targetType = getMigrationTargetTypeEnum(migration);
|
||||
const targetServerVersion = MigrationTargetTypeName[targetType ?? ''];
|
||||
|
||||
this._progressDetail = migration?.properties.migrationStatusDetails?.listOfCopyProgressDetails ?? [];
|
||||
|
||||
const hashSet: loc.LookupTable<number> = {};
|
||||
await this._populateTableData(hashSet);
|
||||
|
||||
const successCount = hashSet[PipelineStatusCodes.Succeeded] ?? 0;
|
||||
const cancelledCount =
|
||||
(hashSet[PipelineStatusCodes.Canceled] ?? 0) +
|
||||
(hashSet[PipelineStatusCodes.Cancelled] ?? 0);
|
||||
|
||||
const failedCount = hashSet[PipelineStatusCodes.Failed] ?? 0;
|
||||
const inProgressCount =
|
||||
(hashSet[PipelineStatusCodes.Queued] ?? 0) +
|
||||
(hashSet[PipelineStatusCodes.CopyFinished] ?? 0) +
|
||||
(hashSet[PipelineStatusCodes.Copying] ?? 0) +
|
||||
(hashSet[PipelineStatusCodes.PreparingForCopy] ?? 0) +
|
||||
(hashSet[PipelineStatusCodes.RebuildingIndexes] ?? 0) +
|
||||
(hashSet[PipelineStatusCodes.InProgress] ?? 0);
|
||||
|
||||
const totalCount = this._progressDetail.length;
|
||||
|
||||
this._updateSummaryComponent(SummaryCardIndex.TotalTables, totalCount);
|
||||
this._updateSummaryComponent(SummaryCardIndex.InProgressTables, inProgressCount);
|
||||
this._updateSummaryComponent(SummaryCardIndex.SuccessfulTables, successCount);
|
||||
this._updateSummaryComponent(SummaryCardIndex.FailedTables, failedCount);
|
||||
this._updateSummaryComponent(SummaryCardIndex.CanceledTables, cancelledCount);
|
||||
|
||||
this.databaseLabel.value = sourceDatabaseName;
|
||||
// Left side
|
||||
this._updateInfoFieldValue(this._sourceDatabaseInfoField, sourceDatabaseName ?? EmptySettingValue);
|
||||
this._updateInfoFieldValue(this._sourceDetailsInfoField, sqlServerName ?? EmptySettingValue);
|
||||
this._updateInfoFieldValue(this._migrationStatusInfoField, getMigrationStatusString(migration) ?? EmptySettingValue);
|
||||
this._migrationStatusInfoField.icon!.iconPath = getMigrationStatusImage(migration);
|
||||
|
||||
// Right side
|
||||
this._updateInfoFieldValue(this._targetDatabaseInfoField, targetDatabaseName ?? EmptySettingValue);
|
||||
this._updateInfoFieldValue(this._targetServerInfoField, targetServerName ?? EmptySettingValue);
|
||||
this._updateInfoFieldValue(this._targetVersionInfoField, targetServerVersion ?? EmptySettingValue);
|
||||
this._updateInfoFieldValue(this._serverObjectsInfoField, totalCount.toLocaleString() ?? EmptySettingValue);
|
||||
|
||||
this.cutoverButton.enabled = canCutoverMigration(migration);
|
||||
this.cancelButton.enabled = canCancelMigration(migration);
|
||||
this.deleteButton.enabled = canDeleteMigration(migration);
|
||||
this.retryButton.enabled = canRetryMigration(migration);
|
||||
}
|
||||
|
||||
private async _populateTableData(hashSet: loc.LookupTable<number> = {}): Promise<void> {
|
||||
if (this._progressTable.data.length > 0) {
|
||||
await this._progressTable.updateProperty('data', []);
|
||||
}
|
||||
|
||||
// Sort table data
|
||||
this._sortTableMigrations(
|
||||
this._progressDetail,
|
||||
(<azdata.CategoryValue>this._columnSortDropdown.value).name,
|
||||
this._columnSortCheckbox.checked === true);
|
||||
|
||||
const data = this._progressDetail.map((d) => {
|
||||
hashSet[d.status] = (hashSet[d.status] ?? 0) + 1;
|
||||
return [
|
||||
d.tableName,
|
||||
<azdata.HyperlinkColumnCellValue>{
|
||||
icon: getPipelineStatusImage(d.status),
|
||||
title: loc.PipelineRunStatus[d.status] ?? d.status?.toUpperCase(),
|
||||
},
|
||||
formatSizeBytes(d.dataRead),
|
||||
formatSizeBytes(d.dataWritten),
|
||||
formatNumber(d.rowsRead),
|
||||
formatNumber(d.rowsCopied),
|
||||
formatSizeKb(d.copyThroughput),
|
||||
formatTime((d.copyDuration ?? 0) * 1000),
|
||||
loc.ParallelCopyType[d.parallelCopyType] ?? d.parallelCopyType,
|
||||
d.usedParallelCopies,
|
||||
formatDateTimeString(d.copyStart),
|
||||
];
|
||||
}) ?? [];
|
||||
|
||||
// Filter tableData
|
||||
const filteredData = this._filterTables(data, this._tableFilterInputBox.value);
|
||||
await this._progressTable.updateProperty('data', filteredData);
|
||||
}
|
||||
|
||||
private _clearControlsValue(): void {
|
||||
this._updateInfoFieldValue(this._sourceDatabaseInfoField, '');
|
||||
this._updateInfoFieldValue(this._sourceDetailsInfoField, '');
|
||||
this._updateInfoFieldValue(this._migrationStatusInfoField, '');
|
||||
|
||||
this._updateInfoFieldValue(this._targetDatabaseInfoField, '');
|
||||
this._updateInfoFieldValue(this._targetServerInfoField, '');
|
||||
this._updateInfoFieldValue(this._targetVersionInfoField, '');
|
||||
this._updateInfoFieldValue(this._serverObjectsInfoField, '');
|
||||
}
|
||||
|
||||
private _sortTableMigrations(data: CopyProgressDetail[], columnName: string, ascending: boolean): void {
|
||||
const sortDir = ascending ? -1 : 1;
|
||||
switch (columnName) {
|
||||
@@ -515,55 +560,4 @@ export class MigrationDetailsTableTab extends MigrationDetailsTabBase<MigrationD
|
||||
.withLayout({ flexFlow: 'column' })
|
||||
.component();
|
||||
}
|
||||
|
||||
protected async migrationInfoGrid(): Promise<azdata.FlexContainer> {
|
||||
const addInfoFieldToContainer = (infoField: InfoFieldSchema, container: azdata.FlexContainer): void => {
|
||||
container.addItem(
|
||||
infoField.flexContainer,
|
||||
{ CSSStyles: { width: infoFieldLgWidth } });
|
||||
};
|
||||
|
||||
const flexServer = this.view.modelBuilder.flexContainer()
|
||||
.withLayout({ flexFlow: 'column' })
|
||||
.component();
|
||||
this._sourceDatabaseInfoField = await this.createInfoField(loc.SOURCE_DATABASE, '');
|
||||
this._sourceDetailsInfoField = await this.createInfoField(loc.SOURCE_SERVER, '');
|
||||
this._sourceVersionInfoField = await this.createInfoField(loc.SOURCE_VERSION, '');
|
||||
addInfoFieldToContainer(this._sourceDatabaseInfoField, flexServer);
|
||||
addInfoFieldToContainer(this._sourceDetailsInfoField, flexServer);
|
||||
addInfoFieldToContainer(this._sourceVersionInfoField, flexServer);
|
||||
|
||||
const flexTarget = this.view.modelBuilder.flexContainer()
|
||||
.withLayout({ flexFlow: 'column' })
|
||||
.component();
|
||||
this._targetDatabaseInfoField = await this.createInfoField(loc.TARGET_DATABASE_NAME, '');
|
||||
this._targetServerInfoField = await this.createInfoField(loc.TARGET_SERVER, '');
|
||||
this._targetVersionInfoField = await this.createInfoField(loc.TARGET_VERSION, '');
|
||||
addInfoFieldToContainer(this._targetDatabaseInfoField, flexTarget);
|
||||
addInfoFieldToContainer(this._targetServerInfoField, flexTarget);
|
||||
addInfoFieldToContainer(this._targetVersionInfoField, flexTarget);
|
||||
|
||||
const flexStatus = this.view.modelBuilder.flexContainer()
|
||||
.withLayout({ flexFlow: 'column' })
|
||||
.component();
|
||||
this._migrationStatusInfoField = await this.createInfoField(loc.MIGRATION_STATUS, '', false, ' ');
|
||||
this._serverObjectsInfoField = await this.createInfoField(loc.SERVER_OBJECTS_FIELD_LABEL, '');
|
||||
addInfoFieldToContainer(this._migrationStatusInfoField, flexStatus);
|
||||
addInfoFieldToContainer(this._serverObjectsInfoField, flexStatus);
|
||||
|
||||
const flexInfoProps = {
|
||||
flex: '0',
|
||||
CSSStyles: { 'flex': '0', 'width': infoFieldLgWidth }
|
||||
};
|
||||
|
||||
const flexInfo = this.view.modelBuilder.flexContainer()
|
||||
.withLayout({ flexWrap: 'wrap' })
|
||||
.withProps({ width: '100%' })
|
||||
.component();
|
||||
flexInfo.addItem(flexServer, flexInfoProps);
|
||||
flexInfo.addItem(flexTarget, flexInfoProps);
|
||||
flexInfo.addItem(flexStatus, flexInfoProps);
|
||||
|
||||
return flexInfo;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,7 +166,8 @@ export class MigrationsListTab extends TabBase<MigrationsListTab> {
|
||||
description: loc.MIGRATION_SERVICE_DESCRIPTION,
|
||||
buttonType: azdata.ButtonType.Informational,
|
||||
width: 230,
|
||||
}).component();
|
||||
})
|
||||
.component();
|
||||
|
||||
this.disposables.push(
|
||||
this._serviceContextButton.onDidClick(
|
||||
|
||||
@@ -12,8 +12,7 @@ import { DatabaseMigration, getMigrationDetails } from '../api/azure';
|
||||
import { MigrationLocalStorage } from '../models/migrationLocalStorage';
|
||||
import { FileStorageType } from '../models/stateMachine';
|
||||
import { MigrationDetailsTabBase } from './migrationDetailsTabBase';
|
||||
import { MigrationDetailsFileShareTab } from './migrationDetailsFileShareTab';
|
||||
import { MigrationDetailsBlobContainerTab } from './migrationDetailsBlobContainerTab';
|
||||
import { MigrationDetailsTab } from './migrationDetailsTab';
|
||||
import { MigrationDetailsTableTab } from './migrationDetailsTableTab';
|
||||
import { DashboardStatusBar } from './DashboardStatusBar';
|
||||
import { getSourceConnectionId } from '../api/sqlUtils';
|
||||
@@ -23,9 +22,8 @@ export const MigrationsTabId = 'MigrationsTab';
|
||||
export class MigrationsTab extends TabBase<MigrationsTab> {
|
||||
private _tab!: azdata.DivContainer;
|
||||
private _migrationsListTab!: MigrationsListTab;
|
||||
private _migrationDetailsViewTab!: MigrationDetailsTabBase<any>;
|
||||
private _migrationDetailsTab!: MigrationDetailsTabBase<any>;
|
||||
private _migrationDetailsFileShareTab!: MigrationDetailsTabBase<any>;
|
||||
private _migrationDetailsBlobTab!: MigrationDetailsTabBase<any>;
|
||||
private _migrationDetailsTableTab!: MigrationDetailsTabBase<any>;
|
||||
private _selectedTabId: string | undefined = undefined;
|
||||
private _migrationDetailsEvent!: vscode.EventEmitter<MigrationDetailsEvent>;
|
||||
@@ -61,7 +59,7 @@ export class MigrationsTab extends TabBase<MigrationsTab> {
|
||||
case MigrationsListTabId:
|
||||
return this._migrationsListTab.refresh();
|
||||
default:
|
||||
return this._migrationDetailsTab.refresh();
|
||||
return this._migrationDetailsViewTab.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,26 +91,19 @@ export class MigrationsTab extends TabBase<MigrationsTab> {
|
||||
}
|
||||
};
|
||||
|
||||
this._migrationDetailsBlobTab = await new MigrationDetailsBlobContainerTab().create(
|
||||
this._migrationDetailsTab = await new MigrationDetailsTab().create(
|
||||
this.context,
|
||||
this.view,
|
||||
openMigrationsListTab,
|
||||
this.statusBar);
|
||||
this.disposables.push(this._migrationDetailsBlobTab);
|
||||
|
||||
this._migrationDetailsFileShareTab = await new MigrationDetailsFileShareTab().create(
|
||||
this.context,
|
||||
this.view,
|
||||
openMigrationsListTab,
|
||||
this.statusBar);
|
||||
this.disposables.push(this._migrationDetailsFileShareTab);
|
||||
this.disposables.push(this._migrationDetailsTab);
|
||||
|
||||
this._migrationDetailsTableTab = await new MigrationDetailsTableTab().create(
|
||||
this.context,
|
||||
this.view,
|
||||
openMigrationsListTab,
|
||||
this.statusBar);
|
||||
this.disposables.push(this._migrationDetailsFileShareTab);
|
||||
this.disposables.push(this._migrationDetailsTableTab);
|
||||
|
||||
this.disposables.push(
|
||||
this._migrationDetailsEvent.event(async e => {
|
||||
@@ -135,22 +126,20 @@ export class MigrationsTab extends TabBase<MigrationsTab> {
|
||||
public async openMigrationDetails(migration: DatabaseMigration): Promise<void> {
|
||||
switch (migration.properties.backupConfiguration?.sourceLocation?.fileStorageType) {
|
||||
case FileStorageType.AzureBlob:
|
||||
this._migrationDetailsTab = this._migrationDetailsBlobTab;
|
||||
break;
|
||||
case FileStorageType.FileShare:
|
||||
this._migrationDetailsTab = this._migrationDetailsFileShareTab;
|
||||
this._migrationDetailsViewTab = this._migrationDetailsTab;
|
||||
break;
|
||||
case FileStorageType.None:
|
||||
this._migrationDetailsTab = this._migrationDetailsTableTab;
|
||||
this._migrationDetailsViewTab = this._migrationDetailsTableTab;
|
||||
break;
|
||||
}
|
||||
|
||||
await this._migrationDetailsTab.setMigrationContext(
|
||||
await this._migrationDetailsViewTab.setMigrationContext(
|
||||
await MigrationLocalStorage.getMigrationServiceContext(),
|
||||
migration);
|
||||
|
||||
const promise = this._migrationDetailsTab.refresh();
|
||||
await this._openTab(this._migrationDetailsTab);
|
||||
const promise = this._migrationDetailsViewTab.refresh();
|
||||
await this._openTab(this._migrationDetailsViewTab);
|
||||
await promise;
|
||||
}
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ export abstract class TabBase<T> implements azdata.Tab, vscode.Disposable {
|
||||
|
||||
protected abstract initialize(view: azdata.ModelView): Promise<void>;
|
||||
|
||||
public abstract refresh(): Promise<void>;
|
||||
public abstract refresh(initialize?: boolean): Promise<void>;
|
||||
|
||||
dispose() {
|
||||
this.disposables.forEach(
|
||||
|
||||
@@ -5,11 +5,12 @@
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import { MigrationStateModel, MigrationTargetType } from '../../models/stateMachine';
|
||||
import { MigrationStateModel } from '../../models/stateMachine';
|
||||
import { SqlDatabaseTree } from './sqlDatabasesTree';
|
||||
import { SKURecommendationPage } from '../../wizard/skuRecommendationPage';
|
||||
import * as constants from '../../constants/strings';
|
||||
import * as utils from '../../api/utils';
|
||||
import { MigrationTargetType } from '../../api/utils';
|
||||
import * as fs from 'fs';
|
||||
import path = require('path');
|
||||
import { SqlMigrationImpactedObjectInfo } from '../../service/contracts';
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import { MigrationStateModel, MigrationTargetType } from '../../models/stateMachine';
|
||||
import { MigrationStateModel } from '../../models/stateMachine';
|
||||
import * as constants from '../../constants/strings';
|
||||
import { debounce } from '../../api/utils';
|
||||
import { debounce, MigrationTargetType } from '../../api/utils';
|
||||
import { IconPath, IconPathHelper } from '../../constants/iconPathHelper';
|
||||
import * as styles from '../../constants/styles';
|
||||
import { EOL } from 'os';
|
||||
|
||||
@@ -9,10 +9,10 @@ import { MigrationCutoverDialogModel } from './migrationCutoverDialogModel';
|
||||
import * as constants from '../../constants/strings';
|
||||
import { getMigrationTargetInstance, SqlManagedInstance } from '../../api/azure';
|
||||
import { IconPathHelper } from '../../constants/iconPathHelper';
|
||||
import { convertByteSizeToReadableUnit, get12HourTime } from '../../api/utils';
|
||||
import { convertByteSizeToReadableUnit, get12HourTime, MigrationTargetType } from '../../api/utils';
|
||||
import * as styles from '../../constants/styles';
|
||||
import { getMigrationTargetTypeEnum, isBlobMigration } from '../../constants/helper';
|
||||
import { MigrationTargetType, ServiceTier } from '../../models/stateMachine';
|
||||
import { ServiceTier } from '../../models/stateMachine';
|
||||
export class ConfirmCutoverDialog {
|
||||
private _dialogObject!: azdata.window.Dialog;
|
||||
private _view!: azdata.ModelView;
|
||||
@@ -79,8 +79,8 @@ export class ConfirmCutoverDialog {
|
||||
let infoDisplay = 'none';
|
||||
if (getMigrationTargetTypeEnum(this.migrationCutoverModel.migration) === MigrationTargetType.SQLMI) {
|
||||
const targetInstance = await getMigrationTargetInstance(
|
||||
this.migrationCutoverModel.serviceConstext.azureAccount!,
|
||||
this.migrationCutoverModel.serviceConstext.subscription!,
|
||||
this.migrationCutoverModel.serviceContext.azureAccount!,
|
||||
this.migrationCutoverModel.serviceContext.subscription!,
|
||||
this.migrationCutoverModel.migration);
|
||||
|
||||
if ((<SqlManagedInstance>targetInstance)?.sku?.tier === ServiceTier.BusinessCritical) {
|
||||
|
||||
@@ -14,23 +14,26 @@ export class MigrationCutoverDialogModel {
|
||||
public CancelMigrationError?: Error;
|
||||
|
||||
constructor(
|
||||
public serviceConstext: MigrationServiceContext,
|
||||
public serviceContext: MigrationServiceContext,
|
||||
public migration: DatabaseMigration) { }
|
||||
|
||||
public async fetchStatus(): Promise<void> {
|
||||
const migrationStatus = await getMigrationDetails(
|
||||
this.serviceConstext.azureAccount!,
|
||||
this.serviceConstext.subscription!,
|
||||
this.migration.id,
|
||||
this.migration.properties?.migrationOperationId);
|
||||
|
||||
sendSqlMigrationActionEvent(
|
||||
TelemetryViews.MigrationCutoverDialog,
|
||||
TelemetryAction.MigrationStatus,
|
||||
{ 'migrationStatus': migrationStatus.properties?.migrationStatus },
|
||||
{});
|
||||
|
||||
this.migration = migrationStatus;
|
||||
try {
|
||||
const migrationStatus = await getMigrationDetails(
|
||||
this.serviceContext.azureAccount!,
|
||||
this.serviceContext.subscription!,
|
||||
this.migration.id,
|
||||
this.migration.properties?.migrationOperationId);
|
||||
this.migration = migrationStatus;
|
||||
} catch (error) {
|
||||
logError(TelemetryViews.MigrationDetailsTab, 'fetchStatus', error);
|
||||
} finally {
|
||||
sendSqlMigrationActionEvent(
|
||||
TelemetryViews.MigrationDetailsTab,
|
||||
TelemetryAction.MigrationStatus,
|
||||
{ 'migrationStatus': this.migration.properties?.migrationStatus },
|
||||
{});
|
||||
}
|
||||
}
|
||||
|
||||
public async startCutover(): Promise<DatabaseMigration | undefined> {
|
||||
@@ -38,14 +41,14 @@ export class MigrationCutoverDialogModel {
|
||||
this.CutoverError = undefined;
|
||||
if (this.migration) {
|
||||
const cutover = await startMigrationCutover(
|
||||
this.serviceConstext.azureAccount!,
|
||||
this.serviceConstext.subscription!,
|
||||
this.serviceContext.azureAccount!,
|
||||
this.serviceContext.subscription!,
|
||||
this.migration!);
|
||||
sendSqlMigrationActionEvent(
|
||||
TelemetryViews.MigrationCutoverDialog,
|
||||
TelemetryAction.CutoverMigration,
|
||||
{
|
||||
...this.getTelemetryProps(this.serviceConstext, this.migration),
|
||||
...this.getTelemetryProps(this.serviceContext, this.migration),
|
||||
'migrationEndTime': new Date().toString(),
|
||||
},
|
||||
{}
|
||||
@@ -65,14 +68,14 @@ export class MigrationCutoverDialogModel {
|
||||
if (this.migration) {
|
||||
const cutoverStartTime = new Date().toString();
|
||||
await stopMigration(
|
||||
this.serviceConstext.azureAccount!,
|
||||
this.serviceConstext.subscription!,
|
||||
this.serviceContext.azureAccount!,
|
||||
this.serviceContext.subscription!,
|
||||
this.migration);
|
||||
sendSqlMigrationActionEvent(
|
||||
TelemetryViews.MigrationCutoverDialog,
|
||||
TelemetryAction.CancelMigration,
|
||||
{
|
||||
...this.getTelemetryProps(this.serviceConstext, this.migration),
|
||||
...this.getTelemetryProps(this.serviceContext, this.migration),
|
||||
'migrationMode': getMigrationMode(this.migration),
|
||||
'cutoverStartTime': cutoverStartTime,
|
||||
},
|
||||
|
||||
@@ -5,11 +5,12 @@
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import { MigrationStateModel, MigrationTargetType } from '../../models/stateMachine';
|
||||
import { MigrationStateModel } from '../../models/stateMachine';
|
||||
import * as constants from '../../constants/strings';
|
||||
import * as contracts from '../../service/contracts';
|
||||
import * as styles from '../../constants/styles';
|
||||
import * as utils from '../../api/utils';
|
||||
import { MigrationTargetType } from '../../api/utils';
|
||||
import * as fs from 'fs';
|
||||
import path = require('path');
|
||||
|
||||
|
||||
@@ -7,11 +7,12 @@ import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import * as constants from '../../constants/strings';
|
||||
import { validateIrDatabaseMigrationSettings, validateIrSqlDatabaseMigrationSettings } from '../../api/azure';
|
||||
import { MigrationStateModel, MigrationTargetType, NetworkShare, ValidateIrState, ValidationResult } from '../../models/stateMachine';
|
||||
import { MigrationStateModel, NetworkShare, ValidateIrState, ValidationResult } from '../../models/stateMachine';
|
||||
import { EOL } from 'os';
|
||||
import { IconPathHelper } from '../../constants/iconPathHelper';
|
||||
import { getEncryptConnectionValue, getSourceConnectionProfile, getTrustServerCertificateValue } from '../../api/sqlUtils';
|
||||
import { logError, TelemetryViews } from '../../telemetry';
|
||||
import { MigrationTargetType } from '../../api/utils';
|
||||
|
||||
const DialogName = 'ValidateIrDialog';
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import * as constants from '../constants/strings';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { sendSqlMigrationActionEvent, TelemetryAction, TelemetryViews, logError } from '../telemetry';
|
||||
import { hashString, deepClone, getBlobContainerNameWithFolder, Blob, getLastBackupFileNameWithoutFolder } from '../api/utils';
|
||||
import { hashString, deepClone, getBlobContainerNameWithFolder, Blob, getLastBackupFileNameWithoutFolder, MigrationTargetType } from '../api/utils';
|
||||
import { SKURecommendationPage } from '../wizard/skuRecommendationPage';
|
||||
import { excludeDatabases, getEncryptConnectionValue, getSourceConnectionId, getSourceConnectionProfile, getSourceConnectionServerInfo, getSourceConnectionString, getSourceConnectionUri, getTrustServerCertificateValue, SourceDatabaseInfo, TargetDatabaseInfo } from '../api/sqlUtils';
|
||||
import { LoginMigrationModel } from './loginMigrationModel';
|
||||
@@ -57,12 +57,6 @@ export enum ServiceTier {
|
||||
BusinessCritical = 'BusinessCritical',
|
||||
}
|
||||
|
||||
export enum MigrationTargetType {
|
||||
SQLVM = 'AzureSqlVirtualMachine',
|
||||
SQLMI = 'AzureSqlManagedInstance',
|
||||
SQLDB = 'AzureSqlDatabase'
|
||||
}
|
||||
|
||||
export enum MigrationSourceAuthenticationType {
|
||||
Integrated = 'WindowsAuthentication',
|
||||
Sql = 'SqlAuthentication'
|
||||
|
||||
@@ -8,11 +8,12 @@ import * as vscode from 'vscode';
|
||||
import { EOL } from 'os';
|
||||
import { getStorageAccountAccessKeys, SqlVMServer } from '../api/azure';
|
||||
import { MigrationWizardPage } from '../models/migrationWizardPage';
|
||||
import { MigrationMode, MigrationSourceAuthenticationType, MigrationStateModel, MigrationTargetType, NetworkContainerType, NetworkShare, StateChangeEvent, ValidateIrState, ValidationResult } from '../models/stateMachine';
|
||||
import { MigrationMode, MigrationSourceAuthenticationType, MigrationStateModel, NetworkContainerType, NetworkShare, StateChangeEvent, ValidateIrState, ValidationResult } from '../models/stateMachine';
|
||||
import * as constants from '../constants/strings';
|
||||
import { IconPathHelper } from '../constants/iconPathHelper';
|
||||
import { WIZARD_INPUT_COMPONENT_WIDTH } from './wizardController';
|
||||
import * as utils from '../api/utils';
|
||||
import { MigrationTargetType } from '../api/utils';
|
||||
import { logError, TelemetryViews } from '../telemetry';
|
||||
import * as styles from '../constants/styles';
|
||||
import { TableMigrationSelectionDialog } from '../dialog/tableMigrationSelection/tableMigrationSelectionDialog';
|
||||
|
||||
@@ -7,11 +7,12 @@ import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import { EOL } from 'os';
|
||||
import { MigrationWizardPage } from '../models/migrationWizardPage';
|
||||
import { MigrationStateModel, MigrationTargetType, StateChangeEvent } from '../models/stateMachine';
|
||||
import { MigrationStateModel, StateChangeEvent } from '../models/stateMachine';
|
||||
import * as constants from '../constants/strings';
|
||||
import * as styles from '../constants/styles';
|
||||
import { WIZARD_INPUT_COMPONENT_WIDTH } from './wizardController';
|
||||
import * as utils from '../api/utils';
|
||||
import { MigrationTargetType } from '../api/utils';
|
||||
import { azureResource } from 'azurecore';
|
||||
import { AzureSqlDatabaseServer, getVMInstanceView, SqlVMServer } from '../api/azure';
|
||||
import { collectSourceLogins, collectTargetLogins, getSourceConnectionId, getSourceConnectionProfile, isSourceConnectionSysAdmin, LoginTableInfo } from '../api/sqlUtils';
|
||||
|
||||
@@ -6,9 +6,10 @@
|
||||
import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import * as utils from '../api/utils';
|
||||
import { MigrationTargetType } from '../api/utils';
|
||||
import * as contracts from '../service/contracts';
|
||||
import { MigrationWizardPage } from '../models/migrationWizardPage';
|
||||
import { MigrationStateModel, MigrationTargetType, PerformanceDataSourceOptions, StateChangeEvent, AssessmentRuleId } from '../models/stateMachine';
|
||||
import { MigrationStateModel, PerformanceDataSourceOptions, StateChangeEvent, AssessmentRuleId } from '../models/stateMachine';
|
||||
import { AssessmentResultsDialog } from '../dialog/assessmentResults/assessmentResultsDialog';
|
||||
import { SkuRecommendationResultsDialog } from '../dialog/skuRecommendationResults/skuRecommendationResultsDialog';
|
||||
import { GetAzureRecommendationDialog } from '../dialog/skuRecommendationResults/getAzureRecommendationDialog';
|
||||
|
||||
@@ -6,12 +6,13 @@
|
||||
import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import { MigrationWizardPage } from '../models/migrationWizardPage';
|
||||
import { MigrationMode, MigrationStateModel, MigrationTargetType, NetworkContainerType, StateChangeEvent } from '../models/stateMachine';
|
||||
import { MigrationMode, MigrationStateModel, NetworkContainerType, StateChangeEvent } from '../models/stateMachine';
|
||||
import * as constants from '../constants/strings';
|
||||
import { createHeadingTextComponent, createInformationRow, createLabelTextComponent } from './wizardController';
|
||||
import { getResourceGroupFromId } from '../api/azure';
|
||||
import { TargetDatabaseSummaryDialog } from '../dialog/targetDatabaseSummary/targetDatabaseSummaryDialog';
|
||||
import * as styles from '../constants/styles';
|
||||
import { MigrationTargetType } from '../api/utils';
|
||||
|
||||
export class SummaryPage extends MigrationWizardPage {
|
||||
private _view!: azdata.ModelView;
|
||||
|
||||
@@ -7,11 +7,12 @@ import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import { EOL } from 'os';
|
||||
import { MigrationWizardPage } from '../models/migrationWizardPage';
|
||||
import { MigrationStateModel, MigrationTargetType, StateChangeEvent } from '../models/stateMachine';
|
||||
import { MigrationStateModel, StateChangeEvent } from '../models/stateMachine';
|
||||
import * as constants from '../constants/strings';
|
||||
import * as styles from '../constants/styles';
|
||||
import { WIZARD_INPUT_COMPONENT_WIDTH } from './wizardController';
|
||||
import * as utils from '../api/utils';
|
||||
import { MigrationTargetType } from '../api/utils';
|
||||
import { azureResource } from 'azurecore';
|
||||
import { AzureSqlDatabaseServer, getVMInstanceView, SqlVMServer } from '../api/azure';
|
||||
import { collectTargetDatabaseInfo, TargetDatabaseInfo } from '../api/sqlUtils';
|
||||
@@ -42,7 +43,7 @@ export class TargetSelectionPage extends MigrationWizardPage {
|
||||
private _targetPasswordInputBox!: azdata.InputBoxComponent;
|
||||
private _testConectionButton!: azdata.ButtonComponent;
|
||||
private _connectionResultsInfoBox!: azdata.InfoBoxComponent;
|
||||
private _migrationTargetPlatform!: MigrationTargetType;
|
||||
private _migrationTargetPlatform!: utils.MigrationTargetType;
|
||||
private _serviceContext!: MigrationServiceContext;
|
||||
|
||||
constructor(
|
||||
|
||||
Reference in New Issue
Block a user