mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 18:46:40 -05:00
Add SQL DB offline migration wizard experience (#20403)
* sql db wizard with target selection * add database table selection * add sqldb to service and IR page * Code complete * navigation bug fixes * fix target db selection * improve sqldb error and status reporting * fix error count bug * remove table status inference * address review feedback * update resource strings and content * fix migraton status string, use localized value * fix ux navigation issues * fix back/fwd w/o changes from changing data
This commit is contained in:
@@ -8,12 +8,12 @@ import * as azdata from 'azdata';
|
|||||||
import * as azurecore from 'azurecore';
|
import * as azurecore from 'azurecore';
|
||||||
import * as constants from '../constants/strings';
|
import * as constants from '../constants/strings';
|
||||||
import { getSessionIdHeader } from './utils';
|
import { getSessionIdHeader } from './utils';
|
||||||
import { ProvisioningState } from '../models/migrationLocalStorage';
|
|
||||||
import { URL } from 'url';
|
import { URL } from 'url';
|
||||||
|
|
||||||
const ARM_MGMT_API_VERSION = '2021-04-01';
|
const ARM_MGMT_API_VERSION = '2021-04-01';
|
||||||
const SQL_VM_API_VERSION = '2021-11-01-preview';
|
const SQL_VM_API_VERSION = '2021-11-01-preview';
|
||||||
const SQL_MI_API_VERSION = '2021-11-01-preview';
|
const SQL_MI_API_VERSION = '2021-11-01-preview';
|
||||||
|
const SQL_SQLDB_API_VERSION = '2021-11-01-preview';
|
||||||
const DMSV2_API_VERSION = '2022-03-30-preview';
|
const DMSV2_API_VERSION = '2022-03-30-preview';
|
||||||
|
|
||||||
async function getAzureCoreAPI(): Promise<azurecore.IExtension> {
|
async function getAzureCoreAPI(): Promise<azurecore.IExtension> {
|
||||||
@@ -93,6 +93,100 @@ export async function getAvailableSqlServers(account: azdata.Account, subscripti
|
|||||||
return result.resources;
|
return result.resources;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SKU {
|
||||||
|
name: string,
|
||||||
|
tier: 'GeneralPurpose' | 'BusinessCritical',
|
||||||
|
family: string,
|
||||||
|
capacity: number,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AzureSqlDatabase {
|
||||||
|
id: string,
|
||||||
|
name: string,
|
||||||
|
location: string,
|
||||||
|
tags: any,
|
||||||
|
type: string,
|
||||||
|
sku: SKU,
|
||||||
|
kind: string,
|
||||||
|
properties: {
|
||||||
|
collation: string,
|
||||||
|
maxSizeBytes: number,
|
||||||
|
status: string,
|
||||||
|
databaseId: string,
|
||||||
|
creationDate: string,
|
||||||
|
currentServiceObjectiveName: string,
|
||||||
|
requestedServiceObjectiveName: string,
|
||||||
|
defaultSecondaryLocation: string,
|
||||||
|
catalogCollation: string,
|
||||||
|
zoneRedundant: boolean,
|
||||||
|
earliestRestoreDate: string,
|
||||||
|
readScale: string,
|
||||||
|
currentSku: SKU,
|
||||||
|
currentBackupStorageRedundancy: string,
|
||||||
|
requestedBackupStorageRedundancy: string,
|
||||||
|
maintenanceConfigurationId: string,
|
||||||
|
isLedgerOn: boolean
|
||||||
|
isInfraEncryptionEnabled: boolean,
|
||||||
|
licenseType: string,
|
||||||
|
maxLogSizeBytes: number,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ServerAdministrators {
|
||||||
|
administratorType: string,
|
||||||
|
azureADOnlyAuthentication: boolean,
|
||||||
|
login: string,
|
||||||
|
principalType: string,
|
||||||
|
sid: string,
|
||||||
|
tenantId: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PrivateEndpointProperty {
|
||||||
|
id?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PrivateLinkServiceConnectionStateProperty {
|
||||||
|
status: string;
|
||||||
|
description: string;
|
||||||
|
readonly actionsRequired?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PrivateEndpointConnectionProperties {
|
||||||
|
groupIds: string[];
|
||||||
|
privateEndpoint?: PrivateEndpointProperty;
|
||||||
|
privateLinkServiceConnectionState?: PrivateLinkServiceConnectionStateProperty;
|
||||||
|
readonly provisioningState?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ServerPrivateEndpointConnection {
|
||||||
|
readonly id?: string;
|
||||||
|
readonly properties?: PrivateEndpointConnectionProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AzureSqlDatabaseServer {
|
||||||
|
id: string,
|
||||||
|
name: string,
|
||||||
|
kind: string,
|
||||||
|
location: string,
|
||||||
|
tags?: { [propertyName: string]: string; };
|
||||||
|
type: string,
|
||||||
|
// sku: SKU,
|
||||||
|
// subscriptionId: string,
|
||||||
|
// tenantId: string,
|
||||||
|
// fullName: string,
|
||||||
|
properties: {
|
||||||
|
administratorLogin: string,
|
||||||
|
administrators: ServerAdministrators,
|
||||||
|
fullyQualifiedDomainName: string,
|
||||||
|
minimalTlsVersion: string,
|
||||||
|
privateEndpointConnections: ServerPrivateEndpointConnection[],
|
||||||
|
publicNetworkAccess: string,
|
||||||
|
restrictOutboundNetworkAccess: string,
|
||||||
|
state: string,
|
||||||
|
version: string,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
export type SqlVMServer = {
|
export type SqlVMServer = {
|
||||||
properties: {
|
properties: {
|
||||||
virtualMachineResourceId: string,
|
virtualMachineResourceId: string,
|
||||||
@@ -108,6 +202,31 @@ export type SqlVMServer = {
|
|||||||
tenantId: string,
|
tenantId: string,
|
||||||
subscriptionId: string
|
subscriptionId: string
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export async function getAvailableSqlDatabaseServers(account: azdata.Account, subscription: Subscription): Promise<AzureSqlDatabaseServer[]> {
|
||||||
|
const api = await getAzureCoreAPI();
|
||||||
|
const path = encodeURI(`/subscriptions/${subscription.id}/providers/Microsoft.Sql/servers?api-version=${SQL_SQLDB_API_VERSION}`);
|
||||||
|
const host = api.getProviderMetadataForAccount(account).settings.armResource?.endpoint;
|
||||||
|
const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.GET, undefined, true, host);
|
||||||
|
if (response.errors.length > 0) {
|
||||||
|
throw new Error(response.errors.toString());
|
||||||
|
}
|
||||||
|
sortResourceArrayByName(response.response.data.value);
|
||||||
|
return response.response.data.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAvailableSqlDatabases(account: azdata.Account, subscription: Subscription, resourceGroupName: string, serverName: string): Promise<AzureSqlDatabase[]> {
|
||||||
|
const api = await getAzureCoreAPI();
|
||||||
|
const path = encodeURI(`/subscriptions/${subscription.id}/resourceGroups/${resourceGroupName}/providers/Microsoft.Sql/servers/${serverName}/databases?api-version=${SQL_SQLDB_API_VERSION}`);
|
||||||
|
const host = api.getProviderMetadataForAccount(account).settings.armResource?.endpoint;
|
||||||
|
const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.GET, undefined, true, host);
|
||||||
|
if (response.errors.length > 0) {
|
||||||
|
throw new Error(response.errors.toString());
|
||||||
|
}
|
||||||
|
sortResourceArrayByName(response.response.data.value);
|
||||||
|
return response.response.data.value;
|
||||||
|
}
|
||||||
|
|
||||||
export async function getAvailableSqlVMs(account: azdata.Account, subscription: Subscription): Promise<SqlVMServer[]> {
|
export async function getAvailableSqlVMs(account: azdata.Account, subscription: Subscription): Promise<SqlVMServer[]> {
|
||||||
const api = await getAzureCoreAPI();
|
const api = await getAzureCoreAPI();
|
||||||
const path = encodeURI(`/subscriptions/${subscription.id}/providers/Microsoft.SqlVirtualMachine/sqlVirtualMachines?api-version=${SQL_VM_API_VERSION}`);
|
const path = encodeURI(`/subscriptions/${subscription.id}/providers/Microsoft.SqlVirtualMachine/sqlVirtualMachines?api-version=${SQL_VM_API_VERSION}`);
|
||||||
@@ -203,10 +322,10 @@ export async function getSqlMigrationServices(account: azdata.Account, subscript
|
|||||||
export async function createSqlMigrationService(account: azdata.Account, subscription: Subscription, resourceGroupName: string, regionName: string, sqlMigrationServiceName: string, sessionId: string): Promise<SqlMigrationService> {
|
export async function createSqlMigrationService(account: azdata.Account, subscription: Subscription, resourceGroupName: string, regionName: string, sqlMigrationServiceName: string, sessionId: string): Promise<SqlMigrationService> {
|
||||||
const api = await getAzureCoreAPI();
|
const api = await getAzureCoreAPI();
|
||||||
const path = encodeURI(`/subscriptions/${subscription.id}/resourceGroups/${resourceGroupName}/providers/Microsoft.DataMigration/sqlMigrationServices/${sqlMigrationServiceName}?api-version=${DMSV2_API_VERSION}`);
|
const path = encodeURI(`/subscriptions/${subscription.id}/resourceGroups/${resourceGroupName}/providers/Microsoft.DataMigration/sqlMigrationServices/${sqlMigrationServiceName}?api-version=${DMSV2_API_VERSION}`);
|
||||||
|
const host = api.getProviderMetadataForAccount(account).settings.armResource?.endpoint;
|
||||||
const requestBody = {
|
const requestBody = {
|
||||||
'location': regionName
|
'location': regionName
|
||||||
};
|
};
|
||||||
const host = api.getProviderMetadataForAccount(account).settings.armResource?.endpoint;
|
|
||||||
const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.PUT, requestBody, true, host, getSessionIdHeader(sessionId));
|
const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.PUT, requestBody, true, host, getSessionIdHeader(sessionId));
|
||||||
if (response.errors.length > 0) {
|
if (response.errors.length > 0) {
|
||||||
throw new Error(response.errors.toString());
|
throw new Error(response.errors.toString());
|
||||||
@@ -219,9 +338,9 @@ export async function createSqlMigrationService(account: azdata.Account, subscri
|
|||||||
for (i = 0; i < maxRetry; i++) {
|
for (i = 0; i < maxRetry; i++) {
|
||||||
const asyncResponse = await api.makeAzureRestRequest(account, subscription, asyncPath, azurecore.HttpRequestMethod.GET, undefined, true, host);
|
const asyncResponse = await api.makeAzureRestRequest(account, subscription, asyncPath, azurecore.HttpRequestMethod.GET, undefined, true, host);
|
||||||
const creationStatus = asyncResponse.response.data.status;
|
const creationStatus = asyncResponse.response.data.status;
|
||||||
if (creationStatus === ProvisioningState.Succeeded) {
|
if (creationStatus === constants.ProvisioningState.Succeeded) {
|
||||||
break;
|
break;
|
||||||
} else if (creationStatus === ProvisioningState.Failed) {
|
} else if (creationStatus === constants.ProvisioningState.Failed) {
|
||||||
throw new Error(asyncResponse.errors.toString());
|
throw new Error(asyncResponse.errors.toString());
|
||||||
}
|
}
|
||||||
await new Promise(resolve => setTimeout(resolve, 5000)); //adding 5 sec delay before getting creation status
|
await new Promise(resolve => setTimeout(resolve, 5000)); //adding 5 sec delay before getting creation status
|
||||||
@@ -287,7 +406,15 @@ export async function getSqlMigrationServiceMonitoringData(account: azdata.Accou
|
|||||||
return response.response.data;
|
return response.response.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function startDatabaseMigration(account: azdata.Account, subscription: Subscription, regionName: string, targetServer: SqlManagedInstance | SqlVMServer, targetDatabaseName: string, requestBody: StartDatabaseMigrationRequest, sessionId: string): Promise<StartDatabaseMigrationResponse> {
|
export async function startDatabaseMigration(
|
||||||
|
account: azdata.Account,
|
||||||
|
subscription: Subscription,
|
||||||
|
regionName: string,
|
||||||
|
targetServer: SqlManagedInstance | SqlVMServer | AzureSqlDatabaseServer,
|
||||||
|
targetDatabaseName: string,
|
||||||
|
requestBody: StartDatabaseMigrationRequest,
|
||||||
|
sessionId: string): Promise<StartDatabaseMigrationResponse> {
|
||||||
|
|
||||||
const api = await getAzureCoreAPI();
|
const api = await getAzureCoreAPI();
|
||||||
const path = encodeURI(`${targetServer.id}/providers/Microsoft.DataMigration/databaseMigrations/${targetDatabaseName}?api-version=${DMSV2_API_VERSION}`);
|
const path = encodeURI(`${targetServer.id}/providers/Microsoft.DataMigration/databaseMigrations/${targetDatabaseName}?api-version=${DMSV2_API_VERSION}`);
|
||||||
const host = api.getProviderMetadataForAccount(account).settings.armResource?.endpoint;
|
const host = api.getProviderMetadataForAccount(account).settings.armResource?.endpoint;
|
||||||
@@ -438,10 +565,10 @@ export interface SqlMigrationServiceProperties {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface SqlMigrationService {
|
export interface SqlMigrationService {
|
||||||
properties: SqlMigrationServiceProperties;
|
|
||||||
location: string;
|
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
location: string;
|
||||||
|
properties: SqlMigrationServiceProperties;
|
||||||
error: {
|
error: {
|
||||||
code: string,
|
code: string,
|
||||||
message: string
|
message: string
|
||||||
@@ -485,14 +612,29 @@ export interface StartDatabaseMigrationRequest {
|
|||||||
},
|
},
|
||||||
sourceLocation?: SourceLocation
|
sourceLocation?: SourceLocation
|
||||||
},
|
},
|
||||||
sourceSqlConnection: {
|
targetSqlConnection?: {
|
||||||
authentication: string,
|
|
||||||
dataSource: string,
|
dataSource: string,
|
||||||
username: string,
|
authentication: string,
|
||||||
password: string
|
userName: string,
|
||||||
|
password: string,
|
||||||
|
encryptConnection?: boolean,
|
||||||
|
trustServerCertificate?: boolean,
|
||||||
},
|
},
|
||||||
|
sourceSqlConnection: {
|
||||||
|
dataSource: string,
|
||||||
|
authentication: string,
|
||||||
|
userName: string,
|
||||||
|
password: string,
|
||||||
|
encryptConnection?: boolean,
|
||||||
|
trustServerCertificate?: boolean,
|
||||||
|
},
|
||||||
|
sqlDataCopyThresholds?: {
|
||||||
|
cidxrowthreshold: number,
|
||||||
|
cidxkbsthreshold: number,
|
||||||
|
},
|
||||||
|
tableList?: string[],
|
||||||
scope: string,
|
scope: string,
|
||||||
offlineConfiguration: OfflineConfiguration,
|
offlineConfiguration?: OfflineConfiguration,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -503,10 +645,10 @@ export interface StartDatabaseMigrationResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface DatabaseMigration {
|
export interface DatabaseMigration {
|
||||||
properties: DatabaseMigrationProperties;
|
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
type: string;
|
type: string;
|
||||||
|
properties: DatabaseMigrationProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DatabaseMigrationProperties {
|
export interface DatabaseMigrationProperties {
|
||||||
@@ -525,6 +667,7 @@ export interface DatabaseMigrationProperties {
|
|||||||
backupConfiguration: BackupConfiguration;
|
backupConfiguration: BackupConfiguration;
|
||||||
offlineConfiguration: OfflineConfiguration;
|
offlineConfiguration: OfflineConfiguration;
|
||||||
migrationFailureError: ErrorInfo;
|
migrationFailureError: ErrorInfo;
|
||||||
|
tableList: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MigrationStatusDetails {
|
export interface MigrationStatusDetails {
|
||||||
@@ -543,6 +686,7 @@ export interface MigrationStatusDetails {
|
|||||||
pendingLogBackupsCount: number;
|
pendingLogBackupsCount: number;
|
||||||
invalidFiles: string[];
|
invalidFiles: string[];
|
||||||
listOfCopyProgressDetails: CopyProgressDetail[];
|
listOfCopyProgressDetails: CopyProgressDetail[];
|
||||||
|
sqlDataCopyErrors: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CopyProgressDetail {
|
export interface CopyProgressDetail {
|
||||||
|
|||||||
282
extensions/sql-migration/src/api/sqlUtils.ts
Normal file
282
extensions/sql-migration/src/api/sqlUtils.ts
Normal file
@@ -0,0 +1,282 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import * as azdata from 'azdata';
|
||||||
|
import { azureResource } from 'azurecore';
|
||||||
|
import { AzureSqlDatabase, AzureSqlDatabaseServer } from './azure';
|
||||||
|
import { generateGuid } from './utils';
|
||||||
|
import * as utils from '../api/utils';
|
||||||
|
|
||||||
|
const query_database_tables_sql = `
|
||||||
|
SELECT
|
||||||
|
DB_NAME() as database_name,
|
||||||
|
QUOTENAME(SCHEMA_NAME(o.schema_id)) + '.' + QUOTENAME(o.name) AS table_name,
|
||||||
|
SUM(p.Rows) AS row_count
|
||||||
|
FROM
|
||||||
|
sys.objects AS o
|
||||||
|
INNER JOIN sys.partitions AS p
|
||||||
|
ON o.object_id = p.object_id
|
||||||
|
WHERE
|
||||||
|
o.type = 'U'
|
||||||
|
AND o.is_ms_shipped = 0x0
|
||||||
|
AND index_id < 2
|
||||||
|
GROUP BY
|
||||||
|
o.schema_id,
|
||||||
|
o.name
|
||||||
|
ORDER BY table_name;`;
|
||||||
|
|
||||||
|
const query_target_databases_sql = `
|
||||||
|
SELECT
|
||||||
|
('servername') as server_name,
|
||||||
|
SERVERPROPERTY ('collation') as server_collation,
|
||||||
|
db.database_id as database_id,
|
||||||
|
db.name as database_name,
|
||||||
|
db.collation_name as database_collation,
|
||||||
|
CASE WHEN 'A' = 'a' THEN 0 ELSE 1 END as is_server_case_sensitive,
|
||||||
|
db.state as database_state,
|
||||||
|
db.is_read_only as is_read_only
|
||||||
|
FROM sys.databases db
|
||||||
|
WHERE
|
||||||
|
db.name not in ('master', 'tempdb', 'model', 'msdb')
|
||||||
|
AND is_distributor <> 1
|
||||||
|
ORDER BY db.name;`;
|
||||||
|
|
||||||
|
export const excludeDatabses: string[] = [
|
||||||
|
'master',
|
||||||
|
'tempdb',
|
||||||
|
'msdb',
|
||||||
|
'model'
|
||||||
|
];
|
||||||
|
|
||||||
|
export enum AuthenticationType {
|
||||||
|
Integrated = 'Integrated',
|
||||||
|
SqlLogin = 'SqlLogin'
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TableInfo {
|
||||||
|
databaseName: string;
|
||||||
|
tableName: string;
|
||||||
|
rowCount: number;
|
||||||
|
selectedForMigration: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TargetDatabaseInfo {
|
||||||
|
serverName: string;
|
||||||
|
serverCollation: string;
|
||||||
|
databaseId: string;
|
||||||
|
databaseName: string;
|
||||||
|
databaseCollation: string;
|
||||||
|
isServerCaseSensitive: boolean;
|
||||||
|
databaseState: number;
|
||||||
|
isReadOnly: boolean;
|
||||||
|
sourceTables: Map<string, TableInfo>;
|
||||||
|
targetTables: Map<string, TableInfo>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSqlDbConnectionProfile(
|
||||||
|
serverName: string,
|
||||||
|
tenantId: string,
|
||||||
|
databaseName: string,
|
||||||
|
userName: string,
|
||||||
|
password: string): azdata.IConnectionProfile {
|
||||||
|
return {
|
||||||
|
id: generateGuid(),
|
||||||
|
providerName: 'MSSQL',
|
||||||
|
connectionName: '',
|
||||||
|
serverName: serverName,
|
||||||
|
databaseName: databaseName,
|
||||||
|
userName: userName,
|
||||||
|
password: password,
|
||||||
|
authenticationType: AuthenticationType.SqlLogin,
|
||||||
|
savePassword: false,
|
||||||
|
saveProfile: false,
|
||||||
|
options: {
|
||||||
|
conectionName: '',
|
||||||
|
server: serverName,
|
||||||
|
database: databaseName,
|
||||||
|
authenticationType: AuthenticationType.SqlLogin,
|
||||||
|
user: userName,
|
||||||
|
password: password,
|
||||||
|
connectionTimeout: 60,
|
||||||
|
columnEncryptionSetting: 'Enabled',
|
||||||
|
encrypt: true,
|
||||||
|
trustServerCertificate: false,
|
||||||
|
connectRetryCount: '1',
|
||||||
|
connectRetryInterval: '10',
|
||||||
|
applicationName: 'azdata',
|
||||||
|
azureTenantId: tenantId,
|
||||||
|
originalDatabase: databaseName,
|
||||||
|
databaseDisplayName: databaseName,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getConnectionProfile(
|
||||||
|
serverName: string,
|
||||||
|
azureResourceId: string,
|
||||||
|
userName: string,
|
||||||
|
password: string): azdata.IConnectionProfile {
|
||||||
|
return {
|
||||||
|
serverName: serverName,
|
||||||
|
id: generateGuid(),
|
||||||
|
connectionName: undefined,
|
||||||
|
azureResourceId: azureResourceId,
|
||||||
|
userName: userName,
|
||||||
|
password: password,
|
||||||
|
authenticationType: AuthenticationType.SqlLogin,
|
||||||
|
savePassword: false,
|
||||||
|
groupFullName: '',
|
||||||
|
groupId: '',
|
||||||
|
providerName: 'MSSQL',
|
||||||
|
saveProfile: false,
|
||||||
|
options: {
|
||||||
|
conectionName: '',
|
||||||
|
server: serverName,
|
||||||
|
authenticationType: AuthenticationType.SqlLogin,
|
||||||
|
user: userName,
|
||||||
|
password: password,
|
||||||
|
connectionTimeout: 60,
|
||||||
|
columnEncryptionSetting: 'Enabled',
|
||||||
|
encrypt: true,
|
||||||
|
trustServerCertificate: false,
|
||||||
|
connectRetryCount: '1',
|
||||||
|
connectRetryInterval: '10',
|
||||||
|
applicationName: 'azdata',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function collectSourceDatabaseTableInfo(sourceConnectionId: string, sourceDatabase: string): Promise<TableInfo[]> {
|
||||||
|
const ownerUri = await azdata.connection.getUriForConnection(sourceConnectionId);
|
||||||
|
const connectionProvider = azdata.dataprotocol.getProvider<azdata.ConnectionProvider>(
|
||||||
|
'MSSQL',
|
||||||
|
azdata.DataProviderType.ConnectionProvider);
|
||||||
|
await connectionProvider.changeDatabase(ownerUri, sourceDatabase);
|
||||||
|
const queryProvider = azdata.dataprotocol.getProvider<azdata.QueryProvider>(
|
||||||
|
'MSSQL',
|
||||||
|
azdata.DataProviderType.QueryProvider);
|
||||||
|
|
||||||
|
const results = await queryProvider.runQueryAndReturn(
|
||||||
|
ownerUri,
|
||||||
|
query_database_tables_sql);
|
||||||
|
|
||||||
|
return results.rows.map(row => {
|
||||||
|
return {
|
||||||
|
databaseName: getSqlString(row[0]),
|
||||||
|
tableName: getSqlString(row[1]),
|
||||||
|
rowCount: getSqlNumber(row[2]),
|
||||||
|
selectedForMigration: false,
|
||||||
|
};
|
||||||
|
}) ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function collectTargetDatabaseTableInfo(
|
||||||
|
targetServer: AzureSqlDatabaseServer,
|
||||||
|
targetDatabaseName: string,
|
||||||
|
tenantId: string,
|
||||||
|
userName: string,
|
||||||
|
password: string): Promise<TableInfo[]> {
|
||||||
|
const connectionProfile = getSqlDbConnectionProfile(
|
||||||
|
targetServer.properties.fullyQualifiedDomainName,
|
||||||
|
tenantId,
|
||||||
|
targetDatabaseName,
|
||||||
|
userName,
|
||||||
|
password);
|
||||||
|
|
||||||
|
const result = await azdata.connection.connect(connectionProfile, false, false);
|
||||||
|
if (result.connected && result.connectionId) {
|
||||||
|
const queryProvider = azdata.dataprotocol.getProvider<azdata.QueryProvider>(
|
||||||
|
'MSSQL',
|
||||||
|
azdata.DataProviderType.QueryProvider);
|
||||||
|
|
||||||
|
const ownerUri = await azdata.connection.getUriForConnection(result.connectionId);
|
||||||
|
const results = await queryProvider.runQueryAndReturn(
|
||||||
|
ownerUri,
|
||||||
|
query_database_tables_sql);
|
||||||
|
|
||||||
|
return results.rows.map(row => {
|
||||||
|
return {
|
||||||
|
databaseName: getSqlString(row[0]),
|
||||||
|
tableName: getSqlString(row[1]),
|
||||||
|
rowCount: getSqlNumber(row[2]),
|
||||||
|
selectedForMigration: false,
|
||||||
|
};
|
||||||
|
}) ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(result.errorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function collectTargetDatabaseInfo(
|
||||||
|
targetServer: AzureSqlDatabaseServer,
|
||||||
|
userName: string,
|
||||||
|
password: string): Promise<TargetDatabaseInfo[]> {
|
||||||
|
|
||||||
|
const connectionProfile = getConnectionProfile(
|
||||||
|
targetServer.properties.fullyQualifiedDomainName,
|
||||||
|
targetServer.id,
|
||||||
|
userName,
|
||||||
|
password);
|
||||||
|
|
||||||
|
const result = await azdata.connection.connect(connectionProfile, false, false);
|
||||||
|
if (result.connected && result.connectionId) {
|
||||||
|
const queryProvider = azdata.dataprotocol.getProvider<azdata.QueryProvider>(
|
||||||
|
'MSSQL',
|
||||||
|
azdata.DataProviderType.QueryProvider);
|
||||||
|
|
||||||
|
const ownerUri = await azdata.connection.getUriForConnection(result.connectionId);
|
||||||
|
const results = await queryProvider.runQueryAndReturn(
|
||||||
|
ownerUri,
|
||||||
|
query_target_databases_sql);
|
||||||
|
|
||||||
|
return results.rows.map(row => {
|
||||||
|
return {
|
||||||
|
serverName: getSqlString(row[0]),
|
||||||
|
serverCollation: getSqlString(row[1]),
|
||||||
|
databaseId: getSqlString(row[2]),
|
||||||
|
databaseName: getSqlString(row[3]),
|
||||||
|
databaseCollation: getSqlString(row[4]),
|
||||||
|
isServerCaseSensitive: getSqlBoolean(row[5]),
|
||||||
|
databaseState: getSqlNumber(row[6]),
|
||||||
|
isReadOnly: getSqlBoolean(row[7]),
|
||||||
|
sourceTables: new Map(),
|
||||||
|
targetTables: new Map(),
|
||||||
|
};
|
||||||
|
}) ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(result.errorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function collectAzureTargetDatabases(
|
||||||
|
account: azdata.Account,
|
||||||
|
subscription: azureResource.AzureResourceSubscription,
|
||||||
|
resourceGroup: string,
|
||||||
|
targetServerName: string,
|
||||||
|
): Promise<AzureSqlDatabase[]> {
|
||||||
|
const databaseList: AzureSqlDatabase[] = [];
|
||||||
|
if (resourceGroup && targetServerName) {
|
||||||
|
databaseList.push(...
|
||||||
|
await utils.getAzureSqlDatabases(
|
||||||
|
account,
|
||||||
|
subscription,
|
||||||
|
resourceGroup,
|
||||||
|
targetServerName));
|
||||||
|
}
|
||||||
|
return databaseList.filter(
|
||||||
|
database => !excludeDatabses.includes(database.name)) ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getSqlString(value: azdata.DbCellValue): string {
|
||||||
|
return value.isNull ? '' : value.displayValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getSqlNumber(value: azdata.DbCellValue): number {
|
||||||
|
return value.isNull ? 0 : parseInt(value.displayValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getSqlBoolean(value: azdata.DbCellValue): boolean {
|
||||||
|
return value.isNull ? false : value.displayValue === '1';
|
||||||
|
}
|
||||||
@@ -3,10 +3,9 @@
|
|||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import { window, Account, accounts, CategoryValue, DropDownComponent, IconPath } from 'azdata';
|
import { window, Account, accounts, CategoryValue, DropDownComponent, IconPath, DisplayType, Component } from 'azdata';
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import { IconPathHelper } from '../constants/iconPathHelper';
|
import { IconPathHelper } from '../constants/iconPathHelper';
|
||||||
import { MigrationStatus, ProvisioningState } from '../models/migrationLocalStorage';
|
|
||||||
import * as crypto from 'crypto';
|
import * as crypto from 'crypto';
|
||||||
import * as azure from './azure';
|
import * as azure from './azure';
|
||||||
import { azureResource, Tenant } from 'azurecore';
|
import { azureResource, Tenant } from 'azurecore';
|
||||||
@@ -15,8 +14,26 @@ import { logError, TelemetryViews } from '../telemtery';
|
|||||||
import { AdsMigrationStatus } from '../dashboard/tabBase';
|
import { AdsMigrationStatus } from '../dashboard/tabBase';
|
||||||
import { getMigrationMode, getMigrationStatus, getMigrationTargetType, PipelineStatusCodes } from '../constants/helper';
|
import { getMigrationMode, getMigrationStatus, getMigrationTargetType, PipelineStatusCodes } from '../constants/helper';
|
||||||
|
|
||||||
|
export type TargetServerType = azure.SqlVMServer | azureResource.AzureSqlManagedInstance | azure.AzureSqlDatabaseServer;
|
||||||
|
|
||||||
|
export const SqlMigrationExtensionId = 'microsoft.sql-migration';
|
||||||
export const DefaultSettingValue = '---';
|
export const DefaultSettingValue = '---';
|
||||||
|
|
||||||
|
export const MenuCommands = {
|
||||||
|
Cutover: 'sqlmigration.cutover',
|
||||||
|
ViewDatabase: 'sqlmigration.view.database',
|
||||||
|
ViewTarget: 'sqlmigration.view.target',
|
||||||
|
ViewService: 'sqlmigration.view.service',
|
||||||
|
CopyMigration: 'sqlmigration.copy.migration',
|
||||||
|
CancelMigration: 'sqlmigration.cancel.migration',
|
||||||
|
RetryMigration: 'sqlmigration.retry.migration',
|
||||||
|
StartMigration: 'sqlmigration.start',
|
||||||
|
IssueReporter: 'workbench.action.openIssueReporter',
|
||||||
|
OpenNotebooks: 'sqlmigration.openNotebooks',
|
||||||
|
NewSupportRequest: 'sqlmigration.newsupportrequest',
|
||||||
|
SendFeedback: 'sqlmigration.sendfeedback',
|
||||||
|
};
|
||||||
|
|
||||||
export function deepClone<T>(obj: T): T {
|
export function deepClone<T>(obj: T): T {
|
||||||
if (!obj || typeof obj !== 'object') {
|
if (!obj || typeof obj !== 'object') {
|
||||||
return obj;
|
return obj;
|
||||||
@@ -145,19 +162,19 @@ export function filterMigrations(databaseMigrations: azure.DatabaseMigration[],
|
|||||||
return filteredMigration.filter(
|
return filteredMigration.filter(
|
||||||
value => {
|
value => {
|
||||||
const status = getMigrationStatus(value);
|
const status = getMigrationStatus(value);
|
||||||
return status === MigrationStatus.InProgress
|
return status === constants.MigrationStatus.InProgress
|
||||||
|| status === MigrationStatus.Retriable
|
|| status === constants.MigrationStatus.Retriable
|
||||||
|| status === MigrationStatus.Creating;
|
|| status === constants.MigrationStatus.Creating;
|
||||||
});
|
});
|
||||||
case AdsMigrationStatus.SUCCEEDED:
|
case AdsMigrationStatus.SUCCEEDED:
|
||||||
return filteredMigration.filter(
|
return filteredMigration.filter(
|
||||||
value => getMigrationStatus(value) === MigrationStatus.Succeeded);
|
value => getMigrationStatus(value) === constants.MigrationStatus.Succeeded);
|
||||||
case AdsMigrationStatus.FAILED:
|
case AdsMigrationStatus.FAILED:
|
||||||
return filteredMigration.filter(
|
return filteredMigration.filter(
|
||||||
value => getMigrationStatus(value) === MigrationStatus.Failed);
|
value => getMigrationStatus(value) === constants.MigrationStatus.Failed);
|
||||||
case AdsMigrationStatus.COMPLETING:
|
case AdsMigrationStatus.COMPLETING:
|
||||||
return filteredMigration.filter(
|
return filteredMigration.filter(
|
||||||
value => getMigrationStatus(value) === MigrationStatus.Completing);
|
value => getMigrationStatus(value) === constants.MigrationStatus.Completing);
|
||||||
}
|
}
|
||||||
return filteredMigration;
|
return filteredMigration;
|
||||||
}
|
}
|
||||||
@@ -192,6 +209,8 @@ export function selectDefaultDropdownValue(dropDown: DropDownComponent, value?:
|
|||||||
selectedIndex = -1;
|
selectedIndex = -1;
|
||||||
}
|
}
|
||||||
selectDropDownIndex(dropDown, selectedIndex > -1 ? selectedIndex : 0);
|
selectDropDownIndex(dropDown, selectedIndex > -1 ? selectedIndex : 0);
|
||||||
|
} else {
|
||||||
|
dropDown.value = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -251,19 +270,22 @@ export function getSessionIdHeader(sessionId: string): { [key: string]: string }
|
|||||||
|
|
||||||
export function getMigrationStatusWithErrors(migration: azure.DatabaseMigration): string {
|
export function getMigrationStatusWithErrors(migration: azure.DatabaseMigration): string {
|
||||||
const properties = migration.properties;
|
const properties = migration.properties;
|
||||||
const migrationStatus = properties.migrationStatus ?? properties.provisioningState;
|
const migrationStatus = getMigrationStatus(migration) ?? '';
|
||||||
let warningCount = 0;
|
|
||||||
|
|
||||||
if (properties.migrationFailureError?.message) {
|
// provisioning error
|
||||||
warningCount++;
|
let warningCount = properties.provisioningError?.length > 0 ? 1 : 0;
|
||||||
}
|
|
||||||
if (properties.migrationStatusDetails?.fileUploadBlockingErrors) {
|
// migration failure error
|
||||||
const blockingErrors = properties.migrationStatusDetails?.fileUploadBlockingErrors.length ?? 0;
|
warningCount += properties.migrationFailureError?.message?.length > 0 ? 1 : 0;
|
||||||
warningCount += blockingErrors;
|
|
||||||
}
|
// file upload blocking errors
|
||||||
if (properties.migrationStatusDetails?.restoreBlockingReason) {
|
warningCount += properties.migrationStatusDetails?.fileUploadBlockingErrors?.length ?? 0;
|
||||||
warningCount++;
|
|
||||||
}
|
// restore blocking reason
|
||||||
|
warningCount += properties.migrationStatusDetails?.restoreBlockingReason ? 1 : 0;
|
||||||
|
|
||||||
|
// sql data copy errors
|
||||||
|
warningCount += properties.migrationStatusDetails?.sqlDataCopyErrors?.length ?? 0;
|
||||||
|
|
||||||
return constants.STATUS_VALUE(migrationStatus, warningCount)
|
return constants.STATUS_VALUE(migrationStatus, warningCount)
|
||||||
+ (constants.STATUS_WARNING_COUNT(migrationStatus, warningCount) ?? '');
|
+ (constants.STATUS_WARNING_COUNT(migrationStatus, warningCount) ?? '');
|
||||||
@@ -302,20 +324,20 @@ export function getPipelineStatusImage(status: string | undefined): IconPath {
|
|||||||
export function getMigrationStatusImage(migration: azure.DatabaseMigration): IconPath {
|
export function getMigrationStatusImage(migration: azure.DatabaseMigration): IconPath {
|
||||||
const status = getMigrationStatus(migration);
|
const status = getMigrationStatus(migration);
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case MigrationStatus.InProgress:
|
case constants.MigrationStatus.InProgress:
|
||||||
return IconPathHelper.inProgressMigration;
|
return IconPathHelper.inProgressMigration;
|
||||||
case MigrationStatus.Succeeded:
|
case constants.MigrationStatus.Succeeded:
|
||||||
return IconPathHelper.completedMigration;
|
return IconPathHelper.completedMigration;
|
||||||
case MigrationStatus.Creating:
|
case constants.MigrationStatus.Creating:
|
||||||
return IconPathHelper.notStartedMigration;
|
return IconPathHelper.notStartedMigration;
|
||||||
case MigrationStatus.Completing:
|
case constants.MigrationStatus.Completing:
|
||||||
return IconPathHelper.completingCutover;
|
return IconPathHelper.completingCutover;
|
||||||
case MigrationStatus.Retriable:
|
case constants.MigrationStatus.Retriable:
|
||||||
return IconPathHelper.retry;
|
return IconPathHelper.retry;
|
||||||
case MigrationStatus.Canceling:
|
case constants.MigrationStatus.Canceling:
|
||||||
case MigrationStatus.Canceled:
|
case constants.MigrationStatus.Canceled:
|
||||||
return IconPathHelper.cancel;
|
return IconPathHelper.cancel;
|
||||||
case MigrationStatus.Failed:
|
case constants.MigrationStatus.Failed:
|
||||||
default:
|
default:
|
||||||
return IconPathHelper.error;
|
return IconPathHelper.error;
|
||||||
}
|
}
|
||||||
@@ -379,34 +401,7 @@ export async function getAzureAccountsDropdownValues(accounts: Account[]): Promi
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getAzureTenants(account?: Account): Tenant[] {
|
export function getAzureTenants(account?: Account): Tenant[] {
|
||||||
let tenants: Tenant[] = [];
|
return account?.properties.tenants || [];
|
||||||
try {
|
|
||||||
if (account) {
|
|
||||||
tenants = account.properties.tenants;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
logError(TelemetryViews.Utils, 'utils.getAzureTenants', e);
|
|
||||||
}
|
|
||||||
return tenants;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getAzureTenantsDropdownValues(tenants: Tenant[]): Promise<CategoryValue[]> {
|
|
||||||
let tenantsValues: CategoryValue[] = [];
|
|
||||||
tenants.forEach((tenant) => {
|
|
||||||
tenantsValues.push({
|
|
||||||
name: tenant.id,
|
|
||||||
displayName: tenant.displayName
|
|
||||||
});
|
|
||||||
});
|
|
||||||
if (tenantsValues.length === 0) {
|
|
||||||
tenantsValues = [
|
|
||||||
{
|
|
||||||
displayName: constants.ACCOUNT_SELECTION_PAGE_NO_LINKED_ACCOUNTS_ERROR,
|
|
||||||
name: ''
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
return tenantsValues;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getAzureSubscriptions(account?: Account): Promise<azureResource.AzureResourceSubscription[]> {
|
export async function getAzureSubscriptions(account?: Account): Promise<azureResource.AzureResourceSubscription[]> {
|
||||||
@@ -441,172 +436,58 @@ export async function getAzureSubscriptionsDropdownValues(subscriptions: azureRe
|
|||||||
return subscriptionsValues;
|
return subscriptionsValues;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getSqlManagedInstanceLocations(account?: Account, subscription?: azureResource.AzureResourceSubscription, managedInstances?: azureResource.AzureSqlManagedInstance[]): Promise<azureResource.AzureLocation[]> {
|
export async function getResourceLocations(
|
||||||
let locations: azureResource.AzureLocation[] = [];
|
account?: Account,
|
||||||
|
subscription?: azureResource.AzureResourceSubscription,
|
||||||
|
resources?: { location: string }[]): Promise<azureResource.AzureLocation[]> {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (account && subscription && managedInstances) {
|
if (account && subscription && resources) {
|
||||||
locations = await azure.getLocations(account, subscription);
|
const locations = await azure.getLocations(account, subscription);
|
||||||
locations = locations.filter((loc, i) => managedInstances.some(mi => mi.location.toLowerCase() === loc.name.toLowerCase()));
|
return locations
|
||||||
|
.filter((loc, i) => resources.some(resource => resource.location.toLowerCase() === loc.name.toLowerCase()))
|
||||||
|
.sort((a, b) => a.displayName.localeCompare(b.displayName));
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logError(TelemetryViews.Utils, 'utils.getSqlManagedInstanceLocations', e);
|
logError(TelemetryViews.Utils, 'utils.getResourceLocations', e);
|
||||||
}
|
}
|
||||||
locations.sort((a, b) => a.displayName.localeCompare(b.displayName));
|
return [];
|
||||||
return locations;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getSqlVirtualMachineLocations(account?: Account, subscription?: azureResource.AzureResourceSubscription, virtualMachines?: azure.SqlVMServer[]): Promise<azureResource.AzureLocation[]> {
|
export function getServiceResourceGroupsByLocation(
|
||||||
let locations: azureResource.AzureLocation[] = [];
|
resources: { location: string, id: string, tenantId?: string }[],
|
||||||
try {
|
location: azureResource.AzureLocation): azureResource.AzureResourceResourceGroup[] {
|
||||||
if (account && subscription && virtualMachines) {
|
|
||||||
locations = await azure.getLocations(account, subscription);
|
|
||||||
locations = locations.filter((loc, i) => virtualMachines.some(vm => vm.location.toLowerCase() === loc.name.toLowerCase()));
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
logError(TelemetryViews.Utils, 'utils.getSqlVirtualMachineLocations', e);
|
|
||||||
}
|
|
||||||
locations.sort((a, b) => a.displayName.localeCompare(b.displayName));
|
|
||||||
return locations;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getSqlMigrationServiceLocations(account?: Account, subscription?: azureResource.AzureResourceSubscription, migrationServices?: azure.SqlMigrationService[]): Promise<azureResource.AzureLocation[]> {
|
|
||||||
let locations: azureResource.AzureLocation[] = [];
|
|
||||||
try {
|
|
||||||
if (account && subscription && migrationServices) {
|
|
||||||
locations = await azure.getLocations(account, subscription);
|
|
||||||
locations = locations.filter((loc, i) => migrationServices.some(dms => dms.location.toLowerCase() === loc.name.toLowerCase()));
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
logError(TelemetryViews.Utils, 'utils.getSqlMigrationServiceLocations', e);
|
|
||||||
}
|
|
||||||
locations.sort((a, b) => a.displayName.localeCompare(b.displayName));
|
|
||||||
return locations;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getAzureLocationsDropdownValues(locations: azureResource.AzureLocation[]): Promise<CategoryValue[]> {
|
|
||||||
let locationValues: CategoryValue[] = [];
|
|
||||||
locations.forEach((loc) => {
|
|
||||||
locationValues.push({
|
|
||||||
name: loc.name,
|
|
||||||
displayName: loc.displayName
|
|
||||||
});
|
|
||||||
});
|
|
||||||
if (locationValues.length === 0) {
|
|
||||||
locationValues = [
|
|
||||||
{
|
|
||||||
displayName: constants.NO_LOCATION_FOUND,
|
|
||||||
name: ''
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
return locationValues;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getSqlManagedInstanceResourceGroups(managedInstances?: azureResource.AzureSqlManagedInstance[], location?: azureResource.AzureLocation): Promise<azureResource.AzureResourceResourceGroup[]> {
|
|
||||||
let resourceGroups: azureResource.AzureResourceResourceGroup[] = [];
|
let resourceGroups: azureResource.AzureResourceResourceGroup[] = [];
|
||||||
try {
|
if (resources && location) {
|
||||||
if (managedInstances && location) {
|
const locationName = location.name.toLowerCase();
|
||||||
resourceGroups = managedInstances
|
resourceGroups = resources
|
||||||
.filter((mi) => mi.location.toLowerCase() === location.name.toLowerCase())
|
.filter(resource => resource.location.toLowerCase() === locationName)
|
||||||
.map((mi) => {
|
.map(resource => {
|
||||||
return <azureResource.AzureResourceResourceGroup>{
|
return <azureResource.AzureResourceResourceGroup>{
|
||||||
id: azure.getFullResourceGroupFromId(mi.id),
|
id: azure.getFullResourceGroupFromId(resource.id),
|
||||||
name: azure.getResourceGroupFromId(mi.id),
|
name: azure.getResourceGroupFromId(resource.id),
|
||||||
subscription: {
|
subscription: { id: getSubscriptionIdFromResourceId(resource.id) },
|
||||||
id: mi.subscriptionId
|
tenant: resource.tenantId
|
||||||
},
|
|
||||||
tenant: mi.tenantId
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
|
||||||
logError(TelemetryViews.Utils, 'utils.getSqlManagedInstanceResourceGroups', e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove duplicates
|
// remove duplicates
|
||||||
resourceGroups = resourceGroups.filter((v, i, a) => a.findIndex(v2 => (v2.id === v.id)) === i);
|
return resourceGroups
|
||||||
resourceGroups.sort((a, b) => a.name.localeCompare(b.name));
|
.filter((v, i, a) => a.findIndex(v2 => (v2.id === v.id)) === i)
|
||||||
return resourceGroups;
|
.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getSqlVirtualMachineResourceGroups(virtualMachines?: azure.SqlVMServer[], location?: azureResource.AzureLocation): Promise<azureResource.AzureResourceResourceGroup[]> {
|
export function getSubscriptionIdFromResourceId(resourceId: string): string | undefined {
|
||||||
let resourceGroups: azureResource.AzureResourceResourceGroup[] = [];
|
let parts = resourceId?.split('/subscriptions/');
|
||||||
try {
|
if (parts?.length > 1) {
|
||||||
if (virtualMachines && location) {
|
parts = parts[1]?.split('/resourcegroups/');
|
||||||
resourceGroups = virtualMachines
|
if (parts?.length > 0) {
|
||||||
.filter((vm) => vm.location.toLowerCase() === location.name.toLowerCase())
|
return parts[0];
|
||||||
.map((vm) => {
|
|
||||||
return <azureResource.AzureResourceResourceGroup>{
|
|
||||||
id: azure.getFullResourceGroupFromId(vm.id),
|
|
||||||
name: azure.getResourceGroupFromId(vm.id),
|
|
||||||
subscription: {
|
|
||||||
id: vm.subscriptionId
|
|
||||||
},
|
|
||||||
tenant: vm.tenantId
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
|
||||||
logError(TelemetryViews.Utils, 'utils.getSqlVirtualMachineResourceGroups', e);
|
|
||||||
}
|
}
|
||||||
|
return undefined;
|
||||||
// remove duplicates
|
|
||||||
resourceGroups = resourceGroups.filter((v, i, a) => a.findIndex(v2 => (v2.id === v.id)) === i);
|
|
||||||
resourceGroups.sort((a, b) => a.name.localeCompare(b.name));
|
|
||||||
return resourceGroups;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getStorageAccountResourceGroups(storageAccounts?: azure.StorageAccount[], location?: azureResource.AzureLocation): Promise<azureResource.AzureResourceResourceGroup[]> {
|
|
||||||
let resourceGroups: azureResource.AzureResourceResourceGroup[] = [];
|
|
||||||
try {
|
|
||||||
if (storageAccounts && location) {
|
|
||||||
resourceGroups = storageAccounts
|
|
||||||
.filter((sa) => sa.location.toLowerCase() === location.name.toLowerCase())
|
|
||||||
.map((sa) => {
|
|
||||||
return <azureResource.AzureResourceResourceGroup>{
|
|
||||||
id: azure.getFullResourceGroupFromId(sa.id),
|
|
||||||
name: azure.getResourceGroupFromId(sa.id),
|
|
||||||
subscription: {
|
|
||||||
id: sa.subscriptionId
|
|
||||||
},
|
|
||||||
tenant: sa.tenantId
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
logError(TelemetryViews.Utils, 'utils.getStorageAccountResourceGroups', e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove duplicates
|
|
||||||
resourceGroups = resourceGroups.filter((v, i, a) => a.findIndex(v2 => (v2.id === v.id)) === i);
|
|
||||||
resourceGroups.sort((a, b) => a.name.localeCompare(b.name));
|
|
||||||
return resourceGroups;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getSqlMigrationServiceResourceGroups(migrationServices?: azure.SqlMigrationService[], location?: azureResource.AzureLocation): Promise<azureResource.AzureResourceResourceGroup[]> {
|
|
||||||
let resourceGroups: azureResource.AzureResourceResourceGroup[] = [];
|
|
||||||
try {
|
|
||||||
if (migrationServices && location) {
|
|
||||||
resourceGroups = migrationServices
|
|
||||||
.filter((dms) => dms.properties.provisioningState === ProvisioningState.Succeeded && dms.location.toLowerCase() === location.name.toLowerCase())
|
|
||||||
.map((dms) => {
|
|
||||||
return <azureResource.AzureResourceResourceGroup>{
|
|
||||||
id: azure.getFullResourceGroupFromId(dms.id),
|
|
||||||
name: azure.getResourceGroupFromId(dms.id),
|
|
||||||
subscription: {
|
|
||||||
id: dms.properties.subscriptionId
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
logError(TelemetryViews.Utils, 'utils.getSqlMigrationServiceResourceGroups', e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove duplicates
|
|
||||||
resourceGroups = resourceGroups.filter((v, i, a) => a.findIndex(v2 => (v2.id === v.id)) === i);
|
|
||||||
resourceGroups.sort((a, b) => a.name.localeCompare(b.name));
|
|
||||||
return resourceGroups;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getAllResourceGroups(account?: Account, subscription?: azureResource.AzureResourceSubscription): Promise<azureResource.AzureResourceResourceGroup[]> {
|
export async function getAllResourceGroups(account?: Account, subscription?: azureResource.AzureResourceSubscription): Promise<azureResource.AzureResourceResourceGroup[]> {
|
||||||
@@ -622,25 +503,6 @@ export async function getAllResourceGroups(account?: Account, subscription?: azu
|
|||||||
return resourceGroups;
|
return resourceGroups;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getAzureResourceGroupsDropdownValues(resourceGroups: azureResource.AzureResourceResourceGroup[]): Promise<CategoryValue[]> {
|
|
||||||
let resourceGroupValues: CategoryValue[] = [];
|
|
||||||
resourceGroups.forEach((rg) => {
|
|
||||||
resourceGroupValues.push({
|
|
||||||
name: rg.id,
|
|
||||||
displayName: rg.name
|
|
||||||
});
|
|
||||||
});
|
|
||||||
if (resourceGroupValues.length === 0) {
|
|
||||||
resourceGroupValues = [
|
|
||||||
{
|
|
||||||
displayName: constants.RESOURCE_GROUP_NOT_FOUND,
|
|
||||||
name: ''
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
return resourceGroupValues;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getManagedInstances(account?: Account, subscription?: azureResource.AzureResourceSubscription): Promise<azureResource.AzureSqlManagedInstance[]> {
|
export async function getManagedInstances(account?: Account, subscription?: azureResource.AzureResourceSubscription): Promise<azureResource.AzureSqlManagedInstance[]> {
|
||||||
let managedInstances: azureResource.AzureSqlManagedInstance[] = [];
|
let managedInstances: azureResource.AzureSqlManagedInstance[] = [];
|
||||||
try {
|
try {
|
||||||
@@ -687,6 +549,31 @@ export async function getManagedInstancesDropdownValues(managedInstances: azureR
|
|||||||
return managedInstancesValues;
|
return managedInstancesValues;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getAzureSqlDatabaseServers(account?: Account, subscription?: azureResource.AzureResourceSubscription): Promise<azure.AzureSqlDatabaseServer[]> {
|
||||||
|
let sqlDatabaseServers: azure.AzureSqlDatabaseServer[] = [];
|
||||||
|
try {
|
||||||
|
if (account && subscription) {
|
||||||
|
sqlDatabaseServers = await azure.getAvailableSqlDatabaseServers(account, subscription);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
logError(TelemetryViews.Utils, 'utils.getAzureSqlDatabaseServers', e);
|
||||||
|
}
|
||||||
|
sqlDatabaseServers.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
|
return sqlDatabaseServers;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAzureSqlDatabases(account?: Account, subscription?: azureResource.AzureResourceSubscription, resourceGroupName?: string, serverName?: string): Promise<azure.AzureSqlDatabase[]> {
|
||||||
|
if (account && subscription && resourceGroupName && serverName) {
|
||||||
|
try {
|
||||||
|
const databases = await azure.getAvailableSqlDatabases(account, subscription, resourceGroupName, serverName);
|
||||||
|
return databases.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
|
} catch (e) {
|
||||||
|
logError(TelemetryViews.Utils, 'utils.getAzureSqlDatabases', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
export async function getVirtualMachines(account?: Account, subscription?: azureResource.AzureResourceSubscription): Promise<azure.SqlVMServer[]> {
|
export async function getVirtualMachines(account?: Account, subscription?: azureResource.AzureResourceSubscription): Promise<azure.SqlVMServer[]> {
|
||||||
let virtualMachines: azure.SqlVMServer[] = [];
|
let virtualMachines: azure.SqlVMServer[] = [];
|
||||||
try {
|
try {
|
||||||
@@ -705,30 +592,6 @@ export async function getVirtualMachines(account?: Account, subscription?: azure
|
|||||||
return virtualMachines;
|
return virtualMachines;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getVirtualMachinesDropdownValues(virtualMachines: azure.SqlVMServer[], location: azureResource.AzureLocation, resourceGroup: azureResource.AzureResourceResourceGroup): Promise<CategoryValue[]> {
|
|
||||||
let virtualMachineValues: CategoryValue[] = [];
|
|
||||||
if (location && resourceGroup) {
|
|
||||||
virtualMachines.forEach((virtualMachine) => {
|
|
||||||
if (virtualMachine.location.toLowerCase() === location.name.toLowerCase() && azure.getResourceGroupFromId(virtualMachine.id).toLowerCase() === resourceGroup.name.toLowerCase()) {
|
|
||||||
virtualMachineValues.push({
|
|
||||||
name: virtualMachine.id,
|
|
||||||
displayName: virtualMachine.name
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (virtualMachineValues.length === 0) {
|
|
||||||
virtualMachineValues = [
|
|
||||||
{
|
|
||||||
displayName: constants.NO_VIRTUAL_MACHINE_FOUND,
|
|
||||||
name: ''
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
return virtualMachineValues;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getStorageAccounts(account?: Account, subscription?: azureResource.AzureResourceSubscription): Promise<azure.StorageAccount[]> {
|
export async function getStorageAccounts(account?: Account, subscription?: azureResource.AzureResourceSubscription): Promise<azure.StorageAccount[]> {
|
||||||
let storageAccounts: azure.StorageAccount[] = [];
|
let storageAccounts: azure.StorageAccount[] = [];
|
||||||
try {
|
try {
|
||||||
@@ -742,65 +605,18 @@ export async function getStorageAccounts(account?: Account, subscription?: azure
|
|||||||
return storageAccounts;
|
return storageAccounts;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getStorageAccountsDropdownValues(storageAccounts: azure.StorageAccount[], location: azureResource.AzureLocation, resourceGroup: azureResource.AzureResourceResourceGroup): Promise<CategoryValue[]> {
|
|
||||||
let storageAccountValues: CategoryValue[] = [];
|
|
||||||
storageAccounts.forEach((storageAccount) => {
|
|
||||||
if (storageAccount.location.toLowerCase() === location.name.toLowerCase() && storageAccount.resourceGroup?.toLowerCase() === resourceGroup.name.toLowerCase()) {
|
|
||||||
storageAccountValues.push({
|
|
||||||
name: storageAccount.id,
|
|
||||||
displayName: storageAccount.name
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (storageAccountValues.length === 0) {
|
|
||||||
storageAccountValues = [
|
|
||||||
{
|
|
||||||
displayName: constants.NO_STORAGE_ACCOUNT_FOUND,
|
|
||||||
name: ''
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
return storageAccountValues;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getAzureSqlMigrationServices(account?: Account, subscription?: azureResource.AzureResourceSubscription): Promise<azure.SqlMigrationService[]> {
|
export async function getAzureSqlMigrationServices(account?: Account, subscription?: azureResource.AzureResourceSubscription): Promise<azure.SqlMigrationService[]> {
|
||||||
let sqlMigrationServices: azure.SqlMigrationService[] = [];
|
|
||||||
try {
|
try {
|
||||||
if (account && subscription) {
|
if (account && subscription) {
|
||||||
sqlMigrationServices = (await azure.getSqlMigrationServices(account, subscription)).filter(dms => {
|
const services = await azure.getSqlMigrationServices(account, subscription);
|
||||||
return dms.properties.provisioningState === ProvisioningState.Succeeded;
|
return services
|
||||||
});
|
.filter(dms => dms.properties.provisioningState === constants.ProvisioningState.Succeeded)
|
||||||
|
.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logError(TelemetryViews.Utils, 'utils.getAzureSqlMigrationServices', e);
|
logError(TelemetryViews.Utils, 'utils.getAzureSqlMigrationServices', e);
|
||||||
}
|
}
|
||||||
sqlMigrationServices.sort((a, b) => a.name.localeCompare(b.name));
|
return [];
|
||||||
return sqlMigrationServices;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getAzureSqlMigrationServicesDropdownValues(sqlMigrationServices: azure.SqlMigrationService[], location: azureResource.AzureLocation, resourceGroup: azureResource.AzureResourceResourceGroup): Promise<CategoryValue[]> {
|
|
||||||
let SqlMigrationServicesValues: CategoryValue[] = [];
|
|
||||||
if (location && resourceGroup) {
|
|
||||||
sqlMigrationServices.forEach((sqlMigrationService) => {
|
|
||||||
if (sqlMigrationService.location.toLowerCase() === location.name.toLowerCase() && sqlMigrationService.properties.resourceGroup.toLowerCase() === resourceGroup.name.toLowerCase()) {
|
|
||||||
SqlMigrationServicesValues.push({
|
|
||||||
name: sqlMigrationService.id,
|
|
||||||
displayName: sqlMigrationService.name
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (SqlMigrationServicesValues.length === 0) {
|
|
||||||
SqlMigrationServicesValues = [
|
|
||||||
{
|
|
||||||
displayName: constants.SQL_MIGRATION_SERVICE_NOT_FOUND_ERROR,
|
|
||||||
name: ''
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
return SqlMigrationServicesValues;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getBlobContainer(account?: Account, subscription?: azureResource.AzureResourceSubscription, storageAccount?: azure.StorageAccount): Promise<azureResource.BlobContainer[]> {
|
export async function getBlobContainer(account?: Account, subscription?: azureResource.AzureResourceSubscription, storageAccount?: azure.StorageAccount): Promise<azureResource.BlobContainer[]> {
|
||||||
@@ -816,25 +632,6 @@ export async function getBlobContainer(account?: Account, subscription?: azureRe
|
|||||||
return blobContainers;
|
return blobContainers;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getBlobContainersValues(blobContainers: azureResource.BlobContainer[]): Promise<CategoryValue[]> {
|
|
||||||
let blobContainersValues: CategoryValue[] = [];
|
|
||||||
blobContainers.forEach((blobContainer) => {
|
|
||||||
blobContainersValues.push({
|
|
||||||
name: blobContainer.id,
|
|
||||||
displayName: blobContainer.name
|
|
||||||
});
|
|
||||||
});
|
|
||||||
if (blobContainersValues.length === 0) {
|
|
||||||
blobContainersValues = [
|
|
||||||
{
|
|
||||||
displayName: constants.NO_BLOBCONTAINERS_FOUND,
|
|
||||||
name: ''
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
return blobContainersValues;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getBlobLastBackupFileNames(account?: Account, subscription?: azureResource.AzureResourceSubscription, storageAccount?: azure.StorageAccount, blobContainer?: azureResource.BlobContainer): Promise<azureResource.Blob[]> {
|
export async function getBlobLastBackupFileNames(account?: Account, subscription?: azureResource.AzureResourceSubscription, storageAccount?: azure.StorageAccount, blobContainer?: azureResource.BlobContainer): Promise<azureResource.Blob[]> {
|
||||||
let lastFileNames: azureResource.Blob[] = [];
|
let lastFileNames: azureResource.Blob[] = [];
|
||||||
try {
|
try {
|
||||||
@@ -848,39 +645,91 @@ export async function getBlobLastBackupFileNames(account?: Account, subscription
|
|||||||
return lastFileNames;
|
return lastFileNames;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getBlobLastBackupFileNamesValues(lastFileNames: azureResource.Blob[]): Promise<CategoryValue[]> {
|
export function getAzureResourceDropdownValues(
|
||||||
let lastFileNamesValues: CategoryValue[] = [];
|
azureResources: { location: string, id: string, name: string }[],
|
||||||
lastFileNames.forEach((lastFileName) => {
|
location: azureResource.AzureLocation | undefined,
|
||||||
lastFileNamesValues.push({
|
resourceGroup: string | undefined,
|
||||||
name: lastFileName.name,
|
resourceNotFoundMessage: string): CategoryValue[] {
|
||||||
displayName: lastFileName.name
|
|
||||||
|
if (location?.name && resourceGroup && azureResources?.length > 0) {
|
||||||
|
const locationName = location.name.toLowerCase();
|
||||||
|
const resourceGroupName = resourceGroup.toLowerCase();
|
||||||
|
|
||||||
|
return azureResources
|
||||||
|
.filter(resource =>
|
||||||
|
resource.location?.toLowerCase() === locationName &&
|
||||||
|
azure.getResourceGroupFromId(resource.id)?.toLowerCase() === resourceGroupName)
|
||||||
|
.map(resource => {
|
||||||
|
return { name: resource.id, displayName: resource.name };
|
||||||
});
|
});
|
||||||
});
|
|
||||||
if (lastFileNamesValues.length === 0) {
|
|
||||||
lastFileNamesValues = [
|
|
||||||
{
|
|
||||||
displayName: constants.NO_BLOBFILES_FOUND,
|
|
||||||
name: ''
|
|
||||||
}
|
}
|
||||||
];
|
|
||||||
|
return [{ name: '', displayName: resourceNotFoundMessage }];
|
||||||
}
|
}
|
||||||
return lastFileNamesValues;
|
|
||||||
|
export function getResourceDropdownValues(resources: { id: string, name: string }[], resourceNotFoundMessage: string): CategoryValue[] {
|
||||||
|
return resources?.map(resource => { return { name: resource.id, displayName: resource.name }; })
|
||||||
|
|| [{ name: '', displayName: resourceNotFoundMessage }];
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAzureTenantsDropdownValues(tenants: Tenant[]): Promise<CategoryValue[]> {
|
||||||
|
return tenants?.map(tenant => { return { name: tenant.id, displayName: tenant.displayName }; })
|
||||||
|
|| [{ name: '', displayName: constants.ACCOUNT_SELECTION_PAGE_NO_LINKED_ACCOUNTS_ERROR }];
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAzureLocationsDropdownValues(locations: azureResource.AzureLocation[]): Promise<CategoryValue[]> {
|
||||||
|
return locations?.map(location => { return { name: location.name, displayName: location.displayName }; })
|
||||||
|
|| [{ name: '', displayName: constants.NO_LOCATION_FOUND }];
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getBlobLastBackupFileNamesValues(blobs: azureResource.Blob[]): Promise<CategoryValue[]> {
|
||||||
|
return blobs?.map(blob => { return { name: blob.name, displayName: blob.name }; })
|
||||||
|
|| [{ name: '', displayName: constants.NO_BLOBFILES_FOUND }];
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateControlDisplay(control: Component, visible: boolean, displayStyle: DisplayType = 'inline'): Promise<void> {
|
||||||
|
const display = visible ? displayStyle : 'none';
|
||||||
|
control.display = display;
|
||||||
|
await control.updateCssStyles({ 'display': display });
|
||||||
|
await control.updateProperties({ 'display': display });
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generateGuid(): string {
|
||||||
|
const hexValues: string[] = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'];
|
||||||
|
// c.f. rfc4122 (UUID version 4 = xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx)
|
||||||
|
let oct: string = '';
|
||||||
|
let tmp: number;
|
||||||
|
/* tslint:disable:no-bitwise */
|
||||||
|
for (let a: number = 0; a < 4; a++) {
|
||||||
|
tmp = (4294967296 * Math.random()) | 0;
|
||||||
|
oct += hexValues[tmp & 0xF] +
|
||||||
|
hexValues[tmp >> 4 & 0xF] +
|
||||||
|
hexValues[tmp >> 8 & 0xF] +
|
||||||
|
hexValues[tmp >> 12 & 0xF] +
|
||||||
|
hexValues[tmp >> 16 & 0xF] +
|
||||||
|
hexValues[tmp >> 20 & 0xF] +
|
||||||
|
hexValues[tmp >> 24 & 0xF] +
|
||||||
|
hexValues[tmp >> 28 & 0xF];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 'Set the two most significant bits (bits 6 and 7) of the clock_seq_hi_and_reserved to zero and one, respectively'
|
||||||
|
const clockSequenceHi: string = hexValues[8 + (Math.random() * 4) | 0];
|
||||||
|
return `${oct.substr(0, 8)}-${oct.substr(9, 4)}-4${oct.substr(13, 3)}-${clockSequenceHi}${oct.substr(16, 3)}-${oct.substr(19, 12)}`;
|
||||||
|
/* tslint:enable:no-bitwise */
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function promptUserForFolder(): Promise<string> {
|
export async function promptUserForFolder(): Promise<string> {
|
||||||
let path = '';
|
const options: vscode.OpenDialogOptions = {
|
||||||
|
|
||||||
let options: vscode.OpenDialogOptions = {
|
|
||||||
defaultUri: vscode.Uri.file(getUserHome()!),
|
defaultUri: vscode.Uri.file(getUserHome()!),
|
||||||
canSelectFiles: false,
|
canSelectFiles: false,
|
||||||
canSelectFolders: true,
|
canSelectFolders: true,
|
||||||
canSelectMany: false,
|
canSelectMany: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
let fileUris = await vscode.window.showOpenDialog(options);
|
const fileUris = await vscode.window.showOpenDialog(options);
|
||||||
if (fileUris && fileUris?.length > 0 && fileUris[0]) {
|
if (fileUris && fileUris.length > 0 && fileUris[0]) {
|
||||||
path = fileUris[0].fsPath;
|
return fileUris[0].fsPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
return path;
|
return '';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
import * as azdata from 'azdata';
|
import * as azdata from 'azdata';
|
||||||
import { DatabaseMigration } from '../api/azure';
|
import { DatabaseMigration } from '../api/azure';
|
||||||
import { MigrationStatus } from '../models/migrationLocalStorage';
|
import { DefaultSettingValue } from '../api/utils';
|
||||||
import { FileStorageType, MigrationMode, MigrationTargetType } from '../models/stateMachine';
|
import { FileStorageType, MigrationMode, MigrationTargetType } from '../models/stateMachine';
|
||||||
import * as loc from './strings';
|
import * as loc from './strings';
|
||||||
|
|
||||||
@@ -143,6 +143,11 @@ export function getMigrationStatus(migration: DatabaseMigration | undefined): st
|
|||||||
?? migration?.properties.provisioningState;
|
?? migration?.properties.provisioningState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getMigrationStatusString(migration: DatabaseMigration | undefined): string {
|
||||||
|
const migrationStatus = getMigrationStatus(migration) ?? DefaultSettingValue;
|
||||||
|
return loc.StatusLookup[migrationStatus] ?? migrationStatus;
|
||||||
|
}
|
||||||
|
|
||||||
export function hasMigrationOperationId(migration: DatabaseMigration | undefined): boolean {
|
export function hasMigrationOperationId(migration: DatabaseMigration | undefined): boolean {
|
||||||
const migrationId = migration?.id ?? '';
|
const migrationId = migration?.id ?? '';
|
||||||
const migationOperationId = migration?.properties?.migrationOperationId ?? '';
|
const migationOperationId = migration?.properties?.migrationOperationId ?? '';
|
||||||
@@ -153,41 +158,41 @@ export function hasMigrationOperationId(migration: DatabaseMigration | undefined
|
|||||||
export function canCancelMigration(migration: DatabaseMigration | undefined): boolean {
|
export function canCancelMigration(migration: DatabaseMigration | undefined): boolean {
|
||||||
const status = getMigrationStatus(migration);
|
const status = getMigrationStatus(migration);
|
||||||
return hasMigrationOperationId(migration)
|
return hasMigrationOperationId(migration)
|
||||||
&& (status === MigrationStatus.InProgress ||
|
&& (status === loc.MigrationStatus.InProgress ||
|
||||||
status === MigrationStatus.Retriable ||
|
status === loc.MigrationStatus.Retriable ||
|
||||||
status === MigrationStatus.Creating);
|
status === loc.MigrationStatus.Creating);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function canDeleteMigration(migration: DatabaseMigration | undefined): boolean {
|
export function canDeleteMigration(migration: DatabaseMigration | undefined): boolean {
|
||||||
const status = getMigrationStatus(migration);
|
const status = getMigrationStatus(migration);
|
||||||
return status === MigrationStatus.Canceled
|
return status === loc.MigrationStatus.Canceled
|
||||||
|| status === MigrationStatus.Failed
|
|| status === loc.MigrationStatus.Failed
|
||||||
|| status === MigrationStatus.Retriable
|
|| status === loc.MigrationStatus.Retriable
|
||||||
|| status === MigrationStatus.Succeeded;
|
|| status === loc.MigrationStatus.Succeeded;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function canRetryMigration(migration: DatabaseMigration | undefined): boolean {
|
export function canRetryMigration(migration: DatabaseMigration | undefined): boolean {
|
||||||
const status = getMigrationStatus(migration);
|
const status = getMigrationStatus(migration);
|
||||||
return status === MigrationStatus.Canceled
|
return status === loc.MigrationStatus.Canceled
|
||||||
|| status === MigrationStatus.Retriable
|
|| status === loc.MigrationStatus.Retriable
|
||||||
|| status === MigrationStatus.Failed
|
|| status === loc.MigrationStatus.Failed
|
||||||
|| status === MigrationStatus.Succeeded;
|
|| status === loc.MigrationStatus.Succeeded;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function canCutoverMigration(migration: DatabaseMigration | undefined): boolean {
|
export function canCutoverMigration(migration: DatabaseMigration | undefined): boolean {
|
||||||
const status = getMigrationStatus(migration);
|
const status = getMigrationStatus(migration);
|
||||||
return hasMigrationOperationId(migration)
|
return hasMigrationOperationId(migration)
|
||||||
&& status === MigrationStatus.InProgress
|
&& status === loc.MigrationStatus.InProgress
|
||||||
&& isOnlineMigration(migration)
|
&& isOnlineMigration(migration)
|
||||||
&& isFullBackupRestored(migration);
|
&& isFullBackupRestored(migration);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isActiveMigration(migration: DatabaseMigration | undefined): boolean {
|
export function isActiveMigration(migration: DatabaseMigration | undefined): boolean {
|
||||||
const status = getMigrationStatus(migration);
|
const status = getMigrationStatus(migration);
|
||||||
return status === MigrationStatus.Completing
|
return status === loc.MigrationStatus.Completing
|
||||||
|| status === MigrationStatus.Retriable
|
|| status === loc.MigrationStatus.Retriable
|
||||||
|| status === MigrationStatus.Creating
|
|| status === loc.MigrationStatus.Creating
|
||||||
|| status === MigrationStatus.InProgress;
|
|| status === loc.MigrationStatus.InProgress;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isFullBackupRestored(migration: DatabaseMigration | undefined): boolean {
|
export function isFullBackupRestored(migration: DatabaseMigration | undefined): boolean {
|
||||||
|
|||||||
@@ -6,11 +6,36 @@
|
|||||||
import { AzureAccount } from 'azurecore';
|
import { AzureAccount } from 'azurecore';
|
||||||
import * as nls from 'vscode-nls';
|
import * as nls from 'vscode-nls';
|
||||||
import { EOL } from 'os';
|
import { EOL } from 'os';
|
||||||
import { MigrationStatus } from '../models/migrationLocalStorage';
|
|
||||||
import { MigrationSourceAuthenticationType } from '../models/stateMachine';
|
import { MigrationSourceAuthenticationType } from '../models/stateMachine';
|
||||||
import { ParallelCopyTypeCodes, PipelineStatusCodes } from './helper';
|
import { formatNumber, ParallelCopyTypeCodes, PipelineStatusCodes } from './helper';
|
||||||
const localize = nls.loadMessageBundle();
|
const localize = nls.loadMessageBundle();
|
||||||
|
|
||||||
|
export enum MigrationStatus {
|
||||||
|
Failed = 'Failed',
|
||||||
|
Succeeded = 'Succeeded',
|
||||||
|
InProgress = 'InProgress',
|
||||||
|
Canceled = 'Canceled',
|
||||||
|
Completing = 'Completing',
|
||||||
|
Creating = 'Creating',
|
||||||
|
Canceling = 'Canceling',
|
||||||
|
Retriable = 'Retriable',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ProvisioningState {
|
||||||
|
Failed = 'Failed',
|
||||||
|
Succeeded = 'Succeeded',
|
||||||
|
Creating = 'Creating'
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum BackupFileInfoStatus {
|
||||||
|
Arrived = 'Arrived',
|
||||||
|
Uploading = 'Uploading',
|
||||||
|
Uploaded = 'Uploaded',
|
||||||
|
Restoring = 'Restoring',
|
||||||
|
Restored = 'Restored',
|
||||||
|
Cancelled = 'Cancelled',
|
||||||
|
Ignored = 'Ignored'
|
||||||
|
}
|
||||||
|
|
||||||
// #region wizard
|
// #region wizard
|
||||||
export function WIZARD_TITLE(instanceName: string): string {
|
export function WIZARD_TITLE(instanceName: string): string {
|
||||||
@@ -67,12 +92,13 @@ export const REFRESH_ASSESSMENT_BUTTON_LABEL = localize('sql.migration.refresh.a
|
|||||||
export const SKU_RECOMMENDATION_CHOOSE_A_TARGET = localize('sql.migration.wizard.sku.choose_a_target', "Choose your Azure SQL target");
|
export const SKU_RECOMMENDATION_CHOOSE_A_TARGET = localize('sql.migration.wizard.sku.choose_a_target', "Choose your Azure SQL target");
|
||||||
|
|
||||||
export const SKU_RECOMMENDATION_MI_CARD_TEXT = localize('sql.migration.sku.mi.card.title', "Azure SQL Managed Instance");
|
export const SKU_RECOMMENDATION_MI_CARD_TEXT = localize('sql.migration.sku.mi.card.title', "Azure SQL Managed Instance");
|
||||||
export const SKU_RECOMMENDATION_DB_CARD_TEXT = localize('sql.migration.sku.db.card.title', "Azure SQL Database");
|
export const SKU_RECOMMENDATION_SQLDB_CARD_TEXT = localize('sql.migration.sku.sqldb.card.title', "Azure SQL Database");
|
||||||
export const SKU_RECOMMENDATION_VM_CARD_TEXT = localize('sql.migration.sku.vm.card.title', "SQL Server on Azure Virtual Machine");
|
export const SKU_RECOMMENDATION_VM_CARD_TEXT = localize('sql.migration.sku.vm.card.title', "SQL Server on Azure Virtual Machine");
|
||||||
export const SELECT_AZURE_MI = localize('sql.migration.select.azure.mi', "Select your target Azure subscription and your target Azure SQL Managed Instance.");
|
export const SELECT_AZURE_MI = localize('sql.migration.select.azure.mi', "Select your target Azure subscription and your target Azure SQL Managed Instance.");
|
||||||
export const SELECT_AZURE_VM = localize('sql.migration.select.azure.vm', "Select your target Azure Subscription and your target SQL Server on Azure Virtual Machine for your target.");
|
export const SELECT_AZURE_VM = localize('sql.migration.select.azure.vm', "Select your target Azure Subscription and your target SQL Server on Azure Virtual Machine for your target.");
|
||||||
export const SKU_RECOMMENDATION_VIEW_ASSESSMENT_MI = localize('sql.migration.sku.recommendation.view.assessment.mi', "To migrate to Azure SQL Managed Instance, view assessment results and select one or more databases.");
|
export const SKU_RECOMMENDATION_VIEW_ASSESSMENT_MI = localize('sql.migration.sku.recommendation.view.assessment.mi', "To migrate to Azure SQL Managed Instance, view assessment results and select one or more databases.");
|
||||||
export const SKU_RECOMMENDATION_VIEW_ASSESSMENT_VM = localize('sql.migration.sku.recommendation.view.assessment.vm', "To migrate to SQL Server on Azure Virtual Machine, view assessment results and select one or more databases.");
|
export const SKU_RECOMMENDATION_VIEW_ASSESSMENT_VM = localize('sql.migration.sku.recommendation.view.assessment.vm', "To migrate to SQL Server on Azure Virtual Machine, view assessment results and select one or more databases.");
|
||||||
|
export const SKU_RECOMMENDATION_VIEW_ASSESSMENT_SQLDB = localize('sql.migration.sku.recommendation.view.assessment.sqldb', "To migrate to Azure SQL Database, view assessment results and select one or more databases.");
|
||||||
export const VIEW_SELECT_BUTTON_LABEL = localize('sql.migration.view.select.button.label', "View/Select");
|
export const VIEW_SELECT_BUTTON_LABEL = localize('sql.migration.view.select.button.label', "View/Select");
|
||||||
export function TOTAL_DATABASES_SELECTED(selectedDbCount: number, totalDbCount: number): string {
|
export function TOTAL_DATABASES_SELECTED(selectedDbCount: number, totalDbCount: number): string {
|
||||||
return localize('total.databases.selected', "{0} of {1} databases selected", selectedDbCount, totalDbCount);
|
return localize('total.databases.selected', "{0} of {1} databases selected", selectedDbCount, totalDbCount);
|
||||||
@@ -148,7 +174,11 @@ export function ASSESSED_DBS(totalDbs: number): string {
|
|||||||
return localize('sql.migration.assessed.databases', "(for {0} assessed databases)", totalDbs);
|
return localize('sql.migration.assessed.databases', "(for {0} assessed databases)", totalDbs);
|
||||||
}
|
}
|
||||||
export function RECOMMENDATIONS_AVAILABLE(totalDbs: number): string {
|
export function RECOMMENDATIONS_AVAILABLE(totalDbs: number): string {
|
||||||
return localize('sql.migration.sku.available.recommendations', "{0} recommendations available", totalDbs);
|
if (totalDbs === 1) {
|
||||||
|
return localize('sql.migration.sku.available.recommendations.one', "{0} recommendation available", totalDbs);
|
||||||
|
} else {
|
||||||
|
return localize('sql.migration.sku.available.recommendations.many', "{0} recommendations available", totalDbs);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
export const RECOMMENDATIONS = localize('sql.migration.sku.recommendations', "Recommendations");
|
export const RECOMMENDATIONS = localize('sql.migration.sku.recommendations', "Recommendations");
|
||||||
export const LOADING_RECOMMENDATIONS = localize('sql.migration.sku.recommendations.loading', "Loading...");
|
export const LOADING_RECOMMENDATIONS = localize('sql.migration.sku.recommendations.loading', "Loading...");
|
||||||
@@ -160,8 +190,11 @@ export function VM_CONFIGURATION(vmSize: string, vCPU: number): string {
|
|||||||
export function VM_CONFIGURATION_PREVIEW(dataDisk: string, logDisk: string, temp: string): string {
|
export function VM_CONFIGURATION_PREVIEW(dataDisk: string, logDisk: string, temp: string): string {
|
||||||
return localize('sql.migration.sku.azureConfiguration.vmPreview', "Data: {0}, Log: {1}, tempdb: {2}", dataDisk, logDisk, temp);
|
return localize('sql.migration.sku.azureConfiguration.vmPreview', "Data: {0}, Log: {1}, tempdb: {2}", dataDisk, logDisk, temp);
|
||||||
}
|
}
|
||||||
export function DB_CONFIGURATION(computeTier: string, vCore: number): string {
|
export function SQLDB_CONFIGURATION(computeTier: string, vCore: number): string {
|
||||||
return localize('sql.migration.sku.azureConfiguration.db', "{0} - {1} vCore", computeTier, vCore);
|
return localize('sql.migration.sku.azureConfiguration.sqldb', "{0} - {1} vCore", computeTier, vCore);
|
||||||
|
}
|
||||||
|
export function SQLDB_CONFIGURATION_PREVIEW(hardwareType: string, computeTier: string, vCore: number, storage: number): string {
|
||||||
|
return localize('sql.migration.sku.azureConfiguration.sqldbPreview', "{0} - {1} - {2} vCore - {3} GB", hardwareType, computeTier, vCore, storage);
|
||||||
}
|
}
|
||||||
export function MI_CONFIGURATION(hardwareType: string, computeTier: string, vCore: number): string {
|
export function MI_CONFIGURATION(hardwareType: string, computeTier: string, vCore: number): string {
|
||||||
return localize('sql.migration.sku.azureConfiguration.mi', "{0} - {1} - {2} vCore", hardwareType, computeTier, vCore);
|
return localize('sql.migration.sku.azureConfiguration.mi', "{0} - {1} - {2} vCore", hardwareType, computeTier, vCore);
|
||||||
@@ -253,6 +286,40 @@ export function AZURE_SQL_TARGET_PAGE_DESCRIPTION(targetInstance: string = 'inst
|
|||||||
return localize('sql.migration.wizard.target.description', "Select an Azure account and your target {0}.", targetInstance);
|
return localize('sql.migration.wizard.target.description', "Select an Azure account and your target {0}.", targetInstance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const AZURE_SQL_TARGET_CONNECTION_ERROR_TITLE = localize('sql.migration.wizard.connection.error.title', "An error occurred while conneting to the target server.");
|
||||||
|
export function SQL_TARGET_CONNECTION_ERROR(message: string): string {
|
||||||
|
return localize('sql.migration.wizard.target.connection.error', "Connection error: {0}", message);
|
||||||
|
}
|
||||||
|
export function SQL_TARGET_CONNECTION_SUCCESS(databaseCount: string): string {
|
||||||
|
return localize('sql.migration.wizard.target.connection.success', "Connection was successful. Target databases found: {0}", databaseCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SQL_TARGET_MISSING_SOURCE_DATABASES = localize('sql.migration.wizard.source.missing', 'Connection was successful but did not find any target databases.');
|
||||||
|
export const SQL_TARGET_MAPPING_ERROR_MISSING_TARGET = localize(
|
||||||
|
'sql.migration.wizard.target.missing',
|
||||||
|
'Database mapping error. Missing target databases to migrate. Please configure the target server connection and click connect to collect the list of available database migration targets.');
|
||||||
|
|
||||||
|
export function SQL_TARGET_CONNECTION_DUPLICATE_TARGET_MAPPING(
|
||||||
|
targetDatabaseName: string,
|
||||||
|
sourceDatabaseName: string,
|
||||||
|
mappedSourceDatabaseName: string,
|
||||||
|
): string {
|
||||||
|
return localize(
|
||||||
|
'sql.migration.wizard.target.mapping.error.duplicate',
|
||||||
|
"Database mapping error. Target database '{0}' cannot be selected to as a migration target for database '{1}'. Target database '${targetDatabaseName}' is already selected as a migration target for database '{2}'. Please select a different target database.",
|
||||||
|
targetDatabaseName,
|
||||||
|
sourceDatabaseName,
|
||||||
|
mappedSourceDatabaseName);
|
||||||
|
}
|
||||||
|
|
||||||
|
//`Database mapping error. Source database '${sourceDatabaseName}' is not mapped to a target database. Please select a target database to migrate to.`
|
||||||
|
export function SQL_TARGET_CONNECTION_SOURCE_NOT_MAPPED(sourceDatabaseName: string): string {
|
||||||
|
return localize(
|
||||||
|
'sql.migration.wizard.target.source.mapping.error',
|
||||||
|
"Database mapping error. Source database '{0}' is not mapped to a target database. Please select a target database to migrate to.",
|
||||||
|
sourceDatabaseName);
|
||||||
|
}
|
||||||
|
|
||||||
// Managed Instance
|
// Managed Instance
|
||||||
export const AZURE_SQL_DATABASE_MANAGED_INSTANCE = localize('sql.migration.azure.sql.database.managed.instance', "Azure SQL Managed Instance");
|
export const AZURE_SQL_DATABASE_MANAGED_INSTANCE = localize('sql.migration.azure.sql.database.managed.instance', "Azure SQL Managed Instance");
|
||||||
export const NO_MANAGED_INSTANCE_FOUND = localize('sql.migration.no.managedInstance.found', "No managed instances found.");
|
export const NO_MANAGED_INSTANCE_FOUND = localize('sql.migration.no.managedInstance.found', "No managed instances found.");
|
||||||
@@ -261,7 +328,6 @@ export function UNAVAILABLE_TARGET_PREFIX(targetName: string): string {
|
|||||||
return localize('sql.migration.unavailable.target', "(Unavailable) {0}", targetName);
|
return localize('sql.migration.unavailable.target', "(Unavailable) {0}", targetName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Virtual Machine
|
// Virtual Machine
|
||||||
export const AZURE_SQL_DATABASE_VIRTUAL_MACHINE = localize('sql.migration.azure.sql.database.virtual.machine', "SQL Server on Azure Virtual Machines");
|
export const AZURE_SQL_DATABASE_VIRTUAL_MACHINE = localize('sql.migration.azure.sql.database.virtual.machine', "SQL Server on Azure Virtual Machines");
|
||||||
export const AZURE_SQL_DATABASE_VIRTUAL_MACHINE_SHORT = localize('sql.migration.azure.sql.database.virtual.machine.short', "SQL Server on Azure VM");
|
export const AZURE_SQL_DATABASE_VIRTUAL_MACHINE_SHORT = localize('sql.migration.azure.sql.database.virtual.machine.short', "SQL Server on Azure VM");
|
||||||
@@ -269,7 +335,10 @@ export const NO_VIRTUAL_MACHINE_FOUND = localize('sql.migration.no.virtualMachin
|
|||||||
export const INVALID_VIRTUAL_MACHINE_ERROR = localize('sql.migration.invalid.virtualMachine.error', "To continue, select a valid virtual machine.");
|
export const INVALID_VIRTUAL_MACHINE_ERROR = localize('sql.migration.invalid.virtualMachine.error', "To continue, select a valid virtual machine.");
|
||||||
|
|
||||||
// Azure SQL Database
|
// Azure SQL Database
|
||||||
export const AZURE_SQL_DATABASE = localize('sql.migration.azure.sql.database', "Azure SQL Database");
|
export const AZURE_SQL_DATABASE = localize('sql.migration.azure.sql.database', "Azure SQL Database Server");
|
||||||
|
export const NO_SQL_DATABASE_SERVER_FOUND = localize('sql.migration.no.sqldatabaseserver.found', "No Azure SQL database servers found.");
|
||||||
|
export const NO_SQL_DATABASE_FOUND = localize('sql.migration.no.sqldatabase.found', "No Azure SQL databases found.");
|
||||||
|
export const INVALID_SQL_DATABASE_ERROR = localize('sql.migration.invalid.sqldatabase.error', "To continue, select a valid Azure SQL Database server.");
|
||||||
|
|
||||||
// Target info tooltip
|
// Target info tooltip
|
||||||
export const TARGET_SUBSCRIPTION_INFO = localize('sql.migration.sku.subscription', "Subscription name for your Azure SQL target");
|
export const TARGET_SUBSCRIPTION_INFO = localize('sql.migration.sku.subscription', "Subscription name for your Azure SQL target");
|
||||||
@@ -313,6 +382,10 @@ export function MI_NOT_READY_ERROR(miName: string, state: string): string {
|
|||||||
return localize('sql.migration.mi.not.ready', "The managed instance '{0}' is unavailable for migration because it is currently in the '{1}' state. To continue, select an available managed instance.", miName, state);
|
return localize('sql.migration.mi.not.ready', "The managed instance '{0}' is unavailable for migration because it is currently in the '{1}' state. To continue, select an available managed instance.", miName, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function SQLDB_NOT_READY_ERROR(sqldbName: string, state: string): string {
|
||||||
|
return localize('sql.migration.sqldb.not.ready', "The SQL database server '{0}' is unavailable for migration because it is currently in the '{1}' state. To continue, select an available SQL database server.", sqldbName, state);
|
||||||
|
}
|
||||||
|
|
||||||
export const SELECT_AN_ACCOUNT = localize('sql.migration.select.service.select.a.', "Sign into Azure and select an account");
|
export const SELECT_AN_ACCOUNT = localize('sql.migration.select.service.select.a.', "Sign into Azure and select an account");
|
||||||
export const SELECT_A_TENANT = localize('sql.migration.select.service.select.a.tenant', "Select a tenant");
|
export const SELECT_A_TENANT = localize('sql.migration.select.service.select.a.tenant', "Select a tenant");
|
||||||
export const SELECT_A_SUBSCRIPTION = localize('sql.migration.select.service.select.a.subscription', "Select a subscription");
|
export const SELECT_A_SUBSCRIPTION = localize('sql.migration.select.service.select.a.subscription', "Select a subscription");
|
||||||
@@ -331,6 +404,7 @@ export function ACCOUNT_CREDENTIALS_REFRESH(accountName: string): string {
|
|||||||
"{0} (requires credentials refresh)",
|
"{0} (requires credentials refresh)",
|
||||||
accountName);
|
accountName);
|
||||||
}
|
}
|
||||||
|
export const SELECT_SERVICE_PLACEHOLDER = localize('sql.migration.select.service.select.migration.target', "Select a target server.");
|
||||||
|
|
||||||
// database backup page
|
// database backup page
|
||||||
export const DATABASE_BACKUP_PAGE_TITLE = localize('sql.migration.database.page.title', "Database backup");
|
export const DATABASE_BACKUP_PAGE_TITLE = localize('sql.migration.database.page.title', "Database backup");
|
||||||
@@ -361,6 +435,18 @@ export const DATABASE_BACKUP_BLOB_STORAGE_TABLE_HELP_TEXT = localize('sql.migrat
|
|||||||
export const DATABASE_BACKUP_BLOB_STORAGE_SUBSCRIPTION_LABEL = localize('sql.migration.blob.storage.subscription.label', "Subscription");
|
export const DATABASE_BACKUP_BLOB_STORAGE_SUBSCRIPTION_LABEL = localize('sql.migration.blob.storage.subscription.label', "Subscription");
|
||||||
export const DATABASE_BACKUP_MIGRATION_MODE_LABEL = localize('sql.migration.database.migration.mode.label', "Migration mode");
|
export const DATABASE_BACKUP_MIGRATION_MODE_LABEL = localize('sql.migration.database.migration.mode.label', "Migration mode");
|
||||||
export const DATABASE_BACKUP_MIGRATION_MODE_DESCRIPTION = localize('sql.migration.database.migration.mode.description', "To migrate to the Azure SQL target, choose a migration mode based on your downtime requirements.");
|
export const DATABASE_BACKUP_MIGRATION_MODE_DESCRIPTION = localize('sql.migration.database.migration.mode.description', "To migrate to the Azure SQL target, choose a migration mode based on your downtime requirements.");
|
||||||
|
export const DATABASE_TABLE_SELECTION_LABEL = localize('sql.migration.database.table.selection.label', "Migration table selection");
|
||||||
|
export const DATABASE_TABLE_SELECTION_DESCRIPTION = localize('sql.migration.database.table.selection.description', "To migrate to the Azure SQL target, select tables in each database for migration.");
|
||||||
|
export const DATABASE_TABLE_REFRESH_LABEL = localize('sql.migration.database.table.refresh.label', "Refresh");
|
||||||
|
export const DATABASE_TABLE_SOURCE_DATABASE_COLUMN_LABEL = localize('sql.migration.database.table.source.column.label', "Source database");
|
||||||
|
export const DATABASE_TABLE_TARGET_DATABASE_COLUMN_LABEL = localize('sql.migration.database.table.target.column.label', "Target database");
|
||||||
|
export const DATABASE_TABLE_SELECTED_TABLES_COLUMN_LABEL = localize('sql.migration.database.table.tables.column.label', "Select tables");
|
||||||
|
export const DATABASE_TABLE_CONNECTION_ERROR = localize('sql.migration.database.connection.error', "An error occurred while connecting to target migration database.");
|
||||||
|
export function DATABASE_TABLE_CONNECTION_ERROR_MESSAGE(message: string): string {
|
||||||
|
return localize('sql.migration.database.connection.error.message', "Connection error:{0} {1}", EOL, message);
|
||||||
|
}
|
||||||
|
export const DATABASE_TABLE_DATA_LOADING = localize('sql.migration.database.loading', "Loading database table list..");
|
||||||
|
export const DATABASE_TABLE_VALIDATE_SELECTION_MESSAGE = localize('sql.migration.database.validate.selection', "Please select target database tables to migrate to. At least one database with one table is required.");
|
||||||
export const DATABASE_BACKUP_MIGRATION_MODE_ONLINE_LABEL = localize('sql.migration.database.migration.mode.online.label', "Online migration");
|
export const DATABASE_BACKUP_MIGRATION_MODE_ONLINE_LABEL = localize('sql.migration.database.migration.mode.online.label', "Online migration");
|
||||||
export const DATABASE_BACKUP_MIGRATION_MODE_ONLINE_DESCRIPTION = localize('sql.migration.database.migration.mode.online.description', "Application downtime is limited to cutover at the end of migration.");
|
export const DATABASE_BACKUP_MIGRATION_MODE_ONLINE_DESCRIPTION = localize('sql.migration.database.migration.mode.online.description', "Application downtime is limited to cutover at the end of migration.");
|
||||||
export const DATABASE_BACKUP_MIGRATION_MODE_OFFLINE_LABEL = localize('sql.migration.database.migration.mode.offline.label', "Offline migration");
|
export const DATABASE_BACKUP_MIGRATION_MODE_OFFLINE_LABEL = localize('sql.migration.database.migration.mode.offline.label', "Offline migration");
|
||||||
@@ -378,6 +464,22 @@ export const INVALID_SUBSCRIPTION_ERROR = localize('sql.migration.invalid.subscr
|
|||||||
export const INVALID_LOCATION_ERROR = localize('sql.migration.invalid.location.error', "To continue, select a valid location.");
|
export const INVALID_LOCATION_ERROR = localize('sql.migration.invalid.location.error', "To continue, select a valid location.");
|
||||||
export const INVALID_RESOURCE_GROUP_ERROR = localize('sql.migration.invalid.resourceGroup.error', "To continue, select a valid resource group.");
|
export const INVALID_RESOURCE_GROUP_ERROR = localize('sql.migration.invalid.resourceGroup.error', "To continue, select a valid resource group.");
|
||||||
export const INVALID_STORAGE_ACCOUNT_ERROR = localize('sql.migration.invalid.storageAccount.error', "To continue, select a valid storage account.");
|
export const INVALID_STORAGE_ACCOUNT_ERROR = localize('sql.migration.invalid.storageAccount.error', "To continue, select a valid storage account.");
|
||||||
|
export const MISSING_TARGET_USERNAME_ERROR = localize('sql.migration.missing.targetUserName.error', "To continue, enter a valid target user name.");
|
||||||
|
export const MISSING_TARGET_PASSWORD_ERROR = localize('sql.migration.missing.targetPassword.error', "To continue, enter a valid target password.");
|
||||||
|
|
||||||
|
export const TARGET_TABLE_NOT_EMPTY = localize('sql.migration.target.table.not.empty', "Target table is not empty.");
|
||||||
|
export const TARGET_TABLE_MISSING = localize('sql.migration.target.table.missing', "Target table does not exist");
|
||||||
|
export const TARGET_USERNAME_LAbEL = localize('sql.migration.username.label', "Target user name");
|
||||||
|
export const TARGET_USERNAME_PLACEHOLDER = localize('sql.migration.username.placeholder', "Enter the target user name");
|
||||||
|
export const TARGET_PASSWORD_LAbEL = localize('sql.migration.password.label', "Target password");
|
||||||
|
export const TARGET_PASSWORD_PLACEHOLDER = localize('sql.migration.password.placeholder', "Enter the target password");
|
||||||
|
export const TARGET_CONNECTION_LABEL = localize('sql.migration.connection.label', "Connect");
|
||||||
|
export const MAP_SOURCE_TARGET_HEADING = localize('sql.migration.map.target.heading', "Map selected source databases to target databases for migration");
|
||||||
|
export const MAP_SOURCE_TARGET_DESCRIPTION = localize('sql.migration.map.target.description', "Select the target database where you would like to migrate your source database to. You can choose a target database for only one source database.");
|
||||||
|
export const MAP_SOURCE_COLUMN = localize('sql.migration.map.source.column', "Source database");
|
||||||
|
export const MAP_TARGET_COLUMN = localize('sql.migration.map.target.column', "Target database");
|
||||||
|
export const MAP_TARGET_PLACEHOLDER = localize('sql.migration.map.target.placeholder', "Select a target database");
|
||||||
|
|
||||||
export function INVALID_BLOB_RESOURCE_GROUP_ERROR(sourceDb: string): string {
|
export function INVALID_BLOB_RESOURCE_GROUP_ERROR(sourceDb: string): string {
|
||||||
return localize('sql.migration.invalid.blob.resourceGroup.error', "To continue, select a valid resource group for source database '{0}'.", sourceDb);
|
return localize('sql.migration.invalid.blob.resourceGroup.error', "To continue, select a valid resource group for source database '{0}'.", sourceDb);
|
||||||
}
|
}
|
||||||
@@ -406,6 +508,26 @@ export const SELECT_RESOURCE_GROUP_PROMPT = localize('sql.migration.blob.resourc
|
|||||||
export const SELECT_STORAGE_ACCOUNT = localize('sql.migration.blob.storageAccount.select', "Select a storage account value first.");
|
export const SELECT_STORAGE_ACCOUNT = localize('sql.migration.blob.storageAccount.select', "Select a storage account value first.");
|
||||||
export const SELECT_BLOB_CONTAINER = localize('sql.migration.blob.container.select', "Select a blob container value first.");
|
export const SELECT_BLOB_CONTAINER = localize('sql.migration.blob.container.select', "Select a blob container value first.");
|
||||||
|
|
||||||
|
export function SELECT_DATABASE_TABLES_TITLE(targetDatabaseName: string): string {
|
||||||
|
return localize('sql.migration.table.select.label', "Select tables for {0}", targetDatabaseName);
|
||||||
|
}
|
||||||
|
export const TABLE_SELECTION_EDIT = localize('sql.migration.table.selection.edit', "Edit");
|
||||||
|
|
||||||
|
export function TABLE_SELECTION_COUNT(selectedCount: number, rowCount: number): string {
|
||||||
|
return localize('sql.migration.table.selection.count', "{0} of {1}", selectedCount, rowCount);
|
||||||
|
}
|
||||||
|
export function TABLE_SELECTED_COUNT(selectedCount: number, rowCount: number): string {
|
||||||
|
return localize('sql.migration.table.selected.count', "{0} of {1} tables selected", selectedCount, rowCount);
|
||||||
|
}
|
||||||
|
export const DATABASE_MISSING_TABLES = localize('sql.migratino.database.missing.tables', "0 tables found.");
|
||||||
|
export const DATABASE_LOADING_TABLES = localize('sql.migratino.database.loading.tables', "Loading tables list...");
|
||||||
|
export const TABLE_SELECTION_FILTER = localize('sql.migratino.table.selection.filter', "Filter tables");
|
||||||
|
export const TABLE_SELECTION_UPDATE_BUTTON = localize('sql.migratino.table.selection.update.button', "Update");
|
||||||
|
export const TABLE_SELECTION_CANCEL_BUTTON = localize('sql.migratino.table.selection.update.cancel', "Cancel");
|
||||||
|
|
||||||
|
export const TABLE_SELECTION_TABLENAME_COLUMN = localize('sql.migratino.table.selection.tablename.column', "Table name");
|
||||||
|
export const TABLE_SELECTION_HASROWS_COLUMN = localize('sql.migratino.table.selection.status.column', "Has rows");
|
||||||
|
|
||||||
// integration runtime page
|
// integration runtime page
|
||||||
export const SELECT_RESOURCE_GROUP = localize('sql.migration.blob.resourceGroup.select', "Select a resource group.");
|
export const SELECT_RESOURCE_GROUP = localize('sql.migration.blob.resourceGroup.select', "Select a resource group.");
|
||||||
export const IR_PAGE_TITLE = localize('sql.migration.ir.page.title', "Azure Database Migration Service");
|
export const IR_PAGE_TITLE = localize('sql.migration.ir.page.title', "Azure Database Migration Service");
|
||||||
@@ -503,6 +625,7 @@ export const START_MIGRATION_TEXT = localize('sql.migration.start.migration.butt
|
|||||||
export const SUMMARY_PAGE_TITLE = localize('sql.migration.summary.page.title', "Summary");
|
export const SUMMARY_PAGE_TITLE = localize('sql.migration.summary.page.title', "Summary");
|
||||||
export const SUMMARY_MI_TYPE = localize('sql.migration.summary.mi.type', "Azure SQL Managed Instance");
|
export const SUMMARY_MI_TYPE = localize('sql.migration.summary.mi.type', "Azure SQL Managed Instance");
|
||||||
export const SUMMARY_VM_TYPE = localize('sql.migration.summary.vm.type', "SQL Server on Azure Virtual Machine");
|
export const SUMMARY_VM_TYPE = localize('sql.migration.summary.vm.type', "SQL Server on Azure Virtual Machine");
|
||||||
|
export const SUMMARY_SQLDB_TYPE = localize('sql.migration.summary.sqldb.type', "Azure SQL Database");
|
||||||
export const SUMMARY_DATABASE_COUNT_LABEL = localize('sql.migration.summary.database.count', "Databases for migration");
|
export const SUMMARY_DATABASE_COUNT_LABEL = localize('sql.migration.summary.database.count', "Databases for migration");
|
||||||
export const SUMMARY_AZURE_STORAGE_SUBSCRIPTION = localize('sql.migration.summary.azure.storage.subscription', "Azure storage subscription");
|
export const SUMMARY_AZURE_STORAGE_SUBSCRIPTION = localize('sql.migration.summary.azure.storage.subscription', "Azure storage subscription");
|
||||||
export const SUMMARY_AZURE_STORAGE = localize('sql.migration.summary.azure.storage', "Azure storage");
|
export const SUMMARY_AZURE_STORAGE = localize('sql.migration.summary.azure.storage', "Azure storage");
|
||||||
@@ -521,6 +644,9 @@ export const DATABASE_TO_BE_MIGRATED = localize('sql.migration.database.to.be.mi
|
|||||||
export function COUNT_DATABASES(count: number): string {
|
export function COUNT_DATABASES(count: number): string {
|
||||||
return (count === 1) ? localize('sql.migration.count.database.single', "{0} database", count) : localize('sql.migration.count.database.multiple', "{0} databases", count);
|
return (count === 1) ? localize('sql.migration.count.database.single', "{0} database", count) : localize('sql.migration.count.database.multiple', "{0} databases", count);
|
||||||
}
|
}
|
||||||
|
export function TOTAL_TABLES_SELECTED(selected: number, total: number): string {
|
||||||
|
return localize('total.tables.selected.of.total', "{0} of {1}", formatNumber(selected), formatNumber(total));
|
||||||
|
}
|
||||||
|
|
||||||
// Open notebook quick pick string
|
// Open notebook quick pick string
|
||||||
export const NOTEBOOK_QUICK_PICK_PLACEHOLDER = localize('sql.migration.quick.pick.placeholder', "Select the operation you'd like to perform.");
|
export const NOTEBOOK_QUICK_PICK_PLACEHOLDER = localize('sql.migration.quick.pick.placeholder', "Select the operation you'd like to perform.");
|
||||||
@@ -565,6 +691,7 @@ export const SOURCE_DATABASE = localize('sql.migration.source.database', "Source
|
|||||||
export const SOURCE_SERVER = localize('sql.migration.source.server', "Source server");
|
export const SOURCE_SERVER = localize('sql.migration.source.server', "Source server");
|
||||||
export const SOURCE_VERSION = localize('sql.migration.source.version', "Source version");
|
export const SOURCE_VERSION = localize('sql.migration.source.version', "Source version");
|
||||||
export const TARGET_DATABASE_NAME = localize('sql.migration.target.database.name', "Target database name");
|
export const TARGET_DATABASE_NAME = localize('sql.migration.target.database.name', "Target database name");
|
||||||
|
export const TARGET_TABLE_COUNT_NAME = localize('sql.migration.target.table.count.name', "Tables selected");
|
||||||
export const TARGET_SERVER = localize('sql.migration.target.server', "Target server");
|
export const TARGET_SERVER = localize('sql.migration.target.server', "Target server");
|
||||||
export const TARGET_VERSION = localize('sql.migration.target.version', "Target version");
|
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 = localize('sql.migration.migration.status', "Migration status");
|
||||||
@@ -683,12 +810,12 @@ export const OPEN_MIGRATION_TARGET_ERROR = localize('sql.migration.open.migratio
|
|||||||
export const OPEN_MIGRATION_SERVICE_ERROR = localize('sql.migration.open.migration.service.error', "Error opening migration service dialog");
|
export const OPEN_MIGRATION_SERVICE_ERROR = localize('sql.migration.open.migration.service.error', "Error opening migration service dialog");
|
||||||
export const LOAD_MIGRATION_LIST_ERROR = localize('sql.migration.load.migration.list.error', "Error loading migrations list");
|
export const LOAD_MIGRATION_LIST_ERROR = localize('sql.migration.load.migration.list.error', "Error loading migrations list");
|
||||||
export const ERROR_DIALOG_CLEAR_BUTTON_LABEL = localize('sql.migration.error.dialog.clear.button.label', "Clear");
|
export const ERROR_DIALOG_CLEAR_BUTTON_LABEL = localize('sql.migration.error.dialog.clear.button.label', "Clear");
|
||||||
|
export const ERROR_DIALOG_ARIA_CLICK_VIEW_ERROR_DETAILS = localize('sql.migration.error.aria.view.details', 'Click to view error details');
|
||||||
|
|
||||||
export interface LookupTable<T> {
|
export interface LookupTable<T> {
|
||||||
[key: string]: T;
|
[key: string]: T;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export const StatusLookup: LookupTable<string | undefined> = {
|
export const StatusLookup: LookupTable<string | undefined> = {
|
||||||
[MigrationStatus.InProgress]: localize('sql.migration.status.inprogress', 'In progress'),
|
[MigrationStatus.InProgress]: localize('sql.migration.status.inprogress', 'In progress'),
|
||||||
[MigrationStatus.Succeeded]: localize('sql.migration.status.succeeded', 'Succeeded'),
|
[MigrationStatus.Succeeded]: localize('sql.migration.status.succeeded', 'Succeeded'),
|
||||||
@@ -819,7 +946,8 @@ export const WARNINGS_DETAILS = localize('sql.migration.warnings.details', "Warn
|
|||||||
export const ISSUES_DETAILS = localize('sql.migration.issues.details', "Issue details");
|
export const ISSUES_DETAILS = localize('sql.migration.issues.details', "Issue details");
|
||||||
export const SELECT_DB_PROMPT = localize('sql.migration.select.prompt', "Click on SQL Server instance or any of the databases on the left to view its details.");
|
export const SELECT_DB_PROMPT = localize('sql.migration.select.prompt', "Click on SQL Server instance or any of the databases on the left to view its details.");
|
||||||
export const NO_ISSUES_FOUND_VM = localize('sql.migration.no.issues.vm', "No issues found for migrating to SQL Server on Azure Virtual Machine.");
|
export const NO_ISSUES_FOUND_VM = localize('sql.migration.no.issues.vm', "No issues found for migrating to SQL Server on Azure Virtual Machine.");
|
||||||
export const NO_ISSUES_FOUND_MI = localize('sql.migration.no.issues.mi', "No issues found for migrating to SQL Server on Azure SQL Managed Instance.");
|
export const NO_ISSUES_FOUND_MI = localize('sql.migration.no.issues.mi', "No issues found for migrating to Azure SQL Managed Instance.");
|
||||||
|
export const NO_ISSUES_FOUND_SQLDB = localize('sql.migration.no.issues.sqldb', "No issues found for migrating to Azure SQL Database.");
|
||||||
export const NO_RESULTS_AVAILABLE = localize('sql.migration.no.results', 'Assessment results are unavailable.');
|
export const NO_RESULTS_AVAILABLE = localize('sql.migration.no.results', 'Assessment results are unavailable.');
|
||||||
|
|
||||||
export function IMPACT_OBJECT_TYPE(objectType?: string): string {
|
export function IMPACT_OBJECT_TYPE(objectType?: string): string {
|
||||||
|
|||||||
125
extensions/sql-migration/src/dashboard/DashboardStatusBar.ts
Normal file
125
extensions/sql-migration/src/dashboard/DashboardStatusBar.ts
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* 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';
|
||||||
|
|
||||||
|
export interface ErrorEvent {
|
||||||
|
connectionId: string;
|
||||||
|
title: string;
|
||||||
|
label: string;
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DashboardStatusBar implements vscode.Disposable {
|
||||||
|
private _errorTitle: string = '';
|
||||||
|
private _errorLabel: string = '';
|
||||||
|
private _errorDescription: string = '';
|
||||||
|
private _errorDialogIsOpen: boolean = false;
|
||||||
|
private _statusInfoBox: azdata.InfoBoxComponent;
|
||||||
|
private _context: vscode.ExtensionContext;
|
||||||
|
private _errorEvent: vscode.EventEmitter<ErrorEvent> = new vscode.EventEmitter<ErrorEvent>();
|
||||||
|
private _disposables: vscode.Disposable[] = [];
|
||||||
|
|
||||||
|
constructor(context: vscode.ExtensionContext, connectionId: string, statusInfoBox: azdata.InfoBoxComponent, errorEvent: vscode.EventEmitter<ErrorEvent>) {
|
||||||
|
this._context = context;
|
||||||
|
this._statusInfoBox = statusInfoBox;
|
||||||
|
this._errorEvent = errorEvent;
|
||||||
|
|
||||||
|
this._disposables.push(
|
||||||
|
this._errorEvent.event(
|
||||||
|
async (e) => {
|
||||||
|
if (e.connectionId === connectionId) {
|
||||||
|
return (e.title.length > 0 && e.label.length > 0)
|
||||||
|
? await this.showError(e.title, e.label, e.message)
|
||||||
|
: await this.clearError();
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose() {
|
||||||
|
this._disposables.forEach(
|
||||||
|
d => { try { d.dispose(); } catch { } });
|
||||||
|
}
|
||||||
|
|
||||||
|
public async showError(errorTitle: string, errorLabel: string, errorDescription: string): Promise<void> {
|
||||||
|
this._errorTitle = errorTitle;
|
||||||
|
this._errorLabel = errorLabel;
|
||||||
|
this._errorDescription = errorDescription;
|
||||||
|
this._statusInfoBox.style = 'error';
|
||||||
|
this._statusInfoBox.text = errorTitle;
|
||||||
|
this._statusInfoBox.ariaLabel = errorTitle;
|
||||||
|
|
||||||
|
await this._updateStatusDisplay(this._statusInfoBox, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async clearError(): Promise<void> {
|
||||||
|
await this._updateStatusDisplay(this._statusInfoBox, false);
|
||||||
|
this._errorTitle = '';
|
||||||
|
this._errorLabel = '';
|
||||||
|
this._errorDescription = '';
|
||||||
|
this._statusInfoBox.style = 'success';
|
||||||
|
this._statusInfoBox.text = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public async openErrorDialog(): Promise<void> {
|
||||||
|
if (this._errorDialogIsOpen) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const tab = azdata.window.createTab(this._errorTitle);
|
||||||
|
tab.registerContent(async (view) => {
|
||||||
|
const flex = view.modelBuilder.flexContainer()
|
||||||
|
.withItems([
|
||||||
|
view.modelBuilder.text()
|
||||||
|
.withProps({ value: this._errorLabel, CSSStyles: { 'margin': '0px 0px 5px 5px' } })
|
||||||
|
.component(),
|
||||||
|
view.modelBuilder.inputBox()
|
||||||
|
.withProps({
|
||||||
|
value: this._errorDescription,
|
||||||
|
readOnly: true,
|
||||||
|
multiline: true,
|
||||||
|
height: 400,
|
||||||
|
inputType: 'text',
|
||||||
|
display: 'inline-block',
|
||||||
|
CSSStyles: { 'overflow': 'hidden auto', 'margin': '0px 0px 0px 5px' },
|
||||||
|
})
|
||||||
|
.component()])
|
||||||
|
.withLayout({ flexFlow: 'column', width: 420, })
|
||||||
|
.withProps({ CSSStyles: { 'margin': '0 10px 0 10px' } })
|
||||||
|
.component();
|
||||||
|
|
||||||
|
await view.initializeModel(flex);
|
||||||
|
});
|
||||||
|
|
||||||
|
const dialog = azdata.window.createModelViewDialog(
|
||||||
|
this._errorTitle,
|
||||||
|
'errorDialog',
|
||||||
|
450,
|
||||||
|
'flyout');
|
||||||
|
dialog.content = [tab];
|
||||||
|
dialog.okButton.label = loc.ERROR_DIALOG_CLEAR_BUTTON_LABEL;
|
||||||
|
dialog.okButton.focused = true;
|
||||||
|
dialog.cancelButton.label = loc.CLOSE;
|
||||||
|
this._context.subscriptions.push(
|
||||||
|
dialog.onClosed(async e => {
|
||||||
|
if (e === 'ok') {
|
||||||
|
await this.clearError();
|
||||||
|
}
|
||||||
|
this._errorDialogIsOpen = false;
|
||||||
|
}));
|
||||||
|
|
||||||
|
azdata.window.openDialog(dialog);
|
||||||
|
} catch (error) {
|
||||||
|
this._errorDialogIsOpen = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _updateStatusDisplay(control: azdata.Component, visible: boolean): Promise<void> {
|
||||||
|
await control.updateCssStyles({ 'display': visible ? 'inline' : 'none' });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,13 +8,13 @@ import * as vscode from 'vscode';
|
|||||||
import { IconPath, IconPathHelper } from '../constants/iconPathHelper';
|
import { IconPath, IconPathHelper } from '../constants/iconPathHelper';
|
||||||
import * as styles from '../constants/styles';
|
import * as styles from '../constants/styles';
|
||||||
import * as loc from '../constants/strings';
|
import * as loc from '../constants/strings';
|
||||||
import { filterMigrations } from '../api/utils';
|
import { filterMigrations, MenuCommands } from '../api/utils';
|
||||||
import { DatabaseMigration } from '../api/azure';
|
import { DatabaseMigration } from '../api/azure';
|
||||||
import { getCurrentMigrations, getSelectedServiceStatus, isServiceContextValid, MigrationLocalStorage } from '../models/migrationLocalStorage';
|
import { getCurrentMigrations, getSelectedServiceStatus, isServiceContextValid, MigrationLocalStorage } from '../models/migrationLocalStorage';
|
||||||
import { SelectMigrationServiceDialog } from '../dialog/selectMigrationService/selectMigrationServiceDialog';
|
import { SelectMigrationServiceDialog } from '../dialog/selectMigrationService/selectMigrationServiceDialog';
|
||||||
import { logError, TelemetryViews } from '../telemtery';
|
import { logError, TelemetryViews } from '../telemtery';
|
||||||
import { AdsMigrationStatus, MenuCommands, TabBase } from './tabBase';
|
import { AdsMigrationStatus, ServiceContextChangeEvent, TabBase } from './tabBase';
|
||||||
import { DashboardStatusBar } from './sqlServerDashboard';
|
import { DashboardStatusBar } from './DashboardStatusBar';
|
||||||
|
|
||||||
interface IActionMetadata {
|
interface IActionMetadata {
|
||||||
title?: string,
|
title?: string,
|
||||||
@@ -62,16 +62,15 @@ export class DashboardTab extends TabBase<DashboardTab> {
|
|||||||
this.icon = IconPathHelper.sqlMigrationLogo;
|
this.icon = IconPathHelper.sqlMigrationLogo;
|
||||||
}
|
}
|
||||||
|
|
||||||
public onDialogClosed = async (): Promise<void> =>
|
|
||||||
await this.updateServiceContext(this._serviceContextButton);
|
|
||||||
|
|
||||||
public async create(
|
public async create(
|
||||||
view: azdata.ModelView,
|
view: azdata.ModelView,
|
||||||
openMigrationsFcn: (status: AdsMigrationStatus) => Promise<void>,
|
openMigrationsFcn: (status: AdsMigrationStatus) => Promise<void>,
|
||||||
|
serviceContextChangedEvent: vscode.EventEmitter<ServiceContextChangeEvent>,
|
||||||
statusBar: DashboardStatusBar): Promise<DashboardTab> {
|
statusBar: DashboardStatusBar): Promise<DashboardTab> {
|
||||||
|
|
||||||
this.view = view;
|
this.view = view;
|
||||||
this.openMigrationFcn = openMigrationsFcn;
|
this.openMigrationsFcn = openMigrationsFcn;
|
||||||
|
this.serviceContextChangedEvent = serviceContextChangedEvent;
|
||||||
this.statusBar = statusBar;
|
this.statusBar = statusBar;
|
||||||
|
|
||||||
await this.initialize(this.view);
|
await this.initialize(this.view);
|
||||||
@@ -80,23 +79,16 @@ export class DashboardTab extends TabBase<DashboardTab> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async refresh(): Promise<void> {
|
public async refresh(): Promise<void> {
|
||||||
if (this.isRefreshing) {
|
if (this.isRefreshing || this._migrationStatusCardLoadingContainer === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.isRefreshing = true;
|
|
||||||
this._migrationStatusCardLoadingContainer.loading = true;
|
|
||||||
let migrations: DatabaseMigration[] = [];
|
|
||||||
try {
|
try {
|
||||||
|
this.isRefreshing = true;
|
||||||
|
this._refreshButton.enabled = false;
|
||||||
|
this._migrationStatusCardLoadingContainer.loading = true;
|
||||||
await this.statusBar.clearError();
|
await this.statusBar.clearError();
|
||||||
migrations = await getCurrentMigrations();
|
const migrations = await getCurrentMigrations();
|
||||||
} catch (e) {
|
|
||||||
await this.statusBar.showError(
|
|
||||||
loc.DASHBOARD_REFRESH_MIGRATIONS_TITLE,
|
|
||||||
loc.DASHBOARD_REFRESH_MIGRATIONS_LABEL,
|
|
||||||
e.message);
|
|
||||||
logError(TelemetryViews.SqlServerDashboard, 'RefreshgMigrationFailed', e);
|
|
||||||
}
|
|
||||||
|
|
||||||
const inProgressMigrations = filterMigrations(migrations, AdsMigrationStatus.ONGOING);
|
const inProgressMigrations = filterMigrations(migrations, AdsMigrationStatus.ONGOING);
|
||||||
let warningCount = 0;
|
let warningCount = 0;
|
||||||
@@ -125,8 +117,17 @@ export class DashboardTab extends TabBase<DashboardTab> {
|
|||||||
this._updateStatusCard(migrations, this._allMigrationButton, AdsMigrationStatus.ALL, true);
|
this._updateStatusCard(migrations, this._allMigrationButton, AdsMigrationStatus.ALL, true);
|
||||||
|
|
||||||
await this._updateSummaryStatus();
|
await this._updateSummaryStatus();
|
||||||
this.isRefreshing = false;
|
} catch (e) {
|
||||||
|
await this.statusBar.showError(
|
||||||
|
loc.DASHBOARD_REFRESH_MIGRATIONS_TITLE,
|
||||||
|
loc.DASHBOARD_REFRESH_MIGRATIONS_LABEL,
|
||||||
|
e.message);
|
||||||
|
logError(TelemetryViews.SqlServerDashboard, 'RefreshgMigrationFailed', e);
|
||||||
|
} finally {
|
||||||
this._migrationStatusCardLoadingContainer.loading = false;
|
this._migrationStatusCardLoadingContainer.loading = false;
|
||||||
|
this._refreshButton.enabled = true;
|
||||||
|
this.isRefreshing = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async initialize(view: azdata.ModelView): Promise<void> {
|
protected async initialize(view: azdata.ModelView): Promise<void> {
|
||||||
@@ -616,11 +617,8 @@ export class DashboardTab extends TabBase<DashboardTab> {
|
|||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
this.disposables.push(
|
this.disposables.push(
|
||||||
this._refreshButton.onDidClick(async (e) => {
|
this._refreshButton.onDidClick(
|
||||||
this._refreshButton.enabled = false;
|
async (e) => await this.refresh()));
|
||||||
await this.refresh();
|
|
||||||
this._refreshButton.enabled = true;
|
|
||||||
}));
|
|
||||||
|
|
||||||
const buttonContainer = view.modelBuilder.flexContainer()
|
const buttonContainer = view.modelBuilder.flexContainer()
|
||||||
.withProps({
|
.withProps({
|
||||||
@@ -668,7 +666,7 @@ export class DashboardTab extends TabBase<DashboardTab> {
|
|||||||
loc.MIGRATION_IN_PROGRESS);
|
loc.MIGRATION_IN_PROGRESS);
|
||||||
this.disposables.push(
|
this.disposables.push(
|
||||||
this._inProgressMigrationButton.container.onDidClick(
|
this._inProgressMigrationButton.container.onDidClick(
|
||||||
async (e) => await this.openMigrationFcn(AdsMigrationStatus.ONGOING)));
|
async (e) => await this.openMigrationsFcn(AdsMigrationStatus.ONGOING)));
|
||||||
this._migrationStatusCardsContainer.addItem(
|
this._migrationStatusCardsContainer.addItem(
|
||||||
this._inProgressMigrationButton.container,
|
this._inProgressMigrationButton.container,
|
||||||
{ flex: '0 0 auto' });
|
{ flex: '0 0 auto' });
|
||||||
@@ -681,7 +679,7 @@ export class DashboardTab extends TabBase<DashboardTab> {
|
|||||||
true);
|
true);
|
||||||
this.disposables.push(
|
this.disposables.push(
|
||||||
this._inProgressWarningMigrationButton.container.onDidClick(
|
this._inProgressWarningMigrationButton.container.onDidClick(
|
||||||
async (e) => await this.openMigrationFcn(AdsMigrationStatus.ONGOING)));
|
async (e) => await this.openMigrationsFcn(AdsMigrationStatus.ONGOING)));
|
||||||
this._migrationStatusCardsContainer.addItem(
|
this._migrationStatusCardsContainer.addItem(
|
||||||
this._inProgressWarningMigrationButton.container,
|
this._inProgressWarningMigrationButton.container,
|
||||||
{ flex: '0 0 auto' });
|
{ flex: '0 0 auto' });
|
||||||
@@ -693,7 +691,7 @@ export class DashboardTab extends TabBase<DashboardTab> {
|
|||||||
loc.MIGRATION_COMPLETED);
|
loc.MIGRATION_COMPLETED);
|
||||||
this.disposables.push(
|
this.disposables.push(
|
||||||
this._successfulMigrationButton.container.onDidClick(
|
this._successfulMigrationButton.container.onDidClick(
|
||||||
async (e) => await this.openMigrationFcn(AdsMigrationStatus.SUCCEEDED)));
|
async (e) => await this.openMigrationsFcn(AdsMigrationStatus.SUCCEEDED)));
|
||||||
this._migrationStatusCardsContainer.addItem(
|
this._migrationStatusCardsContainer.addItem(
|
||||||
this._successfulMigrationButton.container,
|
this._successfulMigrationButton.container,
|
||||||
{ flex: '0 0 auto' });
|
{ flex: '0 0 auto' });
|
||||||
@@ -705,7 +703,7 @@ export class DashboardTab extends TabBase<DashboardTab> {
|
|||||||
loc.MIGRATION_CUTOVER_CARD);
|
loc.MIGRATION_CUTOVER_CARD);
|
||||||
this.disposables.push(
|
this.disposables.push(
|
||||||
this._completingMigrationButton.container.onDidClick(
|
this._completingMigrationButton.container.onDidClick(
|
||||||
async (e) => await this.openMigrationFcn(AdsMigrationStatus.COMPLETING)));
|
async (e) => await this.openMigrationsFcn(AdsMigrationStatus.COMPLETING)));
|
||||||
this._migrationStatusCardsContainer.addItem(
|
this._migrationStatusCardsContainer.addItem(
|
||||||
this._completingMigrationButton.container,
|
this._completingMigrationButton.container,
|
||||||
{ flex: '0 0 auto' });
|
{ flex: '0 0 auto' });
|
||||||
@@ -717,7 +715,7 @@ export class DashboardTab extends TabBase<DashboardTab> {
|
|||||||
loc.MIGRATION_FAILED);
|
loc.MIGRATION_FAILED);
|
||||||
this.disposables.push(
|
this.disposables.push(
|
||||||
this._failedMigrationButton.container.onDidClick(
|
this._failedMigrationButton.container.onDidClick(
|
||||||
async (e) => await this.openMigrationFcn(AdsMigrationStatus.FAILED)));
|
async (e) => await this.openMigrationsFcn(AdsMigrationStatus.FAILED)));
|
||||||
this._migrationStatusCardsContainer.addItem(
|
this._migrationStatusCardsContainer.addItem(
|
||||||
this._failedMigrationButton.container,
|
this._failedMigrationButton.container,
|
||||||
{ flex: '0 0 auto' });
|
{ flex: '0 0 auto' });
|
||||||
@@ -729,7 +727,7 @@ export class DashboardTab extends TabBase<DashboardTab> {
|
|||||||
loc.VIEW_ALL);
|
loc.VIEW_ALL);
|
||||||
this.disposables.push(
|
this.disposables.push(
|
||||||
this._allMigrationButton.container.onDidClick(
|
this._allMigrationButton.container.onDidClick(
|
||||||
async (e) => await this.openMigrationFcn(AdsMigrationStatus.ALL)));
|
async (e) => await this.openMigrationsFcn(AdsMigrationStatus.ALL)));
|
||||||
this._migrationStatusCardsContainer.addItem(
|
this._migrationStatusCardsContainer.addItem(
|
||||||
this._allMigrationButton.container,
|
this._allMigrationButton.container,
|
||||||
{ flex: '0 0 auto' });
|
{ flex: '0 0 auto' });
|
||||||
@@ -759,9 +757,21 @@ export class DashboardTab extends TabBase<DashboardTab> {
|
|||||||
})
|
})
|
||||||
.component();
|
.component();
|
||||||
|
|
||||||
|
const connectionProfile = await azdata.connection.getCurrentConnection();
|
||||||
|
this.disposables.push(
|
||||||
|
this.serviceContextChangedEvent.event(
|
||||||
|
async (e) => {
|
||||||
|
if (e.connectionId === connectionProfile.connectionId) {
|
||||||
|
await this.updateServiceContext(this._serviceContextButton);
|
||||||
|
await this.refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
));
|
||||||
|
await this.updateServiceContext(this._serviceContextButton);
|
||||||
|
|
||||||
this.disposables.push(
|
this.disposables.push(
|
||||||
this._serviceContextButton.onDidClick(async () => {
|
this._serviceContextButton.onDidClick(async () => {
|
||||||
const dialog = new SelectMigrationServiceDialog(async () => await this.onDialogClosed());
|
const dialog = new SelectMigrationServiceDialog(this.serviceContextChangedEvent);
|
||||||
await dialog.initialize();
|
await dialog.initialize();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ import * as vscode from 'vscode';
|
|||||||
import * as loc from '../constants/strings';
|
import * as loc from '../constants/strings';
|
||||||
import { getSqlServerName, getMigrationStatusImage } from '../api/utils';
|
import { getSqlServerName, getMigrationStatusImage } from '../api/utils';
|
||||||
import { logError, TelemetryViews } from '../telemtery';
|
import { logError, TelemetryViews } from '../telemtery';
|
||||||
import { canCancelMigration, canCutoverMigration, canRetryMigration, getMigrationStatus, getMigrationTargetTypeEnum, isOfflineMigation } from '../constants/helper';
|
import { canCancelMigration, canCutoverMigration, canRetryMigration, getMigrationStatusString, getMigrationTargetTypeEnum, isOfflineMigation } from '../constants/helper';
|
||||||
import { getResourceName } from '../api/azure';
|
import { getResourceName } from '../api/azure';
|
||||||
import { InfoFieldSchema, infoFieldWidth, MigrationDetailsTabBase, MigrationTargetTypeName } from './migrationDetailsTabBase';
|
import { InfoFieldSchema, infoFieldWidth, MigrationDetailsTabBase, MigrationTargetTypeName } from './migrationDetailsTabBase';
|
||||||
import { EmptySettingValue } from './tabBase';
|
import { EmptySettingValue } from './tabBase';
|
||||||
import { DashboardStatusBar } from './sqlServerDashboard';
|
import { DashboardStatusBar } from './DashboardStatusBar';
|
||||||
|
|
||||||
const MigrationDetailsBlobContainerTabId = 'MigrationDetailsBlobContainerTab';
|
const MigrationDetailsBlobContainerTabId = 'MigrationDetailsBlobContainerTab';
|
||||||
|
|
||||||
@@ -36,13 +36,13 @@ export class MigrationDetailsBlobContainerTab extends MigrationDetailsTabBase<Mi
|
|||||||
public async create(
|
public async create(
|
||||||
context: vscode.ExtensionContext,
|
context: vscode.ExtensionContext,
|
||||||
view: azdata.ModelView,
|
view: azdata.ModelView,
|
||||||
onClosedCallback: () => Promise<void>,
|
openMigrationsListFcn: () => Promise<void>,
|
||||||
statusBar: DashboardStatusBar,
|
statusBar: DashboardStatusBar,
|
||||||
): Promise<MigrationDetailsBlobContainerTab> {
|
): Promise<MigrationDetailsBlobContainerTab> {
|
||||||
|
|
||||||
this.view = view;
|
this.view = view;
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.onClosedCallback = onClosedCallback;
|
this.openMigrationsListFcn = openMigrationsListFcn;
|
||||||
this.statusBar = statusBar;
|
this.statusBar = statusBar;
|
||||||
|
|
||||||
await this.initialize(this.view);
|
await this.initialize(this.view);
|
||||||
@@ -51,12 +51,14 @@ export class MigrationDetailsBlobContainerTab extends MigrationDetailsTabBase<Mi
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async refresh(): Promise<void> {
|
public async refresh(): Promise<void> {
|
||||||
if (this.isRefreshing || this.model?.migration === undefined) {
|
if (this.isRefreshing ||
|
||||||
|
this.refreshLoader === undefined ||
|
||||||
|
this.model?.migration === undefined) {
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.isRefreshing = true;
|
this.isRefreshing = true;
|
||||||
this.refreshButton.enabled = false;
|
|
||||||
this.refreshLoader.loading = true;
|
this.refreshLoader.loading = true;
|
||||||
await this.statusBar.clearError();
|
await this.statusBar.clearError();
|
||||||
|
|
||||||
@@ -95,7 +97,7 @@ export class MigrationDetailsBlobContainerTab extends MigrationDetailsTabBase<Mi
|
|||||||
this._targetServerInfoField.text.value = targetServerName;
|
this._targetServerInfoField.text.value = targetServerName;
|
||||||
this._targetVersionInfoField.text.value = targetServerVersion;
|
this._targetVersionInfoField.text.value = targetServerVersion;
|
||||||
|
|
||||||
this._migrationStatusInfoField.text.value = getMigrationStatus(migration) ?? EmptySettingValue;
|
this._migrationStatusInfoField.text.value = getMigrationStatusString(migration);
|
||||||
this._migrationStatusInfoField.icon!.iconPath = getMigrationStatusImage(migration);
|
this._migrationStatusInfoField.icon!.iconPath = getMigrationStatusImage(migration);
|
||||||
|
|
||||||
const storageAccountResourceId = migration.properties.backupConfiguration?.sourceLocation?.azureBlob?.storageAccountResourceId;
|
const storageAccountResourceId = migration.properties.backupConfiguration?.sourceLocation?.azureBlob?.storageAccountResourceId;
|
||||||
@@ -114,9 +116,8 @@ export class MigrationDetailsBlobContainerTab extends MigrationDetailsTabBase<Mi
|
|||||||
this.cancelButton.enabled = canCancelMigration(migration);
|
this.cancelButton.enabled = canCancelMigration(migration);
|
||||||
this.retryButton.enabled = canRetryMigration(migration);
|
this.retryButton.enabled = canRetryMigration(migration);
|
||||||
|
|
||||||
this.isRefreshing = false;
|
|
||||||
this.refreshLoader.loading = false;
|
this.refreshLoader.loading = false;
|
||||||
this.refreshButton.enabled = true;
|
this.isRefreshing = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async initialize(view: azdata.ModelView): Promise<void> {
|
protected async initialize(view: azdata.ModelView): Promise<void> {
|
||||||
|
|||||||
@@ -10,11 +10,11 @@ import * as loc from '../constants/strings';
|
|||||||
import { convertByteSizeToReadableUnit, convertIsoTimeToLocalTime, getSqlServerName, getMigrationStatusImage } from '../api/utils';
|
import { convertByteSizeToReadableUnit, convertIsoTimeToLocalTime, getSqlServerName, getMigrationStatusImage } from '../api/utils';
|
||||||
import { logError, TelemetryViews } from '../telemtery';
|
import { logError, TelemetryViews } from '../telemtery';
|
||||||
import * as styles from '../constants/styles';
|
import * as styles from '../constants/styles';
|
||||||
import { canCancelMigration, canCutoverMigration, canRetryMigration, getMigrationStatus, getMigrationTargetTypeEnum, isOfflineMigation } from '../constants/helper';
|
import { canCancelMigration, canCutoverMigration, canRetryMigration, getMigrationStatusString, getMigrationTargetTypeEnum, isOfflineMigation } from '../constants/helper';
|
||||||
import { getResourceName } from '../api/azure';
|
import { getResourceName } from '../api/azure';
|
||||||
import { EmptySettingValue } from './tabBase';
|
import { EmptySettingValue } from './tabBase';
|
||||||
import { InfoFieldSchema, infoFieldWidth, MigrationDetailsTabBase, MigrationTargetTypeName } from './migrationDetailsTabBase';
|
import { InfoFieldSchema, infoFieldWidth, MigrationDetailsTabBase, MigrationTargetTypeName } from './migrationDetailsTabBase';
|
||||||
import { DashboardStatusBar } from './sqlServerDashboard';
|
import { DashboardStatusBar } from './DashboardStatusBar';
|
||||||
|
|
||||||
const MigrationDetailsFileShareTabId = 'MigrationDetailsFileShareTab';
|
const MigrationDetailsFileShareTabId = 'MigrationDetailsFileShareTab';
|
||||||
|
|
||||||
@@ -43,7 +43,6 @@ export class MigrationDetailsFileShareTab extends MigrationDetailsTabBase<Migrat
|
|||||||
private _lastAppliedBackupInfoField!: InfoFieldSchema;
|
private _lastAppliedBackupInfoField!: InfoFieldSchema;
|
||||||
private _lastAppliedBackupTakenOnInfoField!: InfoFieldSchema;
|
private _lastAppliedBackupTakenOnInfoField!: InfoFieldSchema;
|
||||||
private _currentRestoringFileInfoField!: InfoFieldSchema;
|
private _currentRestoringFileInfoField!: InfoFieldSchema;
|
||||||
|
|
||||||
private _fileCount!: azdata.TextComponent;
|
private _fileCount!: azdata.TextComponent;
|
||||||
private _fileTable!: azdata.TableComponent;
|
private _fileTable!: azdata.TableComponent;
|
||||||
private _emptyTableFill!: azdata.FlexContainer;
|
private _emptyTableFill!: azdata.FlexContainer;
|
||||||
@@ -56,12 +55,12 @@ export class MigrationDetailsFileShareTab extends MigrationDetailsTabBase<Migrat
|
|||||||
public async create(
|
public async create(
|
||||||
context: vscode.ExtensionContext,
|
context: vscode.ExtensionContext,
|
||||||
view: azdata.ModelView,
|
view: azdata.ModelView,
|
||||||
onClosedCallback: () => Promise<void>,
|
openMigrationsListFcn: () => Promise<void>,
|
||||||
statusBar: DashboardStatusBar): Promise<MigrationDetailsFileShareTab> {
|
statusBar: DashboardStatusBar): Promise<MigrationDetailsFileShareTab> {
|
||||||
|
|
||||||
this.view = view;
|
this.view = view;
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.onClosedCallback = onClosedCallback;
|
this.openMigrationsListFcn = openMigrationsListFcn;
|
||||||
this.statusBar = statusBar;
|
this.statusBar = statusBar;
|
||||||
|
|
||||||
await this.initialize(this.view);
|
await this.initialize(this.view);
|
||||||
@@ -70,24 +69,20 @@ export class MigrationDetailsFileShareTab extends MigrationDetailsTabBase<Migrat
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async refresh(): Promise<void> {
|
public async refresh(): Promise<void> {
|
||||||
if (this.isRefreshing || this.model?.migration === undefined) {
|
if (this.isRefreshing ||
|
||||||
|
this.refreshLoader === undefined ||
|
||||||
|
this.model?.migration === undefined) {
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
this.isRefreshing = true;
|
this.isRefreshing = true;
|
||||||
this.refreshButton.enabled = false;
|
|
||||||
this.refreshLoader.loading = true;
|
this.refreshLoader.loading = true;
|
||||||
await this.statusBar.clearError();
|
await this.statusBar.clearError();
|
||||||
await this._fileTable.updateProperty('data', []);
|
await this._fileTable.updateProperty('data', []);
|
||||||
|
|
||||||
try {
|
|
||||||
await this.model.fetchStatus();
|
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;
|
const migration = this.model?.migration;
|
||||||
await this.cutoverButton.updateCssStyles(
|
await this.cutoverButton.updateCssStyles(
|
||||||
@@ -142,7 +137,7 @@ export class MigrationDetailsFileShareTab extends MigrationDetailsTabBase<Migrat
|
|||||||
this._targetServerInfoField.text.value = targetServerName;
|
this._targetServerInfoField.text.value = targetServerName;
|
||||||
this._targetVersionInfoField.text.value = targetServerVersion;
|
this._targetVersionInfoField.text.value = targetServerVersion;
|
||||||
|
|
||||||
this._migrationStatusInfoField.text.value = getMigrationStatus(migration) ?? EmptySettingValue;
|
this._migrationStatusInfoField.text.value = getMigrationStatusString(migration);
|
||||||
this._migrationStatusInfoField.icon!.iconPath = getMigrationStatusImage(migration);
|
this._migrationStatusInfoField.icon!.iconPath = getMigrationStatusImage(migration);
|
||||||
|
|
||||||
this._fullBackupFileOnInfoField.text.value = migration?.properties?.migrationStatusDetails?.fullBackupSetInfo?.listOfBackupFiles[0]?.fileName! ?? EmptySettingValue;
|
this._fullBackupFileOnInfoField.text.value = migration?.properties?.migrationStatusDetails?.fullBackupSetInfo?.listOfBackupFiles[0]?.fileName! ?? EmptySettingValue;
|
||||||
@@ -187,9 +182,15 @@ export class MigrationDetailsFileShareTab extends MigrationDetailsTabBase<Migrat
|
|||||||
this.cutoverButton.enabled = canCutoverMigration(migration);
|
this.cutoverButton.enabled = canCutoverMigration(migration);
|
||||||
this.cancelButton.enabled = canCancelMigration(migration);
|
this.cancelButton.enabled = canCancelMigration(migration);
|
||||||
this.retryButton.enabled = canRetryMigration(migration);
|
this.retryButton.enabled = canRetryMigration(migration);
|
||||||
this.isRefreshing = false;
|
} catch (e) {
|
||||||
|
await this.statusBar.showError(
|
||||||
|
loc.MIGRATION_STATUS_REFRESH_ERROR,
|
||||||
|
loc.MIGRATION_STATUS_REFRESH_ERROR,
|
||||||
|
e.message);
|
||||||
|
} finally {
|
||||||
this.refreshLoader.loading = false;
|
this.refreshLoader.loading = false;
|
||||||
this.refreshButton.enabled = true;
|
this.isRefreshing = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async initialize(view: azdata.ModelView): Promise<void> {
|
protected async initialize(view: azdata.ModelView): Promise<void> {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import { MigrationCutoverDialogModel } from '../dialog/migrationCutover/migratio
|
|||||||
import { ConfirmCutoverDialog } from '../dialog/migrationCutover/confirmCutoverDialog';
|
import { ConfirmCutoverDialog } from '../dialog/migrationCutover/confirmCutoverDialog';
|
||||||
import { RetryMigrationDialog } from '../dialog/retryMigration/retryMigrationDialog';
|
import { RetryMigrationDialog } from '../dialog/retryMigration/retryMigrationDialog';
|
||||||
import { MigrationTargetType } from '../models/stateMachine';
|
import { MigrationTargetType } from '../models/stateMachine';
|
||||||
import { DashboardStatusBar } from './sqlServerDashboard';
|
import { DashboardStatusBar } from './DashboardStatusBar';
|
||||||
|
|
||||||
export const infoFieldLgWidth: string = '330px';
|
export const infoFieldLgWidth: string = '330px';
|
||||||
export const infoFieldWidth: string = '250px';
|
export const infoFieldWidth: string = '250px';
|
||||||
@@ -38,8 +38,7 @@ export abstract class MigrationDetailsTabBase<T> extends TabBase<T> {
|
|||||||
protected model!: MigrationCutoverDialogModel;
|
protected model!: MigrationCutoverDialogModel;
|
||||||
protected databaseLabel!: azdata.TextComponent;
|
protected databaseLabel!: azdata.TextComponent;
|
||||||
protected serviceContext!: MigrationServiceContext;
|
protected serviceContext!: MigrationServiceContext;
|
||||||
protected onClosedCallback!: () => Promise<void>;
|
protected openMigrationsListFcn!: () => Promise<void>;
|
||||||
|
|
||||||
protected cutoverButton!: azdata.ButtonComponent;
|
protected cutoverButton!: azdata.ButtonComponent;
|
||||||
protected refreshButton!: azdata.ButtonComponent;
|
protected refreshButton!: azdata.ButtonComponent;
|
||||||
protected cancelButton!: azdata.ButtonComponent;
|
protected cancelButton!: azdata.ButtonComponent;
|
||||||
@@ -49,7 +48,11 @@ export abstract class MigrationDetailsTabBase<T> extends TabBase<T> {
|
|||||||
protected retryButton!: azdata.ButtonComponent;
|
protected retryButton!: azdata.ButtonComponent;
|
||||||
protected summaryTextComponent: azdata.TextComponent[] = [];
|
protected summaryTextComponent: azdata.TextComponent[] = [];
|
||||||
|
|
||||||
public abstract create(context: vscode.ExtensionContext, view: azdata.ModelView, onClosedCallback: () => Promise<void>, statusBar: DashboardStatusBar): Promise<T>;
|
public abstract create(
|
||||||
|
context: vscode.ExtensionContext,
|
||||||
|
view: azdata.ModelView,
|
||||||
|
openMigrationsListFcn: () => Promise<void>,
|
||||||
|
statusBar: DashboardStatusBar): Promise<T>;
|
||||||
|
|
||||||
protected abstract migrationInfoGrid(): Promise<azdata.FlexContainer>;
|
protected abstract migrationInfoGrid(): Promise<azdata.FlexContainer>;
|
||||||
|
|
||||||
@@ -80,7 +83,7 @@ export abstract class MigrationDetailsTabBase<T> extends TabBase<T> {
|
|||||||
.component();
|
.component();
|
||||||
this.disposables.push(
|
this.disposables.push(
|
||||||
migrationsTabLink.onDidClick(
|
migrationsTabLink.onDidClick(
|
||||||
async (e) => await this.onClosedCallback()));
|
async (e) => await this.openMigrationsListFcn()));
|
||||||
|
|
||||||
const breadCrumbImage = this.view.modelBuilder.image()
|
const breadCrumbImage = this.view.modelBuilder.image()
|
||||||
.withProps({
|
.withProps({
|
||||||
@@ -202,7 +205,7 @@ export abstract class MigrationDetailsTabBase<T> extends TabBase<T> {
|
|||||||
this.context,
|
this.context,
|
||||||
this.serviceContext,
|
this.serviceContext,
|
||||||
this.model.migration,
|
this.model.migration,
|
||||||
this.onClosedCallback);
|
this.serviceContextChangedEvent);
|
||||||
await retryMigrationDialog.openDialog();
|
await retryMigrationDialog.openDialog();
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
@@ -254,12 +257,10 @@ export abstract class MigrationDetailsTabBase<T> extends TabBase<T> {
|
|||||||
async (e) => await this.refresh()));
|
async (e) => await this.refresh()));
|
||||||
|
|
||||||
this.refreshLoader = this.view.modelBuilder.loadingComponent()
|
this.refreshLoader = this.view.modelBuilder.loadingComponent()
|
||||||
|
.withItem(this.refreshButton)
|
||||||
.withProps({
|
.withProps({
|
||||||
loading: false,
|
loading: false,
|
||||||
CSSStyles: {
|
CSSStyles: { 'height': '8px', 'margin-top': '4px' }
|
||||||
'height': '8px',
|
|
||||||
'margin-top': '4px'
|
|
||||||
}
|
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
toolbarContainer.addToolbarItems([
|
toolbarContainer.addToolbarItems([
|
||||||
@@ -268,7 +269,6 @@ export abstract class MigrationDetailsTabBase<T> extends TabBase<T> {
|
|||||||
<azdata.ToolbarComponent>{ component: this.retryButton },
|
<azdata.ToolbarComponent>{ component: this.retryButton },
|
||||||
<azdata.ToolbarComponent>{ component: this.copyDatabaseMigrationDetails, toolbarSeparatorAfter: true },
|
<azdata.ToolbarComponent>{ component: this.copyDatabaseMigrationDetails, toolbarSeparatorAfter: true },
|
||||||
<azdata.ToolbarComponent>{ component: this.newSupportRequest, toolbarSeparatorAfter: true },
|
<azdata.ToolbarComponent>{ component: this.newSupportRequest, toolbarSeparatorAfter: true },
|
||||||
<azdata.ToolbarComponent>{ component: this.refreshButton },
|
|
||||||
<azdata.ToolbarComponent>{ component: this.refreshLoader },
|
<azdata.ToolbarComponent>{ component: this.refreshLoader },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -8,13 +8,12 @@ import * as vscode from 'vscode';
|
|||||||
import * as loc from '../constants/strings';
|
import * as loc from '../constants/strings';
|
||||||
import { getSqlServerName, getMigrationStatusImage, getPipelineStatusImage, debounce } from '../api/utils';
|
import { getSqlServerName, getMigrationStatusImage, getPipelineStatusImage, debounce } from '../api/utils';
|
||||||
import { logError, TelemetryViews } from '../telemtery';
|
import { logError, TelemetryViews } from '../telemtery';
|
||||||
import { canCancelMigration, canCutoverMigration, canRetryMigration, formatDateTimeString, formatNumber, formatSizeBytes, formatSizeKb, formatTime, getMigrationStatus, getMigrationTargetTypeEnum, isOfflineMigation, PipelineStatusCodes } from '../constants/helper';
|
import { canCancelMigration, canCutoverMigration, canRetryMigration, formatDateTimeString, formatNumber, formatSizeBytes, formatSizeKb, formatTime, getMigrationStatusString, getMigrationTargetTypeEnum, isOfflineMigation, PipelineStatusCodes } from '../constants/helper';
|
||||||
import { CopyProgressDetail, getResourceName } from '../api/azure';
|
import { CopyProgressDetail, getResourceName } from '../api/azure';
|
||||||
import { InfoFieldSchema, infoFieldLgWidth, MigrationDetailsTabBase, MigrationTargetTypeName } from './migrationDetailsTabBase';
|
import { InfoFieldSchema, infoFieldLgWidth, MigrationDetailsTabBase, MigrationTargetTypeName } from './migrationDetailsTabBase';
|
||||||
import { EmptySettingValue } from './tabBase';
|
|
||||||
import { IconPathHelper } from '../constants/iconPathHelper';
|
import { IconPathHelper } from '../constants/iconPathHelper';
|
||||||
import { DashboardStatusBar } from './sqlServerDashboard';
|
|
||||||
import { EOL } from 'os';
|
import { EOL } from 'os';
|
||||||
|
import { DashboardStatusBar } from './DashboardStatusBar';
|
||||||
|
|
||||||
const MigrationDetailsTableTabId = 'MigrationDetailsTableTab';
|
const MigrationDetailsTableTabId = 'MigrationDetailsTableTab';
|
||||||
|
|
||||||
@@ -63,12 +62,12 @@ export class MigrationDetailsTableTab extends MigrationDetailsTabBase<MigrationD
|
|||||||
public async create(
|
public async create(
|
||||||
context: vscode.ExtensionContext,
|
context: vscode.ExtensionContext,
|
||||||
view: azdata.ModelView,
|
view: azdata.ModelView,
|
||||||
onClosedCallback: () => Promise<void>,
|
openMigrationsListFcn: () => Promise<void>,
|
||||||
statusBar: DashboardStatusBar): Promise<MigrationDetailsTableTab> {
|
statusBar: DashboardStatusBar): Promise<MigrationDetailsTableTab> {
|
||||||
|
|
||||||
this.view = view;
|
this.view = view;
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.onClosedCallback = onClosedCallback;
|
this.openMigrationsListFcn = openMigrationsListFcn;
|
||||||
this.statusBar = statusBar;
|
this.statusBar = statusBar;
|
||||||
|
|
||||||
await this.initialize(this.view);
|
await this.initialize(this.view);
|
||||||
@@ -78,16 +77,17 @@ export class MigrationDetailsTableTab extends MigrationDetailsTabBase<MigrationD
|
|||||||
|
|
||||||
@debounce(500)
|
@debounce(500)
|
||||||
public async refresh(): Promise<void> {
|
public async refresh(): Promise<void> {
|
||||||
if (this.isRefreshing) {
|
if (this.isRefreshing ||
|
||||||
|
this.refreshLoader === undefined) {
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
this.isRefreshing = true;
|
this.isRefreshing = true;
|
||||||
this.refreshButton.enabled = false;
|
|
||||||
this.refreshLoader.loading = true;
|
this.refreshLoader.loading = true;
|
||||||
await this.statusBar.clearError();
|
await this.statusBar.clearError();
|
||||||
|
|
||||||
try {
|
|
||||||
await this.model.fetchStatus();
|
await this.model.fetchStatus();
|
||||||
await this._loadData();
|
await this._loadData();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -95,11 +95,10 @@ export class MigrationDetailsTableTab extends MigrationDetailsTabBase<MigrationD
|
|||||||
loc.MIGRATION_STATUS_REFRESH_ERROR,
|
loc.MIGRATION_STATUS_REFRESH_ERROR,
|
||||||
loc.MIGRATION_STATUS_REFRESH_ERROR,
|
loc.MIGRATION_STATUS_REFRESH_ERROR,
|
||||||
e.message);
|
e.message);
|
||||||
}
|
} finally {
|
||||||
|
|
||||||
this.isRefreshing = false;
|
|
||||||
this.refreshLoader.loading = false;
|
this.refreshLoader.loading = false;
|
||||||
this.refreshButton.enabled = true;
|
this.isRefreshing = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _loadData(): Promise<void> {
|
private async _loadData(): Promise<void> {
|
||||||
@@ -120,8 +119,9 @@ export class MigrationDetailsTableTab extends MigrationDetailsTabBase<MigrationD
|
|||||||
const targetType = getMigrationTargetTypeEnum(migration);
|
const targetType = getMigrationTargetTypeEnum(migration);
|
||||||
const targetServerVersion = MigrationTargetTypeName[targetType ?? ''];
|
const targetServerVersion = MigrationTargetTypeName[targetType ?? ''];
|
||||||
|
|
||||||
const hashSet: loc.LookupTable<number> = {};
|
|
||||||
this._progressDetail = migration?.properties.migrationStatusDetails?.listOfCopyProgressDetails ?? [];
|
this._progressDetail = migration?.properties.migrationStatusDetails?.listOfCopyProgressDetails ?? [];
|
||||||
|
|
||||||
|
const hashSet: loc.LookupTable<number> = {};
|
||||||
await this._populateTableData(hashSet);
|
await this._populateTableData(hashSet);
|
||||||
|
|
||||||
const successCount = hashSet[PipelineStatusCodes.Succeeded] ?? 0;
|
const successCount = hashSet[PipelineStatusCodes.Succeeded] ?? 0;
|
||||||
@@ -138,7 +138,7 @@ export class MigrationDetailsTableTab extends MigrationDetailsTabBase<MigrationD
|
|||||||
(hashSet[PipelineStatusCodes.RebuildingIndexes] ?? 0) +
|
(hashSet[PipelineStatusCodes.RebuildingIndexes] ?? 0) +
|
||||||
(hashSet[PipelineStatusCodes.InProgress] ?? 0);
|
(hashSet[PipelineStatusCodes.InProgress] ?? 0);
|
||||||
|
|
||||||
const totalCount = migration.properties.migrationStatusDetails?.listOfCopyProgressDetails.length ?? 0;
|
const totalCount = this._progressDetail.length;
|
||||||
|
|
||||||
this._updateSummaryComponent(SummaryCardIndex.TotalTables, totalCount);
|
this._updateSummaryComponent(SummaryCardIndex.TotalTables, totalCount);
|
||||||
this._updateSummaryComponent(SummaryCardIndex.InProgressTables, inProgressCount);
|
this._updateSummaryComponent(SummaryCardIndex.InProgressTables, inProgressCount);
|
||||||
@@ -155,7 +155,7 @@ export class MigrationDetailsTableTab extends MigrationDetailsTabBase<MigrationD
|
|||||||
this._targetServerInfoField.text.value = targetServerName;
|
this._targetServerInfoField.text.value = targetServerName;
|
||||||
this._targetVersionInfoField.text.value = targetServerVersion;
|
this._targetVersionInfoField.text.value = targetServerVersion;
|
||||||
|
|
||||||
this._migrationStatusInfoField.text.value = getMigrationStatus(migration) ?? EmptySettingValue;
|
this._migrationStatusInfoField.text.value = getMigrationStatusString(migration);
|
||||||
this._migrationStatusInfoField.icon!.iconPath = getMigrationStatusImage(migration);
|
this._migrationStatusInfoField.icon!.iconPath = getMigrationStatusImage(migration);
|
||||||
this._serverObjectsInfoField.text.value = totalCount.toLocaleString();
|
this._serverObjectsInfoField.text.value = totalCount.toLocaleString();
|
||||||
|
|
||||||
|
|||||||
@@ -6,20 +6,16 @@
|
|||||||
import * as azdata from 'azdata';
|
import * as azdata from 'azdata';
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import { IconPathHelper } from '../constants/iconPathHelper';
|
import { IconPathHelper } from '../constants/iconPathHelper';
|
||||||
import { getCurrentMigrations, getSelectedServiceStatus, MigrationLocalStorage } from '../models/migrationLocalStorage';
|
import { getCurrentMigrations, getSelectedServiceStatus } from '../models/migrationLocalStorage';
|
||||||
import * as loc from '../constants/strings';
|
import * as loc from '../constants/strings';
|
||||||
import { filterMigrations, getMigrationDuration, getMigrationStatusImage, getMigrationStatusWithErrors, getMigrationTime } from '../api/utils';
|
import { filterMigrations, getMigrationDuration, getMigrationStatusImage, getMigrationStatusWithErrors, getMigrationTime, MenuCommands } from '../api/utils';
|
||||||
import { SqlMigrationServiceDetailsDialog } from '../dialog/sqlMigrationService/sqlMigrationServiceDetailsDialog';
|
import { getMigrationTargetType, getMigrationMode, getMigrationModeEnum, canCancelMigration, canCutoverMigration, getMigrationStatus } from '../constants/helper';
|
||||||
import { ConfirmCutoverDialog } from '../dialog/migrationCutover/confirmCutoverDialog';
|
|
||||||
import { MigrationCutoverDialogModel } from '../dialog/migrationCutover/migrationCutoverDialogModel';
|
|
||||||
import { getMigrationTargetType, getMigrationMode, canRetryMigration, getMigrationModeEnum, canCancelMigration, canCutoverMigration, getMigrationStatus } from '../constants/helper';
|
|
||||||
import { RetryMigrationDialog } from '../dialog/retryMigration/retryMigrationDialog';
|
|
||||||
import { DatabaseMigration, getResourceName } from '../api/azure';
|
import { DatabaseMigration, getResourceName } from '../api/azure';
|
||||||
import { logError, TelemetryViews } from '../telemtery';
|
import { logError, TelemetryViews } from '../telemtery';
|
||||||
import { SelectMigrationServiceDialog } from '../dialog/selectMigrationService/selectMigrationServiceDialog';
|
import { SelectMigrationServiceDialog } from '../dialog/selectMigrationService/selectMigrationServiceDialog';
|
||||||
import { AdsMigrationStatus, EmptySettingValue, MenuCommands, TabBase } from './tabBase';
|
import { AdsMigrationStatus, EmptySettingValue, ServiceContextChangeEvent, TabBase } from './tabBase';
|
||||||
import { DashboardStatusBar } from './sqlServerDashboard';
|
|
||||||
import { MigrationMode } from '../models/stateMachine';
|
import { MigrationMode } from '../models/stateMachine';
|
||||||
|
import { DashboardStatusBar } from './DashboardStatusBar';
|
||||||
|
|
||||||
export const MigrationsListTabId = 'MigrationsListTab';
|
export const MigrationsListTabId = 'MigrationsListTab';
|
||||||
|
|
||||||
@@ -58,12 +54,14 @@ export class MigrationsListTab extends TabBase<MigrationsListTab> {
|
|||||||
context: vscode.ExtensionContext,
|
context: vscode.ExtensionContext,
|
||||||
view: azdata.ModelView,
|
view: azdata.ModelView,
|
||||||
openMigrationDetails: (migration: DatabaseMigration) => Promise<void>,
|
openMigrationDetails: (migration: DatabaseMigration) => Promise<void>,
|
||||||
|
serviceContextChangedEvent: vscode.EventEmitter<ServiceContextChangeEvent>,
|
||||||
statusBar: DashboardStatusBar,
|
statusBar: DashboardStatusBar,
|
||||||
): Promise<MigrationsListTab> {
|
): Promise<MigrationsListTab> {
|
||||||
|
|
||||||
this.view = view;
|
this.view = view;
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this._openMigrationDetails = openMigrationDetails;
|
this._openMigrationDetails = openMigrationDetails;
|
||||||
|
this.serviceContextChangedEvent = serviceContextChangedEvent;
|
||||||
this.statusBar = statusBar;
|
this.statusBar = statusBar;
|
||||||
|
|
||||||
await this.initialize();
|
await this.initialize();
|
||||||
@@ -71,29 +69,28 @@ export class MigrationsListTab extends TabBase<MigrationsListTab> {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public onDialogClosed = async (): Promise<void> =>
|
|
||||||
await this.updateServiceContext(this._serviceContextButton);
|
|
||||||
|
|
||||||
public async setMigrationFilter(filter: AdsMigrationStatus): Promise<void> {
|
public async setMigrationFilter(filter: AdsMigrationStatus): Promise<void> {
|
||||||
if (this._statusDropdown.values && this._statusDropdown.values.length > 0) {
|
if (this._statusDropdown.values && this._statusDropdown.values.length > 0) {
|
||||||
const statusFilter = (<azdata.CategoryValue[]>this._statusDropdown.values)
|
const statusFilter = (<azdata.CategoryValue[]>this._statusDropdown.values)
|
||||||
.find(value => value.name === filter.toString());
|
.find(value => value.name === filter.toString());
|
||||||
|
|
||||||
this._statusDropdown.value = statusFilter;
|
await this._statusDropdown.updateProperties({ 'value': statusFilter });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async refresh(): Promise<void> {
|
public async refresh(): Promise<void> {
|
||||||
if (this.isRefreshing) {
|
if (this.isRefreshing ||
|
||||||
|
this._refreshLoader === undefined) {
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
this.isRefreshing = true;
|
this.isRefreshing = true;
|
||||||
this._refresh.enabled = false;
|
|
||||||
this._refreshLoader.loading = true;
|
this._refreshLoader.loading = true;
|
||||||
|
|
||||||
await this.statusBar.clearError();
|
await this.statusBar.clearError();
|
||||||
|
|
||||||
try {
|
|
||||||
await this._statusTable.updateProperty('data', []);
|
await this._statusTable.updateProperty('data', []);
|
||||||
this._migrations = await getCurrentMigrations();
|
this._migrations = await getCurrentMigrations();
|
||||||
await this._populateMigrationTable();
|
await this._populateMigrationTable();
|
||||||
@@ -105,26 +102,22 @@ export class MigrationsListTab extends TabBase<MigrationsListTab> {
|
|||||||
logError(TelemetryViews.MigrationsTab, 'refreshMigrations', e);
|
logError(TelemetryViews.MigrationsTab, 'refreshMigrations', e);
|
||||||
} finally {
|
} finally {
|
||||||
this._refreshLoader.loading = false;
|
this._refreshLoader.loading = false;
|
||||||
this._refresh.enabled = true;
|
|
||||||
this.isRefreshing = false;
|
this.isRefreshing = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async initialize(): Promise<void> {
|
protected async initialize(): Promise<void> {
|
||||||
this._registerCommands();
|
this._createStatusTable();
|
||||||
|
|
||||||
this.content = this.view.modelBuilder.flexContainer()
|
this.content = this.view.modelBuilder.flexContainer()
|
||||||
.withItems(
|
.withItems(
|
||||||
[
|
[
|
||||||
this._createToolbar(),
|
this._createToolbar(),
|
||||||
await this._createSearchAndSortContainer(),
|
await this._createSearchAndSortContainer(),
|
||||||
this._createStatusTable()
|
this._statusTable,
|
||||||
],
|
],
|
||||||
{ CSSStyles: { 'width': '100%' } }
|
{ CSSStyles: { 'width': '100%' } }
|
||||||
).withLayout({
|
).withLayout({ width: '100%', flexFlow: 'column' })
|
||||||
width: '100%',
|
.withProps({ CSSStyles: { 'padding': '0px' } })
|
||||||
flexFlow: 'column',
|
|
||||||
}).withProps({ CSSStyles: { 'padding': '0px' } })
|
|
||||||
.component();
|
.component();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,20 +137,16 @@ export class MigrationsListTab extends TabBase<MigrationsListTab> {
|
|||||||
async (e) => await this.refresh()));
|
async (e) => await this.refresh()));
|
||||||
|
|
||||||
this._refreshLoader = this.view.modelBuilder.loadingComponent()
|
this._refreshLoader = this.view.modelBuilder.loadingComponent()
|
||||||
|
.withItem(this._refresh)
|
||||||
.withProps({
|
.withProps({
|
||||||
loading: false,
|
loading: false,
|
||||||
CSSStyles: {
|
CSSStyles: { 'height': '8px', 'margin-top': '6px' }
|
||||||
'height': '8px',
|
}).component();
|
||||||
'margin-top': '6px'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.component();
|
|
||||||
|
|
||||||
toolbar.addToolbarItems([
|
toolbar.addToolbarItems([
|
||||||
<azdata.ToolbarComponent>{ component: this.createNewMigrationButton(), toolbarSeparatorAfter: true },
|
<azdata.ToolbarComponent>{ component: this.createNewMigrationButton(), toolbarSeparatorAfter: true },
|
||||||
<azdata.ToolbarComponent>{ component: this.createNewSupportRequestButton() },
|
<azdata.ToolbarComponent>{ component: this.createNewSupportRequestButton() },
|
||||||
<azdata.ToolbarComponent>{ component: this.createFeedbackButton(), toolbarSeparatorAfter: true },
|
<azdata.ToolbarComponent>{ component: this.createFeedbackButton(), toolbarSeparatorAfter: true },
|
||||||
<azdata.ToolbarComponent>{ component: this._refresh },
|
|
||||||
<azdata.ToolbarComponent>{ component: this._refreshLoader },
|
<azdata.ToolbarComponent>{ component: this._refreshLoader },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -178,16 +167,25 @@ export class MigrationsListTab extends TabBase<MigrationsListTab> {
|
|||||||
width: 230,
|
width: 230,
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
const onDialogClosed = async (): Promise<void> =>
|
|
||||||
await this.updateServiceContext(this._serviceContextButton);
|
|
||||||
|
|
||||||
this.disposables.push(
|
this.disposables.push(
|
||||||
this._serviceContextButton.onDidClick(
|
this._serviceContextButton.onDidClick(
|
||||||
async () => {
|
async () => {
|
||||||
const dialog = new SelectMigrationServiceDialog(onDialogClosed);
|
const dialog = new SelectMigrationServiceDialog(this.serviceContextChangedEvent);
|
||||||
await dialog.initialize();
|
await dialog.initialize();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const connectionProfile = await azdata.connection.getCurrentConnection();
|
||||||
|
this.disposables.push(
|
||||||
|
this.serviceContextChangedEvent.event(
|
||||||
|
async (e) => {
|
||||||
|
if (e.connectionId === connectionProfile.connectionId) {
|
||||||
|
await this.updateServiceContext(this._serviceContextButton);
|
||||||
|
await this.refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
));
|
||||||
|
await this.updateServiceContext(this._serviceContextButton);
|
||||||
|
|
||||||
this._searchBox = this.view.modelBuilder.inputBox()
|
this._searchBox = this.view.modelBuilder.inputBox()
|
||||||
.withProps({
|
.withProps({
|
||||||
stopEnterPropagation: true,
|
stopEnterPropagation: true,
|
||||||
@@ -212,7 +210,9 @@ export class MigrationsListTab extends TabBase<MigrationsListTab> {
|
|||||||
.withProps({
|
.withProps({
|
||||||
ariaLabel: loc.MIGRATION_STATUS_FILTER,
|
ariaLabel: loc.MIGRATION_STATUS_FILTER,
|
||||||
values: this._statusDropdownValues,
|
values: this._statusDropdownValues,
|
||||||
width: '150px'
|
width: '150px',
|
||||||
|
fireOnTextChange: true,
|
||||||
|
value: this._statusDropdownValues[0],
|
||||||
}).component();
|
}).component();
|
||||||
this.disposables.push(
|
this.disposables.push(
|
||||||
this._statusDropdown.onValueChanged(
|
this._statusDropdown.onValueChanged(
|
||||||
@@ -311,173 +311,6 @@ export class MigrationsListTab extends TabBase<MigrationsListTab> {
|
|||||||
return container;
|
return container;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _registerCommands(): void {
|
|
||||||
this.disposables.push(vscode.commands.registerCommand(
|
|
||||||
MenuCommands.Cutover,
|
|
||||||
async (migrationId: string) => {
|
|
||||||
try {
|
|
||||||
await this.statusBar.clearError();
|
|
||||||
const migration = this._migrations.find(
|
|
||||||
migration => migration.id === migrationId);
|
|
||||||
|
|
||||||
if (canRetryMigration(migration)) {
|
|
||||||
const cutoverDialogModel = new MigrationCutoverDialogModel(
|
|
||||||
await MigrationLocalStorage.getMigrationServiceContext(),
|
|
||||||
migration!);
|
|
||||||
await cutoverDialogModel.fetchStatus();
|
|
||||||
const dialog = new ConfirmCutoverDialog(cutoverDialogModel);
|
|
||||||
await dialog.initialize();
|
|
||||||
if (cutoverDialogModel.CutoverError) {
|
|
||||||
void vscode.window.showErrorMessage(loc.MIGRATION_CUTOVER_ERROR);
|
|
||||||
logError(TelemetryViews.MigrationsTab, MenuCommands.Cutover, cutoverDialogModel.CutoverError);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
await vscode.window.showInformationMessage(loc.MIGRATION_CANNOT_CUTOVER);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
await this.statusBar.showError(
|
|
||||||
loc.MIGRATION_CUTOVER_ERROR,
|
|
||||||
loc.MIGRATION_CUTOVER_ERROR,
|
|
||||||
e.message);
|
|
||||||
|
|
||||||
logError(TelemetryViews.MigrationsTab, MenuCommands.Cutover, e);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
this.disposables.push(vscode.commands.registerCommand(
|
|
||||||
MenuCommands.ViewDatabase,
|
|
||||||
async (migrationId: string) => {
|
|
||||||
try {
|
|
||||||
await this.statusBar.clearError();
|
|
||||||
const migration = this._migrations.find(m => m.id === migrationId);
|
|
||||||
await this._openMigrationDetails(migration!);
|
|
||||||
} catch (e) {
|
|
||||||
await this.statusBar.showError(
|
|
||||||
loc.OPEN_MIGRATION_DETAILS_ERROR,
|
|
||||||
loc.OPEN_MIGRATION_DETAILS_ERROR,
|
|
||||||
e.message);
|
|
||||||
logError(TelemetryViews.MigrationsTab, MenuCommands.ViewDatabase, e);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
this.disposables.push(vscode.commands.registerCommand(
|
|
||||||
MenuCommands.ViewTarget,
|
|
||||||
async (migrationId: string) => {
|
|
||||||
try {
|
|
||||||
const migration = this._migrations.find(migration => migration.id === migrationId);
|
|
||||||
const url = 'https://portal.azure.com/#resource/' + migration!.properties.scope;
|
|
||||||
await vscode.env.openExternal(vscode.Uri.parse(url));
|
|
||||||
} catch (e) {
|
|
||||||
await this.statusBar.showError(
|
|
||||||
loc.OPEN_MIGRATION_TARGET_ERROR,
|
|
||||||
loc.OPEN_MIGRATION_TARGET_ERROR,
|
|
||||||
e.message);
|
|
||||||
logError(TelemetryViews.MigrationsTab, MenuCommands.ViewTarget, e);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
this.disposables.push(vscode.commands.registerCommand(
|
|
||||||
MenuCommands.ViewService,
|
|
||||||
async (migrationId: string) => {
|
|
||||||
try {
|
|
||||||
await this.statusBar.clearError();
|
|
||||||
const migration = this._migrations.find(migration => migration.id === migrationId);
|
|
||||||
const dialog = new SqlMigrationServiceDetailsDialog(
|
|
||||||
await MigrationLocalStorage.getMigrationServiceContext(),
|
|
||||||
migration!);
|
|
||||||
await dialog.initialize();
|
|
||||||
} catch (e) {
|
|
||||||
await this.statusBar.showError(
|
|
||||||
loc.OPEN_MIGRATION_SERVICE_ERROR,
|
|
||||||
loc.OPEN_MIGRATION_SERVICE_ERROR,
|
|
||||||
e.message);
|
|
||||||
logError(TelemetryViews.MigrationsTab, MenuCommands.ViewService, e);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
this.disposables.push(vscode.commands.registerCommand(
|
|
||||||
MenuCommands.CopyMigration,
|
|
||||||
async (migrationId: string) => {
|
|
||||||
await this.statusBar.clearError();
|
|
||||||
const migration = this._migrations.find(migration => migration.id === migrationId);
|
|
||||||
const cutoverDialogModel = new MigrationCutoverDialogModel(
|
|
||||||
await MigrationLocalStorage.getMigrationServiceContext(),
|
|
||||||
migration!);
|
|
||||||
|
|
||||||
try {
|
|
||||||
await cutoverDialogModel.fetchStatus();
|
|
||||||
} catch (e) {
|
|
||||||
await this.statusBar.showError(
|
|
||||||
loc.MIGRATION_STATUS_REFRESH_ERROR,
|
|
||||||
loc.MIGRATION_STATUS_REFRESH_ERROR,
|
|
||||||
e.message);
|
|
||||||
logError(TelemetryViews.MigrationsTab, MenuCommands.CopyMigration, e);
|
|
||||||
}
|
|
||||||
|
|
||||||
await vscode.env.clipboard.writeText(JSON.stringify(cutoverDialogModel.migration, undefined, 2));
|
|
||||||
await vscode.window.showInformationMessage(loc.DETAILS_COPIED);
|
|
||||||
}));
|
|
||||||
|
|
||||||
this.disposables.push(vscode.commands.registerCommand(
|
|
||||||
MenuCommands.CancelMigration,
|
|
||||||
async (migrationId: string) => {
|
|
||||||
try {
|
|
||||||
await this.statusBar.clearError();
|
|
||||||
const migration = this._migrations.find(migration => migration.id === migrationId);
|
|
||||||
if (canCancelMigration(migration)) {
|
|
||||||
void vscode.window.showInformationMessage(loc.CANCEL_MIGRATION_CONFIRMATION, loc.YES, loc.NO).then(async (v) => {
|
|
||||||
if (v === loc.YES) {
|
|
||||||
const cutoverDialogModel = new MigrationCutoverDialogModel(
|
|
||||||
await MigrationLocalStorage.getMigrationServiceContext(),
|
|
||||||
migration!);
|
|
||||||
await cutoverDialogModel.fetchStatus();
|
|
||||||
await cutoverDialogModel.cancelMigration();
|
|
||||||
|
|
||||||
if (cutoverDialogModel.CancelMigrationError) {
|
|
||||||
void vscode.window.showErrorMessage(loc.MIGRATION_CANNOT_CANCEL);
|
|
||||||
logError(TelemetryViews.MigrationsTab, MenuCommands.CancelMigration, cutoverDialogModel.CancelMigrationError);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
await vscode.window.showInformationMessage(loc.MIGRATION_CANNOT_CANCEL);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
await this.statusBar.showError(
|
|
||||||
loc.MIGRATION_CANCELLATION_ERROR,
|
|
||||||
loc.MIGRATION_CANCELLATION_ERROR,
|
|
||||||
e.message);
|
|
||||||
logError(TelemetryViews.MigrationsTab, MenuCommands.CancelMigration, e);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
this.disposables.push(vscode.commands.registerCommand(
|
|
||||||
MenuCommands.RetryMigration,
|
|
||||||
async (migrationId: string) => {
|
|
||||||
try {
|
|
||||||
await this.statusBar.clearError();
|
|
||||||
const migration = this._migrations.find(migration => migration.id === migrationId);
|
|
||||||
if (canRetryMigration(migration)) {
|
|
||||||
let retryMigrationDialog = new RetryMigrationDialog(
|
|
||||||
this.context,
|
|
||||||
await MigrationLocalStorage.getMigrationServiceContext(),
|
|
||||||
migration!,
|
|
||||||
async () => await this.onDialogClosed());
|
|
||||||
await retryMigrationDialog.openDialog();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
await vscode.window.showInformationMessage(loc.MIGRATION_CANNOT_RETRY);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
await this.statusBar.showError(
|
|
||||||
loc.MIGRATION_RETRY_ERROR,
|
|
||||||
loc.MIGRATION_RETRY_ERROR,
|
|
||||||
e.message);
|
|
||||||
logError(TelemetryViews.MigrationsTab, MenuCommands.RetryMigration, e);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
private _sortMigrations(migrations: DatabaseMigration[], columnName: string, ascending: boolean): void {
|
private _sortMigrations(migrations: DatabaseMigration[], columnName: string, ascending: boolean): void {
|
||||||
const sortDir = ascending ? -1 : 1;
|
const sortDir = ascending ? -1 : 1;
|
||||||
switch (columnName) {
|
switch (columnName) {
|
||||||
@@ -575,6 +408,7 @@ export class MigrationsListTab extends TabBase<MigrationsListTab> {
|
|||||||
(<azdata.CategoryValue>this._columnSortDropdown.value).name,
|
(<azdata.CategoryValue>this._columnSortDropdown.value).name,
|
||||||
this._columnSortCheckbox.checked === true);
|
this._columnSortCheckbox.checked === true);
|
||||||
|
|
||||||
|
const connectionProfile = await azdata.connection.getCurrentConnection();
|
||||||
const data: any[] = this._filteredMigrations.map((migration, index) => {
|
const data: any[] = this._filteredMigrations.map((migration, index) => {
|
||||||
return [
|
return [
|
||||||
<azdata.HyperlinkColumnCellValue>{
|
<azdata.HyperlinkColumnCellValue>{
|
||||||
@@ -597,7 +431,11 @@ export class MigrationsListTab extends TabBase<MigrationsListTab> {
|
|||||||
getMigrationTime(migration.properties.endedOn), // finishTime
|
getMigrationTime(migration.properties.endedOn), // finishTime
|
||||||
<azdata.ContextMenuColumnCellValue>{
|
<azdata.ContextMenuColumnCellValue>{
|
||||||
title: '',
|
title: '',
|
||||||
context: migration.id,
|
context: {
|
||||||
|
connectionId: connectionProfile.connectionId,
|
||||||
|
migrationId: migration.id,
|
||||||
|
migrationOperationId: migration.properties.migrationOperationId,
|
||||||
|
},
|
||||||
commands: this._getMenuCommands(migration), // context menu
|
commands: this._getMenuCommands(migration), // context menu
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -632,7 +470,6 @@ export class MigrationsListTab extends TabBase<MigrationsListTab> {
|
|||||||
value: 'sourceDatabase',
|
value: 'sourceDatabase',
|
||||||
width: 190,
|
width: 190,
|
||||||
type: azdata.ColumnType.hyperlink,
|
type: azdata.ColumnType.hyperlink,
|
||||||
showText: true,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cssClass: rowCssStyles,
|
cssClass: rowCssStyles,
|
||||||
@@ -717,7 +554,8 @@ export class MigrationsListTab extends TabBase<MigrationsListTab> {
|
|||||||
]
|
]
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
this.disposables.push(this._statusTable.onCellAction!(async (rowState: azdata.ICellActionEventArgs) => {
|
this.disposables.push(
|
||||||
|
this._statusTable.onCellAction!(async (rowState: azdata.ICellActionEventArgs) => {
|
||||||
const buttonState = <azdata.ICellActionEventArgs>rowState;
|
const buttonState = <azdata.ICellActionEventArgs>rowState;
|
||||||
const migration = this._filteredMigrations[rowState.row];
|
const migration = this._filteredMigrations[rowState.row];
|
||||||
switch (buttonState?.column) {
|
switch (buttonState?.column) {
|
||||||
|
|||||||
@@ -6,16 +6,16 @@
|
|||||||
import * as azdata from 'azdata';
|
import * as azdata from 'azdata';
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import * as loc from '../constants/strings';
|
import * as loc from '../constants/strings';
|
||||||
import { AdsMigrationStatus, TabBase } from './tabBase';
|
import { AdsMigrationStatus, MigrationDetailsEvent, ServiceContextChangeEvent, TabBase } from './tabBase';
|
||||||
import { MigrationsListTab, MigrationsListTabId } from './migrationsListTab';
|
import { MigrationsListTab, MigrationsListTabId } from './migrationsListTab';
|
||||||
import { DatabaseMigration } from '../api/azure';
|
import { DatabaseMigration, getMigrationDetails } from '../api/azure';
|
||||||
import { MigrationLocalStorage } from '../models/migrationLocalStorage';
|
import { MigrationLocalStorage } from '../models/migrationLocalStorage';
|
||||||
import { FileStorageType } from '../models/stateMachine';
|
import { FileStorageType } from '../models/stateMachine';
|
||||||
import { MigrationDetailsTabBase } from './migrationDetailsTabBase';
|
import { MigrationDetailsTabBase } from './migrationDetailsTabBase';
|
||||||
import { MigrationDetailsFileShareTab } from './migrationDetailsFileShareTab';
|
import { MigrationDetailsFileShareTab } from './migrationDetailsFileShareTab';
|
||||||
import { MigrationDetailsBlobContainerTab } from './migrationDetailsBlobContainerTab';
|
import { MigrationDetailsBlobContainerTab } from './migrationDetailsBlobContainerTab';
|
||||||
import { MigrationDetailsTableTab } from './migrationDetailsTableTab';
|
import { MigrationDetailsTableTab } from './migrationDetailsTableTab';
|
||||||
import { DashboardStatusBar } from './sqlServerDashboard';
|
import { DashboardStatusBar } from './DashboardStatusBar';
|
||||||
|
|
||||||
export const MigrationsTabId = 'MigrationsTab';
|
export const MigrationsTabId = 'MigrationsTab';
|
||||||
|
|
||||||
@@ -27,6 +27,7 @@ export class MigrationsTab extends TabBase<MigrationsTab> {
|
|||||||
private _migrationDetailsBlobTab!: MigrationDetailsTabBase<any>;
|
private _migrationDetailsBlobTab!: MigrationDetailsTabBase<any>;
|
||||||
private _migrationDetailsTableTab!: MigrationDetailsTabBase<any>;
|
private _migrationDetailsTableTab!: MigrationDetailsTabBase<any>;
|
||||||
private _selectedTabId: string | undefined = undefined;
|
private _selectedTabId: string | undefined = undefined;
|
||||||
|
private _migrationDetailsEvent!: vscode.EventEmitter<MigrationDetailsEvent>;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
@@ -34,16 +35,17 @@ export class MigrationsTab extends TabBase<MigrationsTab> {
|
|||||||
this.id = MigrationsTabId;
|
this.id = MigrationsTabId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public onDialogClosed = async (): Promise<void> =>
|
|
||||||
await this._migrationsListTab.onDialogClosed();
|
|
||||||
|
|
||||||
public async create(
|
public async create(
|
||||||
context: vscode.ExtensionContext,
|
context: vscode.ExtensionContext,
|
||||||
view: azdata.ModelView,
|
view: azdata.ModelView,
|
||||||
|
serviceContextChangedEvent: vscode.EventEmitter<ServiceContextChangeEvent>,
|
||||||
|
migrationDetailsEvent: vscode.EventEmitter<MigrationDetailsEvent>,
|
||||||
statusBar: DashboardStatusBar): Promise<MigrationsTab> {
|
statusBar: DashboardStatusBar): Promise<MigrationsTab> {
|
||||||
|
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.view = view;
|
this.view = view;
|
||||||
|
this.serviceContextChangedEvent = serviceContextChangedEvent;
|
||||||
|
this._migrationDetailsEvent = migrationDetailsEvent;
|
||||||
this.statusBar = statusBar;
|
this.statusBar = statusBar;
|
||||||
|
|
||||||
await this.initialize(view);
|
await this.initialize(view);
|
||||||
@@ -56,9 +58,9 @@ export class MigrationsTab extends TabBase<MigrationsTab> {
|
|||||||
switch (this._selectedTabId) {
|
switch (this._selectedTabId) {
|
||||||
case undefined:
|
case undefined:
|
||||||
case MigrationsListTabId:
|
case MigrationsListTabId:
|
||||||
return await this._migrationsListTab?.refresh();
|
return this._migrationsListTab.refresh();
|
||||||
default:
|
default:
|
||||||
return await this._migrationDetailsTab?.refresh();
|
return this._migrationDetailsTab.refresh();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,41 +79,58 @@ export class MigrationsTab extends TabBase<MigrationsTab> {
|
|||||||
this._migrationsListTab = await new MigrationsListTab().create(
|
this._migrationsListTab = await new MigrationsListTab().create(
|
||||||
this.context,
|
this.context,
|
||||||
this.view,
|
this.view,
|
||||||
async (migration) => await this._openMigrationDetails(migration),
|
async (migration) => await this.openMigrationDetails(migration),
|
||||||
|
this.serviceContextChangedEvent,
|
||||||
this.statusBar);
|
this.statusBar);
|
||||||
this.disposables.push(this._migrationsListTab);
|
this.disposables.push(this._migrationsListTab);
|
||||||
|
|
||||||
|
const openMigrationsListTab = async (): Promise<void> => {
|
||||||
|
await this.statusBar.clearError();
|
||||||
|
await this._openTab(this._migrationsListTab);
|
||||||
|
};
|
||||||
|
|
||||||
this._migrationDetailsBlobTab = await new MigrationDetailsBlobContainerTab().create(
|
this._migrationDetailsBlobTab = await new MigrationDetailsBlobContainerTab().create(
|
||||||
this.context,
|
this.context,
|
||||||
this.view,
|
this.view,
|
||||||
async () => await this._openMigrationsListTab(),
|
openMigrationsListTab,
|
||||||
this.statusBar);
|
this.statusBar);
|
||||||
this.disposables.push(this._migrationDetailsBlobTab);
|
this.disposables.push(this._migrationDetailsBlobTab);
|
||||||
|
|
||||||
this._migrationDetailsFileShareTab = await new MigrationDetailsFileShareTab().create(
|
this._migrationDetailsFileShareTab = await new MigrationDetailsFileShareTab().create(
|
||||||
this.context,
|
this.context,
|
||||||
this.view,
|
this.view,
|
||||||
async () => await this._openMigrationsListTab(),
|
openMigrationsListTab,
|
||||||
this.statusBar);
|
this.statusBar);
|
||||||
this.disposables.push(this._migrationDetailsFileShareTab);
|
this.disposables.push(this._migrationDetailsFileShareTab);
|
||||||
|
|
||||||
this._migrationDetailsTableTab = await new MigrationDetailsTableTab().create(
|
this._migrationDetailsTableTab = await new MigrationDetailsTableTab().create(
|
||||||
this.context,
|
this.context,
|
||||||
this.view,
|
this.view,
|
||||||
async () => await this._openMigrationsListTab(),
|
openMigrationsListTab,
|
||||||
this.statusBar);
|
this.statusBar);
|
||||||
this.disposables.push(this._migrationDetailsFileShareTab);
|
this.disposables.push(this._migrationDetailsFileShareTab);
|
||||||
|
|
||||||
|
const connectionProfile = await azdata.connection.getCurrentConnection();
|
||||||
|
const connectionId = connectionProfile.connectionId;
|
||||||
|
this.disposables.push(
|
||||||
|
this._migrationDetailsEvent.event(async e => {
|
||||||
|
if (e.connectionId === connectionId) {
|
||||||
|
const migration = await this._getMigrationDetails(e.migrationId, e.migrationOperationId);
|
||||||
|
if (migration) {
|
||||||
|
await this.openMigrationDetails(migration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
this.content = this._tab;
|
this.content = this._tab;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async setMigrationFilter(filter: AdsMigrationStatus): Promise<void> {
|
public async setMigrationFilter(filter: AdsMigrationStatus): Promise<void> {
|
||||||
await this._migrationsListTab?.setMigrationFilter(filter);
|
|
||||||
await this._openTab(this._migrationsListTab);
|
await this._openTab(this._migrationsListTab);
|
||||||
await this._migrationsListTab?.setMigrationFilter(filter);
|
await this._migrationsListTab.setMigrationFilter(filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _openMigrationDetails(migration: DatabaseMigration): Promise<void> {
|
public async openMigrationDetails(migration: DatabaseMigration): Promise<void> {
|
||||||
switch (migration.properties.backupConfiguration?.sourceLocation?.fileStorageType) {
|
switch (migration.properties.backupConfiguration?.sourceLocation?.fileStorageType) {
|
||||||
case FileStorageType.AzureBlob:
|
case FileStorageType.AzureBlob:
|
||||||
this._migrationDetailsTab = this._migrationDetailsBlobTab;
|
this._migrationDetailsTab = this._migrationDetailsBlobTab;
|
||||||
@@ -128,12 +147,21 @@ export class MigrationsTab extends TabBase<MigrationsTab> {
|
|||||||
await MigrationLocalStorage.getMigrationServiceContext(),
|
await MigrationLocalStorage.getMigrationServiceContext(),
|
||||||
migration);
|
migration);
|
||||||
|
|
||||||
|
const promise = this._migrationDetailsTab.refresh();
|
||||||
await this._openTab(this._migrationDetailsTab);
|
await this._openTab(this._migrationDetailsTab);
|
||||||
|
await promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _openMigrationsListTab(): Promise<void> {
|
private async _getMigrationDetails(migrationId: string, migrationOperationId: string): Promise<DatabaseMigration | undefined> {
|
||||||
await this.statusBar.clearError();
|
const context = await MigrationLocalStorage.getMigrationServiceContext();
|
||||||
await this._openTab(this._migrationsListTab);
|
if (context.azureAccount && context.subscription) {
|
||||||
|
return getMigrationDetails(
|
||||||
|
context.azureAccount,
|
||||||
|
context.subscription,
|
||||||
|
migrationId,
|
||||||
|
migrationOperationId);
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _openTab(tab: azdata.Tab): Promise<void> {
|
private async _openTab(tab: azdata.Tab): Promise<void> {
|
||||||
@@ -141,6 +169,7 @@ export class MigrationsTab extends TabBase<MigrationsTab> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await this.statusBar.clearError();
|
||||||
this._tab.clearItems();
|
this._tab.clearItems();
|
||||||
this._tab.addItem(tab.content);
|
this._tab.addItem(tab.content);
|
||||||
this._selectedTabId = tab.id;
|
this._selectedTabId = tab.id;
|
||||||
|
|||||||
@@ -5,82 +5,121 @@
|
|||||||
|
|
||||||
import * as azdata from 'azdata';
|
import * as azdata from 'azdata';
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
|
import * as mssql from 'mssql';
|
||||||
|
import { promises as fs } from 'fs';
|
||||||
|
import { DatabaseMigration, getMigrationDetails } from '../api/azure';
|
||||||
|
import { MenuCommands, SqlMigrationExtensionId } from '../api/utils';
|
||||||
|
import { canCancelMigration, canRetryMigration } from '../constants/helper';
|
||||||
|
import { IconPathHelper } from '../constants/iconPathHelper';
|
||||||
|
import { MigrationNotebookInfo, NotebookPathHelper } from '../constants/notebookPathHelper';
|
||||||
import * as loc from '../constants/strings';
|
import * as loc from '../constants/strings';
|
||||||
|
import { SavedAssessmentDialog } from '../dialog/assessmentResults/savedAssessmentDialog';
|
||||||
|
import { ConfirmCutoverDialog } from '../dialog/migrationCutover/confirmCutoverDialog';
|
||||||
|
import { MigrationCutoverDialogModel } from '../dialog/migrationCutover/migrationCutoverDialogModel';
|
||||||
|
import { RetryMigrationDialog } from '../dialog/retryMigration/retryMigrationDialog';
|
||||||
|
import { SqlMigrationServiceDetailsDialog } from '../dialog/sqlMigrationService/sqlMigrationServiceDetailsDialog';
|
||||||
|
import { MigrationLocalStorage } from '../models/migrationLocalStorage';
|
||||||
|
import { MigrationStateModel, SavedInfo } from '../models/stateMachine';
|
||||||
|
import { logError, TelemetryViews } from '../telemtery';
|
||||||
|
import { WizardController } from '../wizard/wizardController';
|
||||||
|
import { DashboardStatusBar, ErrorEvent } from './DashboardStatusBar';
|
||||||
import { DashboardTab } from './dashboardTab';
|
import { DashboardTab } from './dashboardTab';
|
||||||
import { MigrationsTab, MigrationsTabId } from './migrationsTab';
|
import { MigrationsTab, MigrationsTabId } from './migrationsTab';
|
||||||
import { AdsMigrationStatus } from './tabBase';
|
import { AdsMigrationStatus, MigrationDetailsEvent, ServiceContextChangeEvent } from './tabBase';
|
||||||
|
|
||||||
export interface DashboardStatusBar {
|
export interface MenuCommandArgs {
|
||||||
showError: (errorTitle: string, errorLable: string, errorDescription: string) => Promise<void>;
|
connectionId: string,
|
||||||
clearError: () => Promise<void>;
|
migrationId: string,
|
||||||
errorTitle: string;
|
migrationOperationId: string,
|
||||||
errorLabel: string;
|
|
||||||
errorDescription: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DashboardWidget implements DashboardStatusBar {
|
export class DashboardWidget {
|
||||||
private _context: vscode.ExtensionContext;
|
public stateModel!: MigrationStateModel;
|
||||||
private _view!: azdata.ModelView;
|
private readonly _context: vscode.ExtensionContext;
|
||||||
private _tabs!: azdata.TabbedPanelComponent;
|
private readonly _onServiceContextChanged: vscode.EventEmitter<ServiceContextChangeEvent>;
|
||||||
private _statusInfoBox!: azdata.InfoBoxComponent;
|
private readonly _migrationDetailsEvent: vscode.EventEmitter<MigrationDetailsEvent>;
|
||||||
private _dashboardTab!: DashboardTab;
|
private readonly _errorEvent: vscode.EventEmitter<ErrorEvent>;
|
||||||
private _migrationsTab!: MigrationsTab;
|
|
||||||
private _disposables: vscode.Disposable[] = [];
|
|
||||||
|
|
||||||
constructor(context: vscode.ExtensionContext) {
|
constructor(context: vscode.ExtensionContext) {
|
||||||
this._context = context;
|
this._context = context;
|
||||||
|
NotebookPathHelper.setExtensionContext(context);
|
||||||
|
IconPathHelper.setExtensionContext(context);
|
||||||
|
MigrationLocalStorage.setExtensionContext(context);
|
||||||
|
|
||||||
|
this._onServiceContextChanged = new vscode.EventEmitter<ServiceContextChangeEvent>();
|
||||||
|
this._errorEvent = new vscode.EventEmitter<ErrorEvent>();
|
||||||
|
this._migrationDetailsEvent = new vscode.EventEmitter<MigrationDetailsEvent>();
|
||||||
|
|
||||||
|
context.subscriptions.push(this._onServiceContextChanged);
|
||||||
|
context.subscriptions.push(this._errorEvent);
|
||||||
|
context.subscriptions.push(this._migrationDetailsEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
public errorTitle: string = '';
|
public async register(): Promise<void> {
|
||||||
public errorLabel: string = '';
|
await this._registerCommands();
|
||||||
public errorDescription: string = '';
|
|
||||||
|
|
||||||
public async showError(errorTitle: string, errorLabel: string, errorDescription: string): Promise<void> {
|
|
||||||
this.errorTitle = errorTitle;
|
|
||||||
this.errorLabel = errorLabel;
|
|
||||||
this.errorDescription = errorDescription;
|
|
||||||
this._statusInfoBox.style = 'error';
|
|
||||||
this._statusInfoBox.text = errorTitle;
|
|
||||||
await this._updateStatusDisplay(this._statusInfoBox, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async clearError(): Promise<void> {
|
|
||||||
await this._updateStatusDisplay(this._statusInfoBox, false);
|
|
||||||
this.errorTitle = '';
|
|
||||||
this.errorLabel = '';
|
|
||||||
this.errorDescription = '';
|
|
||||||
this._statusInfoBox.style = 'success';
|
|
||||||
this._statusInfoBox.text = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
public register(): void {
|
|
||||||
azdata.ui.registerModelViewProvider('migration-dashboard', async (view) => {
|
azdata.ui.registerModelViewProvider('migration-dashboard', async (view) => {
|
||||||
this._view = view;
|
const disposables: vscode.Disposable[] = [];
|
||||||
this._disposables.push(
|
const _view = view;
|
||||||
this._view.onClosed(e => {
|
|
||||||
this._disposables.forEach(
|
const statusInfoBox = view.modelBuilder.infoBox()
|
||||||
d => { try { d.dispose(); } catch { } });
|
.withProps({
|
||||||
}));
|
style: 'error',
|
||||||
|
text: '',
|
||||||
|
clickableButtonAriaLabel: loc.ERROR_DIALOG_ARIA_CLICK_VIEW_ERROR_DETAILS,
|
||||||
|
announceText: true,
|
||||||
|
isClickable: true,
|
||||||
|
display: 'none',
|
||||||
|
CSSStyles: { 'font-size': '14px', 'display': 'none', },
|
||||||
|
}).component();
|
||||||
|
|
||||||
|
const connectionProfile = await azdata.connection.getCurrentConnection();
|
||||||
|
const statusBar = new DashboardStatusBar(
|
||||||
|
this._context,
|
||||||
|
connectionProfile.connectionId,
|
||||||
|
statusInfoBox,
|
||||||
|
this._errorEvent);
|
||||||
|
|
||||||
|
disposables.push(
|
||||||
|
statusInfoBox.onDidClick(
|
||||||
|
async e => await statusBar.openErrorDialog()));
|
||||||
|
|
||||||
|
disposables.push(
|
||||||
|
_view.onClosed(e =>
|
||||||
|
disposables.forEach(
|
||||||
|
d => { try { d.dispose(); } catch { } })));
|
||||||
|
|
||||||
const openMigrationFcn = async (filter: AdsMigrationStatus): Promise<void> => {
|
const openMigrationFcn = async (filter: AdsMigrationStatus): Promise<void> => {
|
||||||
this._tabs.selectTab(MigrationsTabId);
|
if (!migrationsTabInitialized) {
|
||||||
await this._migrationsTab.setMigrationFilter(filter);
|
migrationsTabInitialized = true;
|
||||||
|
tabs.selectTab(MigrationsTabId);
|
||||||
|
await migrationsTab.setMigrationFilter(AdsMigrationStatus.ALL);
|
||||||
|
await migrationsTab.refresh();
|
||||||
|
await migrationsTab.setMigrationFilter(filter);
|
||||||
|
} else {
|
||||||
|
const promise = migrationsTab.setMigrationFilter(filter);
|
||||||
|
tabs.selectTab(MigrationsTabId);
|
||||||
|
await promise;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this._dashboardTab = await new DashboardTab().create(
|
const dashboardTab = await new DashboardTab().create(
|
||||||
view,
|
view,
|
||||||
async (filter: AdsMigrationStatus) => await openMigrationFcn(filter),
|
async (filter: AdsMigrationStatus) => await openMigrationFcn(filter),
|
||||||
this);
|
this._onServiceContextChanged,
|
||||||
this._disposables.push(this._dashboardTab);
|
statusBar);
|
||||||
|
disposables.push(dashboardTab);
|
||||||
|
|
||||||
this._migrationsTab = await new MigrationsTab().create(
|
const migrationsTab = await new MigrationsTab().create(
|
||||||
this._context,
|
this._context,
|
||||||
view,
|
view,
|
||||||
this);
|
this._onServiceContextChanged,
|
||||||
this._disposables.push(this._migrationsTab);
|
this._migrationDetailsEvent,
|
||||||
|
statusBar);
|
||||||
|
disposables.push(migrationsTab);
|
||||||
|
|
||||||
this._tabs = view.modelBuilder.tabbedPanel()
|
const tabs = view.modelBuilder.tabbedPanel()
|
||||||
.withTabs([this._dashboardTab, this._migrationsTab])
|
.withTabs([dashboardTab, migrationsTab])
|
||||||
.withLayout({ alwaysShowTabs: true, orientation: azdata.TabOrientation.Horizontal })
|
.withLayout({ alwaysShowTabs: true, orientation: azdata.TabOrientation.Horizontal })
|
||||||
.withProps({
|
.withProps({
|
||||||
CSSStyles: {
|
CSSStyles: {
|
||||||
@@ -91,107 +130,338 @@ export class DashboardWidget implements DashboardStatusBar {
|
|||||||
})
|
})
|
||||||
.component();
|
.component();
|
||||||
|
|
||||||
this._disposables.push(
|
let migrationsTabInitialized = false;
|
||||||
this._tabs.onTabChanged(
|
disposables.push(
|
||||||
async id => {
|
tabs.onTabChanged(async tabId => {
|
||||||
await this.clearError();
|
const connectionProfile = await azdata.connection.getCurrentConnection();
|
||||||
await this.onDialogClosed();
|
await this.clearError(connectionProfile.connectionId);
|
||||||
|
if (tabId === MigrationsTabId && !migrationsTabInitialized) {
|
||||||
|
migrationsTabInitialized = true;
|
||||||
|
await migrationsTab.refresh();
|
||||||
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this._statusInfoBox = view.modelBuilder.infoBox()
|
|
||||||
.withProps({
|
|
||||||
style: 'error',
|
|
||||||
text: '',
|
|
||||||
announceText: true,
|
|
||||||
isClickable: true,
|
|
||||||
display: 'none',
|
|
||||||
CSSStyles: { 'font-size': '14px' },
|
|
||||||
}).component();
|
|
||||||
|
|
||||||
this._disposables.push(
|
|
||||||
this._statusInfoBox.onDidClick(
|
|
||||||
async e => await this.openErrorDialog()));
|
|
||||||
|
|
||||||
const flexContainer = view.modelBuilder.flexContainer()
|
const flexContainer = view.modelBuilder.flexContainer()
|
||||||
.withLayout({ flexFlow: 'column' })
|
.withLayout({ flexFlow: 'column' })
|
||||||
.withItems([this._statusInfoBox, this._tabs])
|
.withItems([statusInfoBox, tabs])
|
||||||
.component();
|
.component();
|
||||||
await view.initializeModel(flexContainer);
|
await view.initializeModel(flexContainer);
|
||||||
|
await dashboardTab.refresh();
|
||||||
await this.refresh();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async refresh(): Promise<void> {
|
private async _registerCommands(): Promise<void> {
|
||||||
void this._migrationsTab.refresh();
|
this._context.subscriptions.push(
|
||||||
await this._dashboardTab.refresh();
|
vscode.commands.registerCommand(
|
||||||
}
|
MenuCommands.Cutover,
|
||||||
|
async (args: MenuCommandArgs) => {
|
||||||
public async onDialogClosed(): Promise<void> {
|
|
||||||
await this._dashboardTab.onDialogClosed();
|
|
||||||
await this._migrationsTab.onDialogClosed();
|
|
||||||
}
|
|
||||||
|
|
||||||
private _errorDialogIsOpen: boolean = false;
|
|
||||||
|
|
||||||
protected async openErrorDialog(): Promise<void> {
|
|
||||||
if (this._errorDialogIsOpen) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const tab = azdata.window.createTab(this.errorTitle);
|
await this.clearError(args.connectionId);
|
||||||
tab.registerContent(async (view) => {
|
const migration = await this._getMigrationById(args.migrationId, args.migrationOperationId);
|
||||||
const flex = view.modelBuilder.flexContainer()
|
if (canRetryMigration(migration)) {
|
||||||
.withItems([
|
const cutoverDialogModel = new MigrationCutoverDialogModel(
|
||||||
view.modelBuilder.text()
|
await MigrationLocalStorage.getMigrationServiceContext(),
|
||||||
.withProps({ value: this.errorLabel, CSSStyles: { 'margin': '0px 0px 5px 5px' } })
|
migration!);
|
||||||
.component(),
|
await cutoverDialogModel.fetchStatus();
|
||||||
view.modelBuilder.inputBox()
|
const dialog = new ConfirmCutoverDialog(cutoverDialogModel);
|
||||||
.withProps({
|
await dialog.initialize();
|
||||||
value: this.errorDescription,
|
if (cutoverDialogModel.CutoverError) {
|
||||||
readOnly: true,
|
void vscode.window.showErrorMessage(loc.MIGRATION_CUTOVER_ERROR);
|
||||||
multiline: true,
|
logError(TelemetryViews.MigrationsTab, MenuCommands.Cutover, cutoverDialogModel.CutoverError);
|
||||||
inputType: 'text',
|
}
|
||||||
rows: 20,
|
} else {
|
||||||
CSSStyles: { 'overflow': 'hidden auto', 'margin': '0px 0px 0px 5px' },
|
await vscode.window.showInformationMessage(loc.MIGRATION_CANNOT_CUTOVER);
|
||||||
})
|
}
|
||||||
.component()
|
} catch (e) {
|
||||||
])
|
await this.showError(
|
||||||
.withLayout({
|
args.connectionId,
|
||||||
flexFlow: 'column',
|
loc.MIGRATION_CUTOVER_ERROR,
|
||||||
width: 420,
|
loc.MIGRATION_CUTOVER_ERROR,
|
||||||
})
|
e.message);
|
||||||
.withProps({ CSSStyles: { 'margin': '0 10px 0 10px' } })
|
|
||||||
.component();
|
logError(TelemetryViews.MigrationsTab, MenuCommands.Cutover, e);
|
||||||
|
|
||||||
await view.initializeModel(flex);
|
|
||||||
});
|
|
||||||
|
|
||||||
const dialog = azdata.window.createModelViewDialog(
|
|
||||||
this.errorTitle,
|
|
||||||
'errorDialog',
|
|
||||||
450,
|
|
||||||
'flyout');
|
|
||||||
dialog.content = [tab];
|
|
||||||
dialog.okButton.label = loc.ERROR_DIALOG_CLEAR_BUTTON_LABEL;
|
|
||||||
dialog.okButton.focused = true;
|
|
||||||
dialog.cancelButton.label = loc.CLOSE;
|
|
||||||
this._disposables.push(
|
|
||||||
dialog.onClosed(async e => {
|
|
||||||
if (e === 'ok') {
|
|
||||||
await this.clearError();
|
|
||||||
}
|
}
|
||||||
this._errorDialogIsOpen = false;
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
azdata.window.openDialog(dialog);
|
this._context.subscriptions.push(
|
||||||
} catch (error) {
|
vscode.commands.registerCommand(
|
||||||
this._errorDialogIsOpen = false;
|
MenuCommands.ViewDatabase,
|
||||||
|
async (args: MenuCommandArgs) => {
|
||||||
|
try {
|
||||||
|
await this.clearError(args.connectionId);
|
||||||
|
this._migrationDetailsEvent.fire({
|
||||||
|
connectionId: args.connectionId,
|
||||||
|
migrationId: args.migrationId,
|
||||||
|
migrationOperationId: args.migrationOperationId,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
await this.showError(
|
||||||
|
args.connectionId,
|
||||||
|
loc.OPEN_MIGRATION_DETAILS_ERROR,
|
||||||
|
loc.OPEN_MIGRATION_DETAILS_ERROR,
|
||||||
|
e.message);
|
||||||
|
logError(TelemetryViews.MigrationsTab, MenuCommands.ViewDatabase, e);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
this._context.subscriptions.push(
|
||||||
|
vscode.commands.registerCommand(
|
||||||
|
MenuCommands.ViewTarget,
|
||||||
|
async (args: MenuCommandArgs) => {
|
||||||
|
try {
|
||||||
|
const migration = await this._getMigrationById(args.migrationId, args.migrationOperationId);
|
||||||
|
const url = 'https://portal.azure.com/#resource/' + migration!.properties.scope;
|
||||||
|
await vscode.env.openExternal(vscode.Uri.parse(url));
|
||||||
|
} catch (e) {
|
||||||
|
await this.showError(
|
||||||
|
args.connectionId,
|
||||||
|
loc.OPEN_MIGRATION_TARGET_ERROR,
|
||||||
|
loc.OPEN_MIGRATION_TARGET_ERROR,
|
||||||
|
e.message);
|
||||||
|
logError(TelemetryViews.MigrationsTab, MenuCommands.ViewTarget, e);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
this._context.subscriptions.push(
|
||||||
|
vscode.commands.registerCommand(
|
||||||
|
MenuCommands.ViewService,
|
||||||
|
async (args: MenuCommandArgs) => {
|
||||||
|
try {
|
||||||
|
await this.clearError(args.connectionId);
|
||||||
|
const migration = await this._getMigrationById(args.migrationId, args.migrationOperationId);
|
||||||
|
const dialog = new SqlMigrationServiceDetailsDialog(
|
||||||
|
await MigrationLocalStorage.getMigrationServiceContext(),
|
||||||
|
migration!);
|
||||||
|
await dialog.initialize();
|
||||||
|
} catch (e) {
|
||||||
|
await this.showError(
|
||||||
|
args.connectionId,
|
||||||
|
loc.OPEN_MIGRATION_SERVICE_ERROR,
|
||||||
|
loc.OPEN_MIGRATION_SERVICE_ERROR,
|
||||||
|
e.message);
|
||||||
|
logError(TelemetryViews.MigrationsTab, MenuCommands.ViewService, e);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
this._context.subscriptions.push(
|
||||||
|
vscode.commands.registerCommand(
|
||||||
|
MenuCommands.CopyMigration,
|
||||||
|
async (args: MenuCommandArgs) => {
|
||||||
|
await this.clearError(args.connectionId);
|
||||||
|
const migration = await this._getMigrationById(args.migrationId, args.migrationOperationId);
|
||||||
|
if (migration) {
|
||||||
|
const cutoverDialogModel = new MigrationCutoverDialogModel(
|
||||||
|
await MigrationLocalStorage.getMigrationServiceContext(),
|
||||||
|
migration);
|
||||||
|
try {
|
||||||
|
await cutoverDialogModel.fetchStatus();
|
||||||
|
} catch (e) {
|
||||||
|
await this.showError(
|
||||||
|
args.connectionId,
|
||||||
|
loc.MIGRATION_STATUS_REFRESH_ERROR,
|
||||||
|
loc.MIGRATION_STATUS_REFRESH_ERROR,
|
||||||
|
e.message);
|
||||||
|
logError(TelemetryViews.MigrationsTab, MenuCommands.CopyMigration, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
await vscode.env.clipboard.writeText(JSON.stringify(cutoverDialogModel.migration, undefined, 2));
|
||||||
|
await vscode.window.showInformationMessage(loc.DETAILS_COPIED);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
this._context.subscriptions.push(vscode.commands.registerCommand(
|
||||||
|
MenuCommands.CancelMigration,
|
||||||
|
async (args: MenuCommandArgs) => {
|
||||||
|
try {
|
||||||
|
await this.clearError(args.connectionId);
|
||||||
|
const migration = await this._getMigrationById(args.migrationId, args.migrationOperationId);
|
||||||
|
if (canCancelMigration(migration)) {
|
||||||
|
void vscode.window.showInformationMessage(loc.CANCEL_MIGRATION_CONFIRMATION, loc.YES, loc.NO)
|
||||||
|
.then(async (v) => {
|
||||||
|
if (v === loc.YES) {
|
||||||
|
const cutoverDialogModel = new MigrationCutoverDialogModel(
|
||||||
|
await MigrationLocalStorage.getMigrationServiceContext(),
|
||||||
|
migration!);
|
||||||
|
await cutoverDialogModel.fetchStatus();
|
||||||
|
await cutoverDialogModel.cancelMigration();
|
||||||
|
|
||||||
|
if (cutoverDialogModel.CancelMigrationError) {
|
||||||
|
void vscode.window.showErrorMessage(loc.MIGRATION_CANNOT_CANCEL);
|
||||||
|
logError(TelemetryViews.MigrationsTab, MenuCommands.CancelMigration, cutoverDialogModel.CancelMigrationError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await vscode.window.showInformationMessage(loc.MIGRATION_CANNOT_CANCEL);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
await this.showError(
|
||||||
|
args.connectionId,
|
||||||
|
loc.MIGRATION_CANCELLATION_ERROR,
|
||||||
|
loc.MIGRATION_CANCELLATION_ERROR,
|
||||||
|
e.message);
|
||||||
|
logError(TelemetryViews.MigrationsTab, MenuCommands.CancelMigration, e);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
this._context.subscriptions.push(
|
||||||
|
vscode.commands.registerCommand(
|
||||||
|
MenuCommands.RetryMigration,
|
||||||
|
async (args: MenuCommandArgs) => {
|
||||||
|
try {
|
||||||
|
await this.clearError(args.connectionId);
|
||||||
|
const migration = await this._getMigrationById(args.migrationId, args.migrationOperationId);
|
||||||
|
if (canRetryMigration(migration)) {
|
||||||
|
const retryMigrationDialog = new RetryMigrationDialog(
|
||||||
|
this._context,
|
||||||
|
await MigrationLocalStorage.getMigrationServiceContext(),
|
||||||
|
migration!,
|
||||||
|
this._onServiceContextChanged);
|
||||||
|
await retryMigrationDialog.openDialog();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
await vscode.window.showInformationMessage(loc.MIGRATION_CANNOT_RETRY);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
await this.showError(
|
||||||
|
args.connectionId,
|
||||||
|
loc.MIGRATION_RETRY_ERROR,
|
||||||
|
loc.MIGRATION_RETRY_ERROR,
|
||||||
|
e.message);
|
||||||
|
logError(TelemetryViews.MigrationsTab, MenuCommands.RetryMigration, e);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
this._context.subscriptions.push(
|
||||||
|
vscode.commands.registerCommand(
|
||||||
|
MenuCommands.StartMigration,
|
||||||
|
async () => await this.launchMigrationWizard()));
|
||||||
|
|
||||||
|
this._context.subscriptions.push(
|
||||||
|
vscode.commands.registerCommand(
|
||||||
|
MenuCommands.OpenNotebooks,
|
||||||
|
async () => {
|
||||||
|
const input = vscode.window.createQuickPick<MigrationNotebookInfo>();
|
||||||
|
input.placeholder = loc.NOTEBOOK_QUICK_PICK_PLACEHOLDER;
|
||||||
|
input.items = NotebookPathHelper.getAllMigrationNotebooks();
|
||||||
|
|
||||||
|
this._context.subscriptions.push(
|
||||||
|
input.onDidAccept(async (e) => {
|
||||||
|
const selectedNotebook = input.selectedItems[0];
|
||||||
|
if (selectedNotebook) {
|
||||||
|
try {
|
||||||
|
await azdata.nb.showNotebookDocument(vscode.Uri.parse(`untitled: ${selectedNotebook.label}`), {
|
||||||
|
preview: false,
|
||||||
|
initialContent: (await fs.readFile(selectedNotebook.notebookPath)).toString(),
|
||||||
|
initialDirtyState: false
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
void vscode.window.showErrorMessage(`${loc.NOTEBOOK_OPEN_ERROR} - ${e.toString()}`);
|
||||||
|
}
|
||||||
|
input.hide();
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
input.show();
|
||||||
|
}));
|
||||||
|
|
||||||
|
this._context.subscriptions.push(azdata.tasks.registerTask(
|
||||||
|
MenuCommands.StartMigration,
|
||||||
|
async () => await this.launchMigrationWizard()));
|
||||||
|
|
||||||
|
this._context.subscriptions.push(
|
||||||
|
azdata.tasks.registerTask(
|
||||||
|
MenuCommands.NewSupportRequest,
|
||||||
|
async () => await this.launchNewSupportRequest()));
|
||||||
|
|
||||||
|
this._context.subscriptions.push(
|
||||||
|
azdata.tasks.registerTask(
|
||||||
|
MenuCommands.SendFeedback,
|
||||||
|
async () => {
|
||||||
|
const actionId = MenuCommands.IssueReporter;
|
||||||
|
const args = {
|
||||||
|
extensionId: SqlMigrationExtensionId,
|
||||||
|
issueTitle: loc.FEEDBACK_ISSUE_TITLE,
|
||||||
|
};
|
||||||
|
return await vscode.commands.executeCommand(actionId, args);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async clearError(connectionId: string): Promise<void> {
|
||||||
|
this._errorEvent.fire({
|
||||||
|
connectionId: connectionId,
|
||||||
|
title: '',
|
||||||
|
label: '',
|
||||||
|
message: '',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async showError(connectionId: string, title: string, label: string, message: string): Promise<void> {
|
||||||
|
this._errorEvent.fire({
|
||||||
|
connectionId: connectionId,
|
||||||
|
title: title,
|
||||||
|
label: label,
|
||||||
|
message: message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _getMigrationById(migrationId: string, migrationOperationId: string): Promise<DatabaseMigration | undefined> {
|
||||||
|
const context = await MigrationLocalStorage.getMigrationServiceContext();
|
||||||
|
if (context.azureAccount && context.subscription) {
|
||||||
|
return getMigrationDetails(
|
||||||
|
context.azureAccount,
|
||||||
|
context.subscription,
|
||||||
|
migrationId,
|
||||||
|
migrationOperationId);
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async launchMigrationWizard(): Promise<void> {
|
||||||
|
const activeConnection = await azdata.connection.getCurrentConnection();
|
||||||
|
let connectionId: string = '';
|
||||||
|
let serverName: string = '';
|
||||||
|
if (!activeConnection) {
|
||||||
|
const connection = await azdata.connection.openConnectionDialog();
|
||||||
|
if (connection) {
|
||||||
|
connectionId = connection.connectionId;
|
||||||
|
serverName = connection.options.server;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
connectionId = activeConnection.connectionId;
|
||||||
|
serverName = activeConnection.serverName;
|
||||||
|
}
|
||||||
|
if (serverName) {
|
||||||
|
const api = (await vscode.extensions.getExtension(mssql.extension.name)?.activate()) as mssql.IExtension;
|
||||||
|
if (api) {
|
||||||
|
this.stateModel = new MigrationStateModel(this._context, connectionId, api.sqlMigration);
|
||||||
|
this._context.subscriptions.push(this.stateModel);
|
||||||
|
const savedInfo = this.checkSavedInfo(serverName);
|
||||||
|
if (savedInfo) {
|
||||||
|
this.stateModel.savedInfo = savedInfo;
|
||||||
|
this.stateModel.serverName = serverName;
|
||||||
|
const savedAssessmentDialog = new SavedAssessmentDialog(
|
||||||
|
this._context,
|
||||||
|
this.stateModel,
|
||||||
|
this._onServiceContextChanged);
|
||||||
|
await savedAssessmentDialog.openDialog();
|
||||||
|
} else {
|
||||||
|
const wizardController = new WizardController(
|
||||||
|
this._context,
|
||||||
|
this.stateModel,
|
||||||
|
this._onServiceContextChanged);
|
||||||
|
await wizardController.openWizard(connectionId);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _updateStatusDisplay(control: azdata.Component, visible: boolean): Promise<void> {
|
private checkSavedInfo(serverName: string): SavedInfo | undefined {
|
||||||
await control.updateCssStyles({ 'display': visible ? 'inline' : 'none' });
|
return this._context.globalState.get<SavedInfo>(`${this.stateModel.mementoString}.${serverName}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async launchNewSupportRequest(): Promise<void> {
|
||||||
|
await vscode.env.openExternal(vscode.Uri.parse(
|
||||||
|
`https://portal.azure.com/#blade/Microsoft_Azure_Support/HelpAndSupportBlade/newsupportrequest`));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,10 +9,10 @@ import * as loc from '../constants/strings';
|
|||||||
import { IconPathHelper } from '../constants/iconPathHelper';
|
import { IconPathHelper } from '../constants/iconPathHelper';
|
||||||
import { EOL } from 'os';
|
import { EOL } from 'os';
|
||||||
import { DatabaseMigration } from '../api/azure';
|
import { DatabaseMigration } from '../api/azure';
|
||||||
import { DashboardStatusBar } from './sqlServerDashboard';
|
|
||||||
import { getSelectedServiceStatus } from '../models/migrationLocalStorage';
|
import { getSelectedServiceStatus } from '../models/migrationLocalStorage';
|
||||||
|
import { MenuCommands, SqlMigrationExtensionId } from '../api/utils';
|
||||||
|
import { DashboardStatusBar } from './DashboardStatusBar';
|
||||||
|
|
||||||
export const SqlMigrationExtensionId = 'microsoft.sql-migration';
|
|
||||||
export const EmptySettingValue = '-';
|
export const EmptySettingValue = '-';
|
||||||
|
|
||||||
export enum AdsMigrationStatus {
|
export enum AdsMigrationStatus {
|
||||||
@@ -23,17 +23,15 @@ export enum AdsMigrationStatus {
|
|||||||
COMPLETING = 'completing'
|
COMPLETING = 'completing'
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MenuCommands = {
|
export interface ServiceContextChangeEvent {
|
||||||
Cutover: 'sqlmigration.cutover',
|
connectionId: string;
|
||||||
ViewDatabase: 'sqlmigration.view.database',
|
}
|
||||||
ViewTarget: 'sqlmigration.view.target',
|
|
||||||
ViewService: 'sqlmigration.view.service',
|
export interface MigrationDetailsEvent {
|
||||||
CopyMigration: 'sqlmigration.copy.migration',
|
connectionId: string,
|
||||||
CancelMigration: 'sqlmigration.cancel.migration',
|
migrationId: string,
|
||||||
RetryMigration: 'sqlmigration.retry.migration',
|
migrationOperationId: string,
|
||||||
StartMigration: 'sqlmigration.start',
|
}
|
||||||
IssueReporter: 'workbench.action.openIssueReporter',
|
|
||||||
};
|
|
||||||
|
|
||||||
export abstract class TabBase<T> implements azdata.Tab, vscode.Disposable {
|
export abstract class TabBase<T> implements azdata.Tab, vscode.Disposable {
|
||||||
public content!: azdata.Component;
|
public content!: azdata.Component;
|
||||||
@@ -45,7 +43,8 @@ export abstract class TabBase<T> implements azdata.Tab, vscode.Disposable {
|
|||||||
protected view!: azdata.ModelView;
|
protected view!: azdata.ModelView;
|
||||||
protected disposables: vscode.Disposable[] = [];
|
protected disposables: vscode.Disposable[] = [];
|
||||||
protected isRefreshing: boolean = false;
|
protected isRefreshing: boolean = false;
|
||||||
protected openMigrationFcn!: (status: AdsMigrationStatus) => Promise<void>;
|
protected openMigrationsFcn!: (status: AdsMigrationStatus) => Promise<void>;
|
||||||
|
protected serviceContextChangedEvent!: vscode.EventEmitter<ServiceContextChangeEvent>;
|
||||||
protected statusBar!: DashboardStatusBar;
|
protected statusBar!: DashboardStatusBar;
|
||||||
|
|
||||||
protected abstract initialize(view: azdata.ModelView): Promise<void>;
|
protected abstract initialize(view: azdata.ModelView): Promise<void>;
|
||||||
@@ -165,8 +164,9 @@ export abstract class TabBase<T> implements azdata.Tab, vscode.Disposable {
|
|||||||
const errors = [];
|
const errors = [];
|
||||||
errors.push(migration.properties.provisioningError);
|
errors.push(migration.properties.provisioningError);
|
||||||
errors.push(migration.properties.migrationFailureError?.message);
|
errors.push(migration.properties.migrationFailureError?.message);
|
||||||
errors.push(...migration.properties.migrationStatusDetails?.fileUploadBlockingErrors ?? []);
|
errors.push(migration.properties.migrationStatusDetails?.fileUploadBlockingErrors ?? []);
|
||||||
errors.push(migration.properties.migrationStatusDetails?.restoreBlockingReason);
|
errors.push(migration.properties.migrationStatusDetails?.restoreBlockingReason);
|
||||||
|
errors.push(migration.properties.migrationStatusDetails?.sqlDataCopyErrors);
|
||||||
|
|
||||||
// remove undefined and duplicate error entries
|
// remove undefined and duplicate error entries
|
||||||
return errors
|
return errors
|
||||||
|
|||||||
@@ -118,12 +118,16 @@ export class AssessmentResultsDialog {
|
|||||||
this._model._miDbs = selectedDbs;
|
this._model._miDbs = selectedDbs;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case MigrationTargetType.SQLVM: {
|
case MigrationTargetType.SQLVM: {
|
||||||
this.didUpdateDatabasesForMigration(this._model._vmDbs, selectedDbs);
|
this.didUpdateDatabasesForMigration(this._model._vmDbs, selectedDbs);
|
||||||
this._model._vmDbs = selectedDbs;
|
this._model._vmDbs = selectedDbs;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case MigrationTargetType.SQLDB: {
|
||||||
|
this.didUpdateDatabasesForMigration(this._model._sqldbDbs, selectedDbs);
|
||||||
|
this._model._sqldbDbs = selectedDbs;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
await this._skuRecommendationPage.refreshCardText();
|
await this._skuRecommendationPage.refreshCardText();
|
||||||
this.model.refreshDatabaseBackupPage = true;
|
this.model.refreshDatabaseBackupPage = true;
|
||||||
|
|||||||
@@ -9,25 +9,28 @@ import * as constants from '../../constants/strings';
|
|||||||
import { MigrationStateModel } from '../../models/stateMachine';
|
import { MigrationStateModel } from '../../models/stateMachine';
|
||||||
import { WizardController } from '../../wizard/wizardController';
|
import { WizardController } from '../../wizard/wizardController';
|
||||||
import * as styles from '../../constants/styles';
|
import * as styles from '../../constants/styles';
|
||||||
|
import { ServiceContextChangeEvent } from '../../dashboard/tabBase';
|
||||||
|
|
||||||
export class SavedAssessmentDialog {
|
export class SavedAssessmentDialog {
|
||||||
|
|
||||||
private static readonly OkButtonText: string = constants.NEXT_LABEL;
|
private static readonly OkButtonText: string = constants.NEXT_LABEL;
|
||||||
private static readonly CancelButtonText: string = constants.CANCEL_LABEL;
|
private static readonly CancelButtonText: string = constants.CANCEL_LABEL;
|
||||||
|
|
||||||
private _isOpen: boolean = false;
|
|
||||||
private dialog: azdata.window.Dialog | undefined;
|
private dialog: azdata.window.Dialog | undefined;
|
||||||
private _rootContainer!: azdata.FlexContainer;
|
|
||||||
private stateModel: MigrationStateModel;
|
private stateModel: MigrationStateModel;
|
||||||
private context: vscode.ExtensionContext;
|
private context: vscode.ExtensionContext;
|
||||||
|
private _serviceContextChangedEvent: vscode.EventEmitter<ServiceContextChangeEvent>;
|
||||||
private _disposables: vscode.Disposable[] = [];
|
private _disposables: vscode.Disposable[] = [];
|
||||||
|
private _isOpen: boolean = false;
|
||||||
|
private _rootContainer!: azdata.FlexContainer;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
context: vscode.ExtensionContext,
|
context: vscode.ExtensionContext,
|
||||||
stateModel: MigrationStateModel,
|
stateModel: MigrationStateModel,
|
||||||
private readonly _onClosedCallback: () => Promise<void>) {
|
serviceContextChangedEvent: vscode.EventEmitter<ServiceContextChangeEvent>) {
|
||||||
this.stateModel = stateModel;
|
this.stateModel = stateModel;
|
||||||
this.context = context;
|
this.context = context;
|
||||||
|
this._serviceContextChangedEvent = serviceContextChangedEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async initializeDialog(dialog: azdata.window.Dialog): Promise<void> {
|
private async initializeDialog(dialog: azdata.window.Dialog): Promise<void> {
|
||||||
@@ -36,18 +39,18 @@ export class SavedAssessmentDialog {
|
|||||||
try {
|
try {
|
||||||
this._rootContainer = this.initializePageContent(view);
|
this._rootContainer = this.initializePageContent(view);
|
||||||
await view.initializeModel(this._rootContainer);
|
await view.initializeModel(this._rootContainer);
|
||||||
this._disposables.push(dialog.okButton.onClick(async e => {
|
this._disposables.push(
|
||||||
await this.execute();
|
dialog.okButton.onClick(
|
||||||
}));
|
async e => await this.execute()));
|
||||||
this._disposables.push(dialog.cancelButton.onClick(e => {
|
|
||||||
this.cancel();
|
this._disposables.push(
|
||||||
}));
|
dialog.cancelButton.onClick(
|
||||||
|
e => this.cancel()));
|
||||||
|
this._disposables.push(
|
||||||
|
view.onClosed(
|
||||||
|
e => this._disposables.forEach(
|
||||||
|
d => { try { d.dispose(); } catch { } })));
|
||||||
|
|
||||||
this._disposables.push(view.onClosed(e => {
|
|
||||||
this._disposables.forEach(
|
|
||||||
d => { try { d.dispose(); } catch { } }
|
|
||||||
);
|
|
||||||
}));
|
|
||||||
resolve();
|
resolve();
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
reject(ex);
|
reject(ex);
|
||||||
@@ -83,7 +86,7 @@ export class SavedAssessmentDialog {
|
|||||||
const wizardController = new WizardController(
|
const wizardController = new WizardController(
|
||||||
this.context,
|
this.context,
|
||||||
this.stateModel,
|
this.stateModel,
|
||||||
this._onClosedCallback);
|
this._serviceContextChangedEvent);
|
||||||
|
|
||||||
await wizardController.openWizard(this.stateModel.sourceConnectionId);
|
await wizardController.openWizard(this.stateModel.sourceConnectionId);
|
||||||
this._isOpen = false;
|
this._isOpen = false;
|
||||||
@@ -100,44 +103,39 @@ export class SavedAssessmentDialog {
|
|||||||
public initializePageContent(view: azdata.ModelView): azdata.FlexContainer {
|
public initializePageContent(view: azdata.ModelView): azdata.FlexContainer {
|
||||||
const buttonGroup = 'resumeMigration';
|
const buttonGroup = 'resumeMigration';
|
||||||
|
|
||||||
const radioStart = view.modelBuilder.radioButton().withProps({
|
const radioStart = view.modelBuilder.radioButton()
|
||||||
|
.withProps({
|
||||||
label: constants.START_NEW_SESSION,
|
label: constants.START_NEW_SESSION,
|
||||||
name: buttonGroup,
|
name: buttonGroup,
|
||||||
CSSStyles: {
|
CSSStyles: { ...styles.BODY_CSS, 'margin-bottom': '8px' },
|
||||||
...styles.BODY_CSS,
|
|
||||||
'margin-bottom': '8px'
|
|
||||||
},
|
|
||||||
checked: true
|
checked: true
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
this._disposables.push(radioStart.onDidChangeCheckedState((e) => {
|
this._disposables.push(
|
||||||
if (e) {
|
radioStart.onDidChangeCheckedState(checked => {
|
||||||
|
if (checked) {
|
||||||
this.stateModel.resumeAssessment = false;
|
this.stateModel.resumeAssessment = false;
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
const radioContinue = view.modelBuilder.radioButton().withProps({
|
const radioContinue = view.modelBuilder.radioButton()
|
||||||
|
.withProps({
|
||||||
label: constants.RESUME_SESSION,
|
label: constants.RESUME_SESSION,
|
||||||
name: buttonGroup,
|
name: buttonGroup,
|
||||||
CSSStyles: {
|
CSSStyles: { ...styles.BODY_CSS },
|
||||||
...styles.BODY_CSS,
|
|
||||||
},
|
|
||||||
checked: false
|
checked: false
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
this._disposables.push(radioContinue.onDidChangeCheckedState((e) => {
|
this._disposables.push(
|
||||||
if (e) {
|
radioContinue.onDidChangeCheckedState(checked => {
|
||||||
|
if (checked) {
|
||||||
this.stateModel.resumeAssessment = true;
|
this.stateModel.resumeAssessment = true;
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const flex = view.modelBuilder.flexContainer()
|
const flex = view.modelBuilder.flexContainer()
|
||||||
.withLayout({
|
.withLayout({ flexFlow: 'column', })
|
||||||
flexFlow: 'column',
|
.withProps({ CSSStyles: { 'padding': '20px 15px', } })
|
||||||
}).withProps({
|
.component();
|
||||||
CSSStyles: {
|
|
||||||
'padding': '20px 15px',
|
|
||||||
}
|
|
||||||
}).component();
|
|
||||||
flex.addItem(radioStart, { flex: '0 0 auto' });
|
flex.addItem(radioStart, { flex: '0 0 auto' });
|
||||||
flex.addItem(radioContinue, { flex: '0 0 auto' });
|
flex.addItem(radioContinue, { flex: '0 0 auto' });
|
||||||
|
|
||||||
|
|||||||
@@ -91,7 +91,14 @@ export class SqlDatabaseTree {
|
|||||||
|
|
||||||
const selectDbMessage = this.createSelectDbMessage();
|
const selectDbMessage = this.createSelectDbMessage();
|
||||||
this._resultComponent = await this.createComponentResult(view);
|
this._resultComponent = await this.createComponentResult(view);
|
||||||
const treeComponent = await this.createComponent(view, this._targetType === MigrationTargetType.SQLVM ? this._model._vmDbs : this._model._miDbs);
|
const treeComponent = await this.createComponent(
|
||||||
|
view,
|
||||||
|
(this._targetType === MigrationTargetType.SQLVM)
|
||||||
|
? this._model._vmDbs
|
||||||
|
: (this._targetType === MigrationTargetType.SQLMI)
|
||||||
|
? this._model._miDbs
|
||||||
|
: this._model._sqldbDbs);
|
||||||
|
|
||||||
this._rootContainer = view.modelBuilder.flexContainer().withLayout({
|
this._rootContainer = view.modelBuilder.flexContainer().withLayout({
|
||||||
flexFlow: 'row',
|
flexFlow: 'row',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
@@ -101,7 +108,8 @@ export class SqlDatabaseTree {
|
|||||||
this._rootContainer.addItem(this._resultComponent, { flex: '0 0 auto' });
|
this._rootContainer.addItem(this._resultComponent, { flex: '0 0 auto' });
|
||||||
this._rootContainer.addItem(selectDbMessage, { flex: '1 1 auto' });
|
this._rootContainer.addItem(selectDbMessage, { flex: '1 1 auto' });
|
||||||
|
|
||||||
if (this._targetType === MigrationTargetType.SQLMI) {
|
if (this._targetType === MigrationTargetType.SQLMI ||
|
||||||
|
this._targetType === MigrationTargetType.SQLDB) {
|
||||||
if (!!this._model._assessmentResults?.issues.find(value => value.databaseRestoreFails) ||
|
if (!!this._model._assessmentResults?.issues.find(value => value.databaseRestoreFails) ||
|
||||||
!!this._model._assessmentResults?.databaseAssessments.find(d => !!d.issues.find(issue => issue.databaseRestoreFails))) {
|
!!this._model._assessmentResults?.databaseAssessments.find(d => !!d.issues.find(issue => issue.databaseRestoreFails))) {
|
||||||
dialog.message = {
|
dialog.message = {
|
||||||
@@ -192,7 +200,8 @@ export class SqlDatabaseTree {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
this._disposables.push(this._databaseTable.onRowSelected(async (e) => {
|
this._disposables.push(this._databaseTable.onRowSelected(async (e) => {
|
||||||
if (this._targetType === MigrationTargetType.SQLMI) {
|
if (this._targetType === MigrationTargetType.SQLMI ||
|
||||||
|
this._targetType === MigrationTargetType.SQLDB) {
|
||||||
this._activeIssues = this._model._assessmentResults?.databaseAssessments[e.row].issues;
|
this._activeIssues = this._model._assessmentResults?.databaseAssessments[e.row].issues;
|
||||||
} else {
|
} else {
|
||||||
this._activeIssues = [];
|
this._activeIssues = [];
|
||||||
@@ -306,7 +315,8 @@ export class SqlDatabaseTree {
|
|||||||
});
|
});
|
||||||
this._recommendation.value = constants.WARNINGS_DETAILS;
|
this._recommendation.value = constants.WARNINGS_DETAILS;
|
||||||
this._recommendationTitle.value = constants.WARNINGS_COUNT(this._activeIssues?.length);
|
this._recommendationTitle.value = constants.WARNINGS_COUNT(this._activeIssues?.length);
|
||||||
if (this._targetType === MigrationTargetType.SQLMI) {
|
if (this._targetType === MigrationTargetType.SQLMI ||
|
||||||
|
this._targetType === MigrationTargetType.SQLDB) {
|
||||||
await this.refreshResults();
|
await this.refreshResults();
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
@@ -388,42 +398,34 @@ export class SqlDatabaseTree {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private createNoIssuesText(): azdata.FlexContainer {
|
private createNoIssuesText(): azdata.FlexContainer {
|
||||||
let message: azdata.TextComponent;
|
|
||||||
const failedAssessment = this.handleFailedAssessment();
|
const failedAssessment = this.handleFailedAssessment();
|
||||||
if (this._targetType === MigrationTargetType.SQLVM) {
|
|
||||||
message = this._view.modelBuilder.text().withProps({
|
|
||||||
value: failedAssessment
|
|
||||||
? constants.NO_RESULTS_AVAILABLE
|
|
||||||
: constants.NO_ISSUES_FOUND_VM,
|
|
||||||
CSSStyles: {
|
|
||||||
...styles.BODY_CSS
|
|
||||||
}
|
|
||||||
}).component();
|
|
||||||
} else {
|
|
||||||
message = this._view.modelBuilder.text().withProps({
|
|
||||||
value: failedAssessment
|
|
||||||
? constants.NO_RESULTS_AVAILABLE
|
|
||||||
: constants.NO_ISSUES_FOUND_MI,
|
|
||||||
CSSStyles: {
|
|
||||||
...styles.BODY_CSS
|
|
||||||
}
|
|
||||||
}).component();
|
|
||||||
}
|
|
||||||
//TODO: will need to add a SQL DB condition here in the future
|
|
||||||
|
|
||||||
this._noIssuesContainer = this._view.modelBuilder.flexContainer().withItems([message]).withProps({
|
|
||||||
CSSStyles: {
|
const value = failedAssessment
|
||||||
'margin-top': '8px',
|
? constants.NO_RESULTS_AVAILABLE
|
||||||
'display': 'none'
|
: (this._targetType === MigrationTargetType.SQLVM)
|
||||||
}
|
? constants.NO_ISSUES_FOUND_VM
|
||||||
|
: (this._targetType === MigrationTargetType.SQLMI)
|
||||||
|
? constants.NO_ISSUES_FOUND_MI
|
||||||
|
: constants.NO_ISSUES_FOUND_SQLDB;
|
||||||
|
|
||||||
|
const message = this._view.modelBuilder.text()
|
||||||
|
.withProps({
|
||||||
|
value: value,
|
||||||
|
CSSStyles: { ...styles.BODY_CSS }
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
|
this._noIssuesContainer = this._view.modelBuilder.flexContainer()
|
||||||
|
.withItems([message])
|
||||||
|
.withProps({ CSSStyles: { 'margin-top': '8px', 'display': 'none' } })
|
||||||
|
.component();
|
||||||
|
|
||||||
return this._noIssuesContainer;
|
return this._noIssuesContainer;
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleFailedAssessment(): boolean {
|
private handleFailedAssessment(): boolean {
|
||||||
const failedAssessment: boolean = this._model._assessmentResults?.assessmentError !== undefined
|
const failedAssessment: boolean = this._model._assessmentResults?.assessmentError !== undefined
|
||||||
|| (this._model._assessmentResults?.errors?.length || 0) > 0;
|
|| (this._model._assessmentResults?.errors?.length ?? 0) > 0;
|
||||||
if (failedAssessment) {
|
if (failedAssessment) {
|
||||||
this._dialog.message = {
|
this._dialog.message = {
|
||||||
level: azdata.window.MessageLevel.Warning,
|
level: azdata.window.MessageLevel.Warning,
|
||||||
@@ -471,16 +473,12 @@ export class SqlDatabaseTree {
|
|||||||
|
|
||||||
private createAssessmentContainer(): azdata.FlexContainer {
|
private createAssessmentContainer(): azdata.FlexContainer {
|
||||||
const title = this.createAssessmentTitle();
|
const title = this.createAssessmentTitle();
|
||||||
|
|
||||||
const bottomContainer = this.createDescriptionContainer();
|
const bottomContainer = this.createDescriptionContainer();
|
||||||
|
const container = this._view.modelBuilder.flexContainer()
|
||||||
const container = this._view.modelBuilder.flexContainer().withItems([title, bottomContainer]).withLayout({
|
.withItems([title, bottomContainer])
|
||||||
flexFlow: 'column'
|
.withLayout({ flexFlow: 'column' })
|
||||||
}).withProps({
|
.withProps({ CSSStyles: { 'margin-left': '24px' } })
|
||||||
CSSStyles: {
|
.component();
|
||||||
'margin-left': '24px'
|
|
||||||
}
|
|
||||||
}).component();
|
|
||||||
|
|
||||||
return container;
|
return container;
|
||||||
}
|
}
|
||||||
@@ -488,14 +486,10 @@ export class SqlDatabaseTree {
|
|||||||
private createDescriptionContainer(): azdata.FlexContainer {
|
private createDescriptionContainer(): azdata.FlexContainer {
|
||||||
const description = this.createDescription();
|
const description = this.createDescription();
|
||||||
const impactedObjects = this.createImpactedObjectsDescription();
|
const impactedObjects = this.createImpactedObjectsDescription();
|
||||||
|
const container = this._view.modelBuilder.flexContainer()
|
||||||
const container = this._view.modelBuilder.flexContainer().withLayout({
|
.withLayout({ flexFlow: 'row' })
|
||||||
flexFlow: 'row'
|
.withProps({ CSSStyles: { 'height': '100%' } })
|
||||||
}).withProps({
|
.component();
|
||||||
CSSStyles: {
|
|
||||||
'height': '100%'
|
|
||||||
}
|
|
||||||
}).component();
|
|
||||||
container.addItem(description, { flex: '0 0 auto', CSSStyles: { 'width': '200px', 'margin-right': '35px' } });
|
container.addItem(description, { flex: '0 0 auto', CSSStyles: { 'width': '200px', 'margin-right': '35px' } });
|
||||||
container.addItem(impactedObjects, { flex: '0 0 auto', CSSStyles: { 'width': '280px' } });
|
container.addItem(impactedObjects, { flex: '0 0 auto', CSSStyles: { 'width': '280px' } });
|
||||||
|
|
||||||
@@ -541,19 +535,8 @@ export class SqlDatabaseTree {
|
|||||||
rowCssStyles: rowStyle
|
rowCssStyles: rowStyle
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
dataValues: [
|
dataValues: [[{ value: '' }, { value: '' }]],
|
||||||
[
|
CSSStyles: { 'margin-top': '12px' }
|
||||||
{
|
|
||||||
value: ''
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: ''
|
|
||||||
}
|
|
||||||
]
|
|
||||||
],
|
|
||||||
CSSStyles: {
|
|
||||||
'margin-top': '12px'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
).component();
|
).component();
|
||||||
|
|
||||||
@@ -562,7 +545,8 @@ export class SqlDatabaseTree {
|
|||||||
this.refreshImpactedObject(impactedObject);
|
this.refreshImpactedObject(impactedObject);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const objectDetailsTitle = this._view.modelBuilder.text().withProps({
|
const objectDetailsTitle = this._view.modelBuilder.text()
|
||||||
|
.withProps({
|
||||||
value: constants.OBJECT_DETAILS,
|
value: constants.OBJECT_DETAILS,
|
||||||
CSSStyles: {
|
CSSStyles: {
|
||||||
...styles.LIGHT_LABEL_CSS,
|
...styles.LIGHT_LABEL_CSS,
|
||||||
@@ -574,24 +558,34 @@ export class SqlDatabaseTree {
|
|||||||
'margin': '5px 0px 0px 0px',
|
'margin': '5px 0px 0px 0px',
|
||||||
'word-wrap': 'break-word'
|
'word-wrap': 'break-word'
|
||||||
};
|
};
|
||||||
this._objectDetailsType = this._view.modelBuilder.text().withProps({
|
this._objectDetailsType = this._view.modelBuilder.text()
|
||||||
|
.withProps({
|
||||||
value: constants.TYPES_LABEL,
|
value: constants.TYPES_LABEL,
|
||||||
CSSStyles: objectDescriptionStyle
|
CSSStyles: objectDescriptionStyle
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
this._objectDetailsName = this._view.modelBuilder.text().withProps({
|
this._objectDetailsName = this._view.modelBuilder.text()
|
||||||
|
.withProps({
|
||||||
value: constants.NAMES_LABEL,
|
value: constants.NAMES_LABEL,
|
||||||
CSSStyles: objectDescriptionStyle
|
CSSStyles: objectDescriptionStyle
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
this._objectDetailsSample = this._view.modelBuilder.text().withProps({
|
this._objectDetailsSample = this._view.modelBuilder.text()
|
||||||
|
.withProps({
|
||||||
value: '',
|
value: '',
|
||||||
CSSStyles: objectDescriptionStyle
|
CSSStyles: objectDescriptionStyle
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
const container = this._view.modelBuilder.flexContainer().withItems([impactedObjectsTitle, this._impactedObjectsTable, objectDetailsTitle, this._objectDetailsType, this._objectDetailsName, this._objectDetailsSample]).withLayout({
|
const container = this._view.modelBuilder.flexContainer()
|
||||||
flexFlow: 'column'
|
.withItems([
|
||||||
}).component();
|
impactedObjectsTitle,
|
||||||
|
this._impactedObjectsTable,
|
||||||
|
objectDetailsTitle,
|
||||||
|
this._objectDetailsType,
|
||||||
|
this._objectDetailsName,
|
||||||
|
this._objectDetailsSample])
|
||||||
|
.withLayout({ flexFlow: 'column' })
|
||||||
|
.component();
|
||||||
|
|
||||||
return container;
|
return container;
|
||||||
}
|
}
|
||||||
@@ -607,26 +601,32 @@ export class SqlDatabaseTree {
|
|||||||
'width': '200px',
|
'width': '200px',
|
||||||
'word-wrap': 'break-word'
|
'word-wrap': 'break-word'
|
||||||
};
|
};
|
||||||
const descriptionTitle = this._view.modelBuilder.text().withProps({
|
const descriptionTitle = this._view.modelBuilder.text()
|
||||||
|
.withProps({
|
||||||
value: constants.DESCRIPTION,
|
value: constants.DESCRIPTION,
|
||||||
CSSStyles: LABEL_CSS
|
CSSStyles: LABEL_CSS
|
||||||
}).component();
|
}).component();
|
||||||
this._descriptionText = this._view.modelBuilder.text().withProps({
|
this._descriptionText = this._view.modelBuilder.text()
|
||||||
|
.withProps({
|
||||||
CSSStyles: textStyle
|
CSSStyles: textStyle
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
const recommendationTitle = this._view.modelBuilder.text().withProps({
|
const recommendationTitle = this._view.modelBuilder.text()
|
||||||
|
.withProps({
|
||||||
value: constants.RECOMMENDATION,
|
value: constants.RECOMMENDATION,
|
||||||
CSSStyles: LABEL_CSS
|
CSSStyles: LABEL_CSS
|
||||||
}).component();
|
}).component();
|
||||||
this._recommendationText = this._view.modelBuilder.text().withProps({
|
this._recommendationText = this._view.modelBuilder.text()
|
||||||
|
.withProps({
|
||||||
CSSStyles: textStyle
|
CSSStyles: textStyle
|
||||||
}).component();
|
}).component();
|
||||||
const moreInfo = this._view.modelBuilder.text().withProps({
|
const moreInfo = this._view.modelBuilder.text()
|
||||||
|
.withProps({
|
||||||
value: constants.MORE_INFO,
|
value: constants.MORE_INFO,
|
||||||
CSSStyles: LABEL_CSS
|
CSSStyles: LABEL_CSS
|
||||||
}).component();
|
}).component();
|
||||||
this._moreInfo = this._view.modelBuilder.hyperlink().withProps({
|
this._moreInfo = this._view.modelBuilder.hyperlink()
|
||||||
|
.withProps({
|
||||||
label: '',
|
label: '',
|
||||||
url: '',
|
url: '',
|
||||||
CSSStyles: textStyle,
|
CSSStyles: textStyle,
|
||||||
@@ -634,15 +634,22 @@ export class SqlDatabaseTree {
|
|||||||
showLinkIcon: true
|
showLinkIcon: true
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
const container = this._view.modelBuilder.flexContainer().withItems([descriptionTitle, this._descriptionText, recommendationTitle, this._recommendationText, moreInfo, this._moreInfo]).withLayout({
|
const container = this._view.modelBuilder.flexContainer()
|
||||||
flexFlow: 'column'
|
.withItems([descriptionTitle,
|
||||||
}).component();
|
this._descriptionText,
|
||||||
|
recommendationTitle,
|
||||||
|
this._recommendationText,
|
||||||
|
moreInfo,
|
||||||
|
this._moreInfo])
|
||||||
|
.withLayout({ flexFlow: 'column' })
|
||||||
|
.component();
|
||||||
|
|
||||||
return container;
|
return container;
|
||||||
}
|
}
|
||||||
|
|
||||||
private createAssessmentTitle(): azdata.TextComponent {
|
private createAssessmentTitle(): azdata.TextComponent {
|
||||||
this._assessmentTitle = this._view.modelBuilder.text().withProps({
|
this._assessmentTitle = this._view.modelBuilder.text()
|
||||||
|
.withProps({
|
||||||
value: '',
|
value: '',
|
||||||
CSSStyles: {
|
CSSStyles: {
|
||||||
...styles.LABEL_CSS,
|
...styles.LABEL_CSS,
|
||||||
@@ -657,26 +664,28 @@ export class SqlDatabaseTree {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private createTitleComponent(): azdata.TextComponent {
|
private createTitleComponent(): azdata.TextComponent {
|
||||||
const title = this._view.modelBuilder.text().withProps({
|
return this._view.modelBuilder.text()
|
||||||
|
.withProps({
|
||||||
value: constants.TARGET_PLATFORM,
|
value: constants.TARGET_PLATFORM,
|
||||||
CSSStyles: {
|
CSSStyles: {
|
||||||
...styles.BODY_CSS,
|
...styles.BODY_CSS,
|
||||||
'margin': '0 0 4px 0'
|
'margin': '0 0 4px 0'
|
||||||
}
|
}
|
||||||
});
|
}).component();
|
||||||
|
|
||||||
return title.component();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private createPlatformComponent(): azdata.TextComponent {
|
private createPlatformComponent(): azdata.TextComponent {
|
||||||
const impact = this._view.modelBuilder.text().withProps({
|
const target = (this._targetType === MigrationTargetType.SQLVM)
|
||||||
value: (this._targetType === MigrationTargetType.SQLVM) ? constants.SUMMARY_VM_TYPE : constants.SUMMARY_MI_TYPE,
|
? constants.SUMMARY_VM_TYPE
|
||||||
CSSStyles: {
|
: (this._targetType === MigrationTargetType.SQLMI)
|
||||||
...styles.PAGE_SUBTITLE_CSS
|
? constants.SUMMARY_MI_TYPE
|
||||||
}
|
: constants.SUMMARY_SQLDB_TYPE;
|
||||||
});
|
|
||||||
|
|
||||||
return impact.component();
|
return this._view.modelBuilder.text()
|
||||||
|
.withProps({
|
||||||
|
value: target,
|
||||||
|
CSSStyles: { ...styles.PAGE_SUBTITLE_CSS }
|
||||||
|
}).component();
|
||||||
}
|
}
|
||||||
|
|
||||||
private createRecommendationComponent(): azdata.TextComponent {
|
private createRecommendationComponent(): azdata.TextComponent {
|
||||||
@@ -718,7 +727,6 @@ export class SqlDatabaseTree {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private createImpactedObjectsTable(): azdata.FlexContainer {
|
private createImpactedObjectsTable(): azdata.FlexContainer {
|
||||||
|
|
||||||
const headerStyle: azdata.CssStyles = {
|
const headerStyle: azdata.CssStyles = {
|
||||||
'border': 'none',
|
'border': 'none',
|
||||||
'text-align': 'left'
|
'text-align': 'left'
|
||||||
@@ -732,13 +740,11 @@ export class SqlDatabaseTree {
|
|||||||
'overflow': 'hidden',
|
'overflow': 'hidden',
|
||||||
};
|
};
|
||||||
|
|
||||||
this._assessmentResultsTable = this._view.modelBuilder.declarativeTable().withProps(
|
this._assessmentResultsTable = this._view.modelBuilder.declarativeTable()
|
||||||
{
|
.withProps({
|
||||||
enableRowSelection: true,
|
enableRowSelection: true,
|
||||||
width: '200px',
|
width: '200px',
|
||||||
CSSStyles: {
|
CSSStyles: { 'table-layout': 'fixed' },
|
||||||
'table-layout': 'fixed'
|
|
||||||
},
|
|
||||||
columns: [
|
columns: [
|
||||||
{
|
{
|
||||||
displayName: '',
|
displayName: '',
|
||||||
@@ -765,14 +771,14 @@ export class SqlDatabaseTree {
|
|||||||
await this.refreshAssessmentDetails(selectedIssue);
|
await this.refreshAssessmentDetails(selectedIssue);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const container = this._view.modelBuilder.flexContainer().withItems([this._assessmentResultsTable]).withLayout({
|
const container = this._view.modelBuilder.flexContainer()
|
||||||
|
.withItems([this._assessmentResultsTable])
|
||||||
|
.withLayout({
|
||||||
flexFlow: 'column',
|
flexFlow: 'column',
|
||||||
height: '100%'
|
height: '100%'
|
||||||
}).withProps({
|
})
|
||||||
CSSStyles: {
|
.withProps({ CSSStyles: { 'border-right': 'solid 1px' } })
|
||||||
'border-right': 'solid 1px'
|
.component();
|
||||||
}
|
|
||||||
}).component();
|
|
||||||
|
|
||||||
return container;
|
return container;
|
||||||
}
|
}
|
||||||
@@ -788,42 +794,23 @@ export class SqlDatabaseTree {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async refreshResults(): Promise<void> {
|
public async refreshResults(): Promise<void> {
|
||||||
if (this._targetType === MigrationTargetType.SQLMI) {
|
if (this._targetType === MigrationTargetType.SQLMI ||
|
||||||
|
this._targetType === MigrationTargetType.SQLDB) {
|
||||||
if (this._activeIssues?.length === 0) {
|
if (this._activeIssues?.length === 0) {
|
||||||
/// show no issues here
|
/// show no issues here
|
||||||
await this._assessmentsTable.updateCssStyles({
|
await this._assessmentsTable.updateCssStyles({ 'display': 'none', 'border-right': 'none' });
|
||||||
'display': 'none',
|
await this._assessmentContainer.updateCssStyles({ 'display': 'none' });
|
||||||
'border-right': 'none'
|
await this._noIssuesContainer.updateCssStyles({ 'display': 'flex' });
|
||||||
});
|
|
||||||
await this._assessmentContainer.updateCssStyles({
|
|
||||||
'display': 'none'
|
|
||||||
});
|
|
||||||
await this._noIssuesContainer.updateCssStyles({
|
|
||||||
'display': 'flex'
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
await this._assessmentContainer.updateCssStyles({
|
await this._assessmentContainer.updateCssStyles({ 'display': 'flex' });
|
||||||
'display': 'flex'
|
await this._assessmentsTable.updateCssStyles({ 'display': 'flex', 'border-right': 'solid 1px' });
|
||||||
});
|
await this._noIssuesContainer.updateCssStyles({ 'display': 'none' });
|
||||||
await this._assessmentsTable.updateCssStyles({
|
|
||||||
'display': 'flex',
|
|
||||||
'border-right': 'solid 1px'
|
|
||||||
});
|
|
||||||
await this._noIssuesContainer.updateCssStyles({
|
|
||||||
'display': 'none'
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
await this._assessmentsTable.updateCssStyles({
|
await this._assessmentsTable.updateCssStyles({ 'display': 'none', 'border-right': 'none' });
|
||||||
'display': 'none',
|
await this._assessmentContainer.updateCssStyles({ 'display': 'none' });
|
||||||
'border-right': 'none'
|
await this._noIssuesContainer.updateCssStyles({ 'display': 'flex' });
|
||||||
});
|
|
||||||
await this._assessmentContainer.updateCssStyles({
|
|
||||||
'display': 'none'
|
|
||||||
});
|
|
||||||
await this._noIssuesContainer.updateCssStyles({
|
|
||||||
'display': 'flex'
|
|
||||||
});
|
|
||||||
this._recommendationTitle.value = constants.ASSESSMENT_RESULTS;
|
this._recommendationTitle.value = constants.ASSESSMENT_RESULTS;
|
||||||
this._recommendation.value = '';
|
this._recommendation.value = '';
|
||||||
}
|
}
|
||||||
@@ -868,7 +855,8 @@ export class SqlDatabaseTree {
|
|||||||
this._impactedObjects = selectedIssue?.impactedObjects || [];
|
this._impactedObjects = selectedIssue?.impactedObjects || [];
|
||||||
this._recommendationText.value = selectedIssue?.message || constants.NA;
|
this._recommendationText.value = selectedIssue?.message || constants.NA;
|
||||||
|
|
||||||
await this._impactedObjectsTable.setDataValues(this._impactedObjects.map(
|
await this._impactedObjectsTable.setDataValues(
|
||||||
|
this._impactedObjects.map(
|
||||||
(object) => [{ value: object.objectType }, { value: object.name }]));
|
(object) => [{ value: object.objectType }, { value: object.name }]));
|
||||||
|
|
||||||
this._impactedObjectsTable.selectedRow = this._impactedObjects?.length > 0 ? 0 : -1;
|
this._impactedObjectsTable.selectedRow = this._impactedObjects?.length > 0 ? 0 : -1;
|
||||||
@@ -884,12 +872,16 @@ export class SqlDatabaseTree {
|
|||||||
let instanceTableValues: azdata.DeclarativeTableCellValue[][] = [];
|
let instanceTableValues: azdata.DeclarativeTableCellValue[][] = [];
|
||||||
this._databaseTableValues = [];
|
this._databaseTableValues = [];
|
||||||
this._dbNames = this._model._databasesForAssessment;
|
this._dbNames = this._model._databasesForAssessment;
|
||||||
const selectedDbs = (this._targetType === MigrationTargetType.SQLVM) ? this._model._vmDbs : this._model._miDbs;
|
const selectedDbs = (this._targetType === MigrationTargetType.SQLVM)
|
||||||
|
? this._model._vmDbs
|
||||||
|
: (this._targetType === MigrationTargetType.SQLMI)
|
||||||
|
? this._model._miDbs
|
||||||
|
: this._model._sqldbDbs;
|
||||||
|
|
||||||
this._serverName = (await this._model.getSourceConnectionProfile()).serverName;
|
this._serverName = (await this._model.getSourceConnectionProfile()).serverName;
|
||||||
|
|
||||||
if (this._targetType === MigrationTargetType.SQLVM || !this._model._assessmentResults) {
|
if (this._targetType === MigrationTargetType.SQLVM || !this._model._assessmentResults) {
|
||||||
instanceTableValues = [
|
instanceTableValues = [[
|
||||||
[
|
|
||||||
{
|
{
|
||||||
value: this.createIconTextCell(IconPathHelper.sqlServerLogo, this._serverName),
|
value: this.createIconTextCell(IconPathHelper.sqlServerLogo, this._serverName),
|
||||||
style: styleLeft
|
style: styleLeft
|
||||||
@@ -898,11 +890,9 @@ export class SqlDatabaseTree {
|
|||||||
value: '0',
|
value: '0',
|
||||||
style: styleRight
|
style: styleRight
|
||||||
}
|
}
|
||||||
]
|
]];
|
||||||
];
|
|
||||||
this._dbNames.forEach((db) => {
|
this._dbNames.forEach((db) => {
|
||||||
this._databaseTableValues.push(
|
this._databaseTableValues.push([
|
||||||
[
|
|
||||||
{
|
{
|
||||||
value: selectedDbs.includes(db),
|
value: selectedDbs.includes(db),
|
||||||
style: styleLeft
|
style: styleLeft
|
||||||
@@ -915,12 +905,10 @@ export class SqlDatabaseTree {
|
|||||||
value: '0',
|
value: '0',
|
||||||
style: styleRight
|
style: styleRight
|
||||||
}
|
}
|
||||||
]
|
]);
|
||||||
);
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
instanceTableValues = [
|
instanceTableValues = [[
|
||||||
[
|
|
||||||
{
|
{
|
||||||
value: this.createIconTextCell(IconPathHelper.sqlServerLogo, this._serverName),
|
value: this.createIconTextCell(IconPathHelper.sqlServerLogo, this._serverName),
|
||||||
style: styleLeft
|
style: styleLeft
|
||||||
@@ -929,11 +917,10 @@ export class SqlDatabaseTree {
|
|||||||
value: this._model._assessmentResults?.issues?.length,
|
value: this._model._assessmentResults?.issues?.length,
|
||||||
style: styleRight
|
style: styleRight
|
||||||
}
|
}
|
||||||
]
|
]];
|
||||||
];
|
this._model._assessmentResults?.databaseAssessments
|
||||||
this._model._assessmentResults?.databaseAssessments.sort((db1, db2) => {
|
.sort((db1, db2) => db2.issues?.length - db1.issues?.length);
|
||||||
return db2.issues?.length - db1.issues?.length;
|
|
||||||
});
|
|
||||||
// Reset the dbName list so that it is in sync with the table
|
// Reset the dbName list so that it is in sync with the table
|
||||||
this._dbNames = this._model._assessmentResults?.databaseAssessments.map(da => da.name);
|
this._dbNames = this._model._assessmentResults?.databaseAssessments.map(da => da.name);
|
||||||
this._model._assessmentResults?.databaseAssessments.forEach((db) => {
|
this._model._assessmentResults?.databaseAssessments.forEach((db) => {
|
||||||
@@ -941,8 +928,7 @@ export class SqlDatabaseTree {
|
|||||||
if (db.issues.find(item => item.databaseRestoreFails)) {
|
if (db.issues.find(item => item.databaseRestoreFails)) {
|
||||||
selectable = false;
|
selectable = false;
|
||||||
}
|
}
|
||||||
this._databaseTableValues.push(
|
this._databaseTableValues.push([
|
||||||
[
|
|
||||||
{
|
{
|
||||||
value: selectedDbs.includes(db.name),
|
value: selectedDbs.includes(db.name),
|
||||||
style: styleLeft,
|
style: styleLeft,
|
||||||
@@ -956,8 +942,7 @@ export class SqlDatabaseTree {
|
|||||||
value: db.issues?.length,
|
value: db.issues?.length,
|
||||||
style: styleRight
|
style: styleRight
|
||||||
}
|
}
|
||||||
]
|
]);
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
await this._instanceTable.setDataValues(instanceTableValues);
|
await this._instanceTable.setDataValues(instanceTableValues);
|
||||||
@@ -973,47 +958,7 @@ export class SqlDatabaseTree {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// undo when bug #16445 is fixed
|
|
||||||
private createIconTextCell(icon: IconPath, text: string): string {
|
private createIconTextCell(icon: IconPath, text: string): string {
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
// private createIconTextCell(icon: IconPath, text: string): azdata.FlexContainer {
|
|
||||||
// const cellContainer = this._view.modelBuilder.flexContainer().withProps({
|
|
||||||
// CSSStyles: {
|
|
||||||
// 'justify-content': 'left'
|
|
||||||
// }
|
|
||||||
// }).component();
|
|
||||||
|
|
||||||
// const iconComponent = this._view.modelBuilder.image().withProps({
|
|
||||||
// iconPath: icon,
|
|
||||||
// iconWidth: '16px',
|
|
||||||
// iconHeight: '16px',
|
|
||||||
// width: '20px',
|
|
||||||
// height: '20px'
|
|
||||||
// }).component();
|
|
||||||
// cellContainer.addItem(iconComponent, {
|
|
||||||
// flex: '0',
|
|
||||||
// CSSStyles: {
|
|
||||||
// 'width': '32px'
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
|
|
||||||
// const textComponent = this._view.modelBuilder.text().withProps({
|
|
||||||
// value: text,
|
|
||||||
// title: text,
|
|
||||||
// CSSStyles: {
|
|
||||||
// 'margin': '0px',
|
|
||||||
// 'width': '100%',
|
|
||||||
// }
|
|
||||||
// }).component();
|
|
||||||
|
|
||||||
// cellContainer.addItem(textComponent, {
|
|
||||||
// CSSStyles: {
|
|
||||||
// 'width': 'auto'
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
|
|
||||||
// return cellContainer;
|
|
||||||
// }
|
|
||||||
// undo when bug #16445 is fixed
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -390,8 +390,12 @@ export class CreateSqlMigrationServiceDialog {
|
|||||||
private async populateResourceGroups(): Promise<void> {
|
private async populateResourceGroups(): Promise<void> {
|
||||||
this.migrationServiceResourceGroupDropdown.loading = true;
|
this.migrationServiceResourceGroupDropdown.loading = true;
|
||||||
try {
|
try {
|
||||||
this._resourceGroups = await utils.getAllResourceGroups(this._model._azureAccount, this._model._targetSubscription);
|
this._resourceGroups = await utils.getAllResourceGroups(
|
||||||
this.migrationServiceResourceGroupDropdown.values = await utils.getAzureResourceGroupsDropdownValues(this._resourceGroups);
|
this._model._azureAccount,
|
||||||
|
this._model._targetSubscription);
|
||||||
|
this.migrationServiceResourceGroupDropdown.values = utils.getResourceDropdownValues(
|
||||||
|
this._resourceGroups,
|
||||||
|
constants.RESOURCE_GROUP_NOT_FOUND);
|
||||||
|
|
||||||
const selectedResourceGroupValue = this.migrationServiceResourceGroupDropdown.values.find(v => v.name.toLowerCase() === this._resourceGroupPreset.toLowerCase());
|
const selectedResourceGroupValue = this.migrationServiceResourceGroupDropdown.values.find(v => v.name.toLowerCase() === this._resourceGroupPreset.toLowerCase());
|
||||||
this.migrationServiceResourceGroupDropdown.value = (selectedResourceGroupValue) ? selectedResourceGroupValue : this.migrationServiceResourceGroupDropdown.values[0];
|
this.migrationServiceResourceGroupDropdown.value = (selectedResourceGroupValue) ? selectedResourceGroupValue : this.migrationServiceResourceGroupDropdown.values[0];
|
||||||
|
|||||||
@@ -156,9 +156,10 @@ export class ConfirmCutoverDialog {
|
|||||||
height: 20,
|
height: 20,
|
||||||
label: constants.REFRESH,
|
label: constants.REFRESH,
|
||||||
}).component();
|
}).component();
|
||||||
this._disposables.push(refreshButton.onDidClick(async e => {
|
this._disposables.push(
|
||||||
refreshLoader.loading = true;
|
refreshButton.onDidClick(async e => {
|
||||||
try {
|
try {
|
||||||
|
refreshLoader.loading = true;
|
||||||
await this.migrationCutoverModel.fetchStatus();
|
await this.migrationCutoverModel.fetchStatus();
|
||||||
containerHeading.value = constants.PENDING_BACKUPS(this.migrationCutoverModel.getPendingLogBackupsCount() ?? 0);
|
containerHeading.value = constants.PENDING_BACKUPS(this.migrationCutoverModel.getPendingLogBackupsCount() ?? 0);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -232,9 +233,10 @@ export class ConfirmCutoverDialog {
|
|||||||
|
|
||||||
headingRow.addItem(containerHeading, { flex: '0' });
|
headingRow.addItem(containerHeading, { flex: '0' });
|
||||||
|
|
||||||
this._disposables.push(refreshButton.onDidClick(async e => {
|
this._disposables.push(
|
||||||
refreshLoader.loading = true;
|
refreshButton.onDidClick(async e => {
|
||||||
try {
|
try {
|
||||||
|
refreshLoader.loading = true;
|
||||||
await this.migrationCutoverModel.fetchStatus();
|
await this.migrationCutoverModel.fetchStatus();
|
||||||
containerHeading.label = constants.PENDING_BACKUPS(this.migrationCutoverModel.getPendingLogBackupsCount() ?? 0);
|
containerHeading.label = constants.PENDING_BACKUPS(this.migrationCutoverModel.getPendingLogBackupsCount() ?? 0);
|
||||||
lastScanCompleted.value = constants.LAST_SCAN_COMPLETED(get12HourTime(new Date()));
|
lastScanCompleted.value = constants.LAST_SCAN_COMPLETED(get12HourTime(new Date()));
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import { DatabaseMigration, startMigrationCutover, stopMigration, BackupFileInfo, getResourceGroupFromId, getMigrationDetails, getMigrationTargetName } from '../../api/azure';
|
import { DatabaseMigration, startMigrationCutover, stopMigration, BackupFileInfo, getResourceGroupFromId, getMigrationDetails, getMigrationTargetName } from '../../api/azure';
|
||||||
import { BackupFileInfoStatus, MigrationServiceContext } from '../../models/migrationLocalStorage';
|
import { MigrationServiceContext } from '../../models/migrationLocalStorage';
|
||||||
import { logError, sendSqlMigrationActionEvent, TelemetryAction, TelemetryViews } from '../../telemtery';
|
import { logError, sendSqlMigrationActionEvent, TelemetryAction, TelemetryViews } from '../../telemtery';
|
||||||
import * as constants from '../../constants/strings';
|
import * as constants from '../../constants/strings';
|
||||||
import { getMigrationTargetType, getMigrationMode, isBlobMigration } from '../../constants/helper';
|
import { getMigrationTargetType, getMigrationMode, isBlobMigration } from '../../constants/helper';
|
||||||
@@ -110,7 +110,7 @@ export class MigrationCutoverDialogModel {
|
|||||||
const files: BackupFileInfo[] = [];
|
const files: BackupFileInfo[] = [];
|
||||||
this.migration.properties.migrationStatusDetails?.activeBackupSets?.forEach(abs => {
|
this.migration.properties.migrationStatusDetails?.activeBackupSets?.forEach(abs => {
|
||||||
abs.listOfBackupFiles.forEach(f => {
|
abs.listOfBackupFiles.forEach(f => {
|
||||||
if (f.status !== BackupFileInfoStatus.Restored) {
|
if (f.status !== constants.BackupFileInfoStatus.Restored) {
|
||||||
files.push(f);
|
files.push(f);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { MigrationServiceContext } from '../../models/migrationLocalStorage';
|
|||||||
import { WizardController } from '../../wizard/wizardController';
|
import { WizardController } from '../../wizard/wizardController';
|
||||||
import { getMigrationModeEnum, getMigrationTargetTypeEnum } from '../../constants/helper';
|
import { getMigrationModeEnum, getMigrationTargetTypeEnum } from '../../constants/helper';
|
||||||
import * as constants from '../../constants/strings';
|
import * as constants from '../../constants/strings';
|
||||||
|
import { ServiceContextChangeEvent } from '../../dashboard/tabBase';
|
||||||
|
|
||||||
export class RetryMigrationDialog {
|
export class RetryMigrationDialog {
|
||||||
|
|
||||||
@@ -20,15 +21,20 @@ export class RetryMigrationDialog {
|
|||||||
private readonly _context: vscode.ExtensionContext,
|
private readonly _context: vscode.ExtensionContext,
|
||||||
private readonly _serviceContext: MigrationServiceContext,
|
private readonly _serviceContext: MigrationServiceContext,
|
||||||
private readonly _migration: DatabaseMigration,
|
private readonly _migration: DatabaseMigration,
|
||||||
private readonly _onClosedCallback: () => Promise<void>) {
|
private readonly _serviceContextChangedEvent: vscode.EventEmitter<ServiceContextChangeEvent>) {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async createMigrationStateModel(serviceContext: MigrationServiceContext, migration: DatabaseMigration, connectionId: string, serverName: string, api: mssql.IExtension, location: azureResource.AzureLocation): Promise<MigrationStateModel> {
|
private async createMigrationStateModel(
|
||||||
let stateModel = new MigrationStateModel(this._context, connectionId, api.sqlMigration);
|
serviceContext: MigrationServiceContext,
|
||||||
|
migration: DatabaseMigration,
|
||||||
|
connectionId: string,
|
||||||
|
serverName: string,
|
||||||
|
api: mssql.IExtension,
|
||||||
|
location: azureResource.AzureLocation): Promise<MigrationStateModel> {
|
||||||
|
|
||||||
|
const stateModel = new MigrationStateModel(this._context, connectionId, api.sqlMigration);
|
||||||
const sourceDatabaseName = migration.properties.sourceDatabaseName;
|
const sourceDatabaseName = migration.properties.sourceDatabaseName;
|
||||||
let savedInfo: SavedInfo;
|
const savedInfo: SavedInfo = {
|
||||||
savedInfo = {
|
|
||||||
closedPage: 0,
|
closedPage: 0,
|
||||||
|
|
||||||
// DatabaseSelector
|
// DatabaseSelector
|
||||||
@@ -142,7 +148,7 @@ export class RetryMigrationDialog {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let activeConnection = await azdata.connection.getCurrentConnection();
|
const activeConnection = await azdata.connection.getCurrentConnection();
|
||||||
let connectionId: string = '';
|
let connectionId: string = '';
|
||||||
let serverName: string = '';
|
let serverName: string = '';
|
||||||
if (!activeConnection) {
|
if (!activeConnection) {
|
||||||
@@ -163,7 +169,7 @@ export class RetryMigrationDialog {
|
|||||||
const wizardController = new WizardController(
|
const wizardController = new WizardController(
|
||||||
this._context,
|
this._context,
|
||||||
stateModel,
|
stateModel,
|
||||||
this._onClosedCallback);
|
this._serviceContextChangedEvent);
|
||||||
await wizardController.openWizard(stateModel.sourceConnectionId);
|
await wizardController.openWizard(stateModel.sourceConnectionId);
|
||||||
} else {
|
} else {
|
||||||
void vscode.window.showInformationMessage(constants.MIGRATION_CANNOT_RETRY);
|
void vscode.window.showInformationMessage(constants.MIGRATION_CANNOT_RETRY);
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import * as constants from '../../constants/strings';
|
|||||||
import * as utils from '../../api/utils';
|
import * as utils from '../../api/utils';
|
||||||
import { SqlMigrationService } from '../../api/azure';
|
import { SqlMigrationService } from '../../api/azure';
|
||||||
import { logError, TelemetryViews } from '../../telemtery';
|
import { logError, TelemetryViews } from '../../telemtery';
|
||||||
|
import { ServiceContextChangeEvent } from '../../dashboard/tabBase';
|
||||||
|
|
||||||
const CONTROL_MARGIN = '20px';
|
const CONTROL_MARGIN = '20px';
|
||||||
const INPUT_COMPONENT_WIDTH = '100%';
|
const INPUT_COMPONENT_WIDTH = '100%';
|
||||||
@@ -56,7 +57,7 @@ export class SelectMigrationServiceDialog {
|
|||||||
private _deleteButton!: azdata.window.Button;
|
private _deleteButton!: azdata.window.Button;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly _onClosedCallback: () => Promise<void>) {
|
private readonly onServiceContextChanged: vscode.EventEmitter<ServiceContextChangeEvent>) {
|
||||||
this._dialog = azdata.window.createModelViewDialog(
|
this._dialog = azdata.window.createModelViewDialog(
|
||||||
constants.MIGRATION_SERVICE_SELECT_TITLE,
|
constants.MIGRATION_SERVICE_SELECT_TITLE,
|
||||||
'SelectMigraitonServiceDialog',
|
'SelectMigraitonServiceDialog',
|
||||||
@@ -85,10 +86,10 @@ export class SelectMigrationServiceDialog {
|
|||||||
'left');
|
'left');
|
||||||
this._disposables.push(
|
this._disposables.push(
|
||||||
this._deleteButton.onClick(async (value) => {
|
this._deleteButton.onClick(async (value) => {
|
||||||
await MigrationLocalStorage.saveMigrationServiceContext({});
|
await MigrationLocalStorage.saveMigrationServiceContext({}, this.onServiceContextChanged);
|
||||||
await this._onClosedCallback();
|
|
||||||
azdata.window.closeDialog(this._dialog);
|
azdata.window.closeDialog(this._dialog);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this._dialog.customButtons = [this._deleteButton];
|
this._dialog.customButtons = [this._deleteButton];
|
||||||
|
|
||||||
azdata.window.openDialog(this._dialog);
|
azdata.window.openDialog(this._dialog);
|
||||||
@@ -262,7 +263,7 @@ export class SelectMigrationServiceDialog {
|
|||||||
? utils.deepClone(selectedLocation)
|
? utils.deepClone(selectedLocation)
|
||||||
: undefined!;
|
: undefined!;
|
||||||
await this._populateResourceGroupDropdown();
|
await this._populateResourceGroupDropdown();
|
||||||
await this._populateMigrationServiceDropdown();
|
this._populateMigrationServiceDropdown();
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -290,7 +291,7 @@ export class SelectMigrationServiceDialog {
|
|||||||
this._serviceContext.resourceGroup = (selectedResourceGroup)
|
this._serviceContext.resourceGroup = (selectedResourceGroup)
|
||||||
? utils.deepClone(selectedResourceGroup)
|
? utils.deepClone(selectedResourceGroup)
|
||||||
: undefined!;
|
: undefined!;
|
||||||
await this._populateMigrationServiceDropdown();
|
this._populateMigrationServiceDropdown();
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -323,10 +324,10 @@ export class SelectMigrationServiceDialog {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
this._disposables.push(
|
this._disposables.push(
|
||||||
this._dialog.okButton.onClick(async (value) => {
|
this._dialog.okButton.onClick(async (value) =>
|
||||||
await MigrationLocalStorage.saveMigrationServiceContext(this._serviceContext);
|
await MigrationLocalStorage.saveMigrationServiceContext(
|
||||||
await this._onClosedCallback();
|
this._serviceContext,
|
||||||
}));
|
this.onServiceContextChanged)));
|
||||||
|
|
||||||
return this._view.modelBuilder.flexContainer()
|
return this._view.modelBuilder.flexContainer()
|
||||||
.withItems([
|
.withItems([
|
||||||
@@ -417,8 +418,14 @@ export class SelectMigrationServiceDialog {
|
|||||||
private async _populateLocationDropdown(): Promise<void> {
|
private async _populateLocationDropdown(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
this._azureLocationDropdown.loading = true;
|
this._azureLocationDropdown.loading = true;
|
||||||
this._sqlMigrationServices = await utils.getAzureSqlMigrationServices(this._serviceContext.azureAccount, this._serviceContext.subscription);
|
this._sqlMigrationServices = await utils.getAzureSqlMigrationServices(
|
||||||
this._locations = await utils.getSqlMigrationServiceLocations(this._serviceContext.azureAccount, this._serviceContext.subscription, this._sqlMigrationServices);
|
this._serviceContext.azureAccount,
|
||||||
|
this._serviceContext.subscription);
|
||||||
|
this._locations = await utils.getResourceLocations(
|
||||||
|
this._serviceContext.azureAccount,
|
||||||
|
this._serviceContext.subscription,
|
||||||
|
this._sqlMigrationServices);
|
||||||
|
|
||||||
this._azureLocationDropdown.values = await utils.getAzureLocationsDropdownValues(this._locations);
|
this._azureLocationDropdown.values = await utils.getAzureLocationsDropdownValues(this._locations);
|
||||||
if (this._azureLocationDropdown.values.length > 0) {
|
if (this._azureLocationDropdown.values.length > 0) {
|
||||||
utils.selectDefaultDropdownValue(
|
utils.selectDefaultDropdownValue(
|
||||||
@@ -439,8 +446,13 @@ export class SelectMigrationServiceDialog {
|
|||||||
private async _populateResourceGroupDropdown(): Promise<void> {
|
private async _populateResourceGroupDropdown(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
this._azureResourceGroupDropdown.loading = true;
|
this._azureResourceGroupDropdown.loading = true;
|
||||||
this._resourceGroups = await utils.getSqlMigrationServiceResourceGroups(this._sqlMigrationServices, this._serviceContext.location!);
|
this._resourceGroups = utils.getServiceResourceGroupsByLocation(
|
||||||
this._azureResourceGroupDropdown.values = await utils.getAzureResourceGroupsDropdownValues(this._resourceGroups);
|
this._sqlMigrationServices,
|
||||||
|
this._serviceContext.location!);
|
||||||
|
this._azureResourceGroupDropdown.values = utils.getResourceDropdownValues(
|
||||||
|
this._resourceGroups,
|
||||||
|
constants.RESOURCE_GROUP_NOT_FOUND);
|
||||||
|
|
||||||
if (this._azureResourceGroupDropdown.values.length > 0) {
|
if (this._azureResourceGroupDropdown.values.length > 0) {
|
||||||
utils.selectDefaultDropdownValue(
|
utils.selectDefaultDropdownValue(
|
||||||
this._azureResourceGroupDropdown,
|
this._azureResourceGroupDropdown,
|
||||||
@@ -457,10 +469,15 @@ export class SelectMigrationServiceDialog {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _populateMigrationServiceDropdown(): Promise<void> {
|
private _populateMigrationServiceDropdown(): void {
|
||||||
try {
|
try {
|
||||||
this._azureServiceDropdown.loading = true;
|
this._azureServiceDropdown.loading = true;
|
||||||
this._azureServiceDropdown.values = await utils.getAzureSqlMigrationServicesDropdownValues(this._sqlMigrationServices, this._serviceContext.location!, this._serviceContext.resourceGroup!);
|
this._azureServiceDropdown.values = utils.getAzureResourceDropdownValues(
|
||||||
|
this._sqlMigrationServices,
|
||||||
|
this._serviceContext.location!,
|
||||||
|
this._serviceContext.resourceGroup?.name,
|
||||||
|
constants.SQL_MIGRATION_SERVICE_NOT_FOUND_ERROR);
|
||||||
|
|
||||||
if (this._azureServiceDropdown.values.length > 0) {
|
if (this._azureServiceDropdown.values.length > 0) {
|
||||||
utils.selectDefaultDropdownValue(
|
utils.selectDefaultDropdownValue(
|
||||||
this._azureServiceDropdown,
|
this._azureServiceDropdown,
|
||||||
|
|||||||
@@ -111,9 +111,11 @@ export class GetAzureRecommendationDialog {
|
|||||||
'margin': '0'
|
'margin': '0'
|
||||||
},
|
},
|
||||||
}).component();
|
}).component();
|
||||||
this._disposables.push(collectDataButton.onDidChangeCheckedState(async (e) => {
|
this._disposables.push(
|
||||||
if (e) {
|
collectDataButton.onDidChangeCheckedState(async checked => {
|
||||||
await this.switchDataSourceContainerFields(PerformanceDataSourceOptions.CollectData);
|
if (checked) {
|
||||||
|
await this.switchDataSourceContainerFields(
|
||||||
|
PerformanceDataSourceOptions.CollectData);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -122,81 +124,72 @@ export class GetAzureRecommendationDialog {
|
|||||||
name: buttonGroup,
|
name: buttonGroup,
|
||||||
label: constants.AZURE_RECOMMENDATION_OPEN_EXISTING,
|
label: constants.AZURE_RECOMMENDATION_OPEN_EXISTING,
|
||||||
checked: this._performanceDataSource === PerformanceDataSourceOptions.OpenExisting,
|
checked: this._performanceDataSource === PerformanceDataSourceOptions.OpenExisting,
|
||||||
CSSStyles: {
|
CSSStyles: { ...styles.BODY_CSS, 'margin': '0 12px' }
|
||||||
...styles.BODY_CSS,
|
|
||||||
'margin': '0 12px',
|
|
||||||
}
|
|
||||||
}).component();
|
}).component();
|
||||||
this._disposables.push(openExistingButton.onDidChangeCheckedState(async (e) => {
|
this._disposables.push(
|
||||||
if (e) {
|
openExistingButton.onDidChangeCheckedState(async checked => {
|
||||||
await this.switchDataSourceContainerFields(PerformanceDataSourceOptions.OpenExisting);
|
if (checked) {
|
||||||
|
await this.switchDataSourceContainerFields(
|
||||||
|
PerformanceDataSourceOptions.OpenExisting);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
radioButtonContainer.addItems([
|
radioButtonContainer.addItems([
|
||||||
collectDataButton,
|
collectDataButton,
|
||||||
openExistingButton
|
openExistingButton]);
|
||||||
]);
|
|
||||||
|
|
||||||
this._collectDataContainer = this.createCollectDataContainer(_view);
|
this._collectDataContainer = this.createCollectDataContainer(_view);
|
||||||
this._openExistingContainer = this.createOpenExistingContainer(_view);
|
this._openExistingContainer = this.createOpenExistingContainer(_view);
|
||||||
|
|
||||||
const container = _view.modelBuilder.flexContainer().withLayout({
|
const container = _view.modelBuilder.flexContainer()
|
||||||
flexFlow: 'column'
|
.withLayout({ flexFlow: 'column' })
|
||||||
}).withItems([
|
.withItems([
|
||||||
chooseMethodText,
|
chooseMethodText,
|
||||||
radioButtonContainer,
|
radioButtonContainer,
|
||||||
this._openExistingContainer,
|
this._openExistingContainer,
|
||||||
this._collectDataContainer,
|
this._collectDataContainer])
|
||||||
]).component();
|
.component();
|
||||||
|
|
||||||
return container;
|
return container;
|
||||||
}
|
}
|
||||||
|
|
||||||
private createCollectDataContainer(_view: azdata.ModelView): azdata.FlexContainer {
|
private createCollectDataContainer(_view: azdata.ModelView): azdata.FlexContainer {
|
||||||
const container = _view.modelBuilder.flexContainer().withProps({
|
const container = _view.modelBuilder.flexContainer()
|
||||||
CSSStyles: {
|
.withProps(
|
||||||
'flex-direction': 'column',
|
{ CSSStyles: { 'flex-direction': 'column', 'display': 'inline' } })
|
||||||
'display': 'inline',
|
.component();
|
||||||
}
|
|
||||||
}).component();
|
|
||||||
|
|
||||||
const instructions = _view.modelBuilder.text().withProps({
|
const instructions = _view.modelBuilder.text()
|
||||||
|
.withProps({
|
||||||
value: constants.AZURE_RECOMMENDATION_COLLECT_DATA_FOLDER,
|
value: constants.AZURE_RECOMMENDATION_COLLECT_DATA_FOLDER,
|
||||||
CSSStyles: {
|
CSSStyles: { ...styles.LABEL_CSS, 'margin-bottom': '8px' }
|
||||||
...styles.LABEL_CSS,
|
|
||||||
'margin-bottom': '8px',
|
|
||||||
}
|
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
const selectFolderContainer = _view.modelBuilder.flexContainer().withProps({
|
const selectFolderContainer = _view.modelBuilder.flexContainer()
|
||||||
CSSStyles: {
|
.withProps(
|
||||||
'flex-direction': 'row',
|
{ CSSStyles: { 'flex-direction': 'row', 'align-items': 'center' } })
|
||||||
'align-items': 'center',
|
.component();
|
||||||
}
|
|
||||||
}).component();
|
|
||||||
|
|
||||||
this._collectDataFolderInput = _view.modelBuilder.inputBox().withProps({
|
this._collectDataFolderInput = _view.modelBuilder.inputBox()
|
||||||
|
.withProps({
|
||||||
placeHolder: constants.FOLDER_NAME,
|
placeHolder: constants.FOLDER_NAME,
|
||||||
readOnly: true,
|
readOnly: true,
|
||||||
width: 320,
|
width: 320,
|
||||||
CSSStyles: {
|
CSSStyles: { 'margin-right': '12px' },
|
||||||
'margin-right': '12px'
|
|
||||||
},
|
|
||||||
}).component();
|
}).component();
|
||||||
this._disposables.push(this._collectDataFolderInput.onTextChanged(async (value) => {
|
this._disposables.push(
|
||||||
|
this._collectDataFolderInput.onTextChanged(async (value) => {
|
||||||
if (value) {
|
if (value) {
|
||||||
this.migrationStateModel._skuRecommendationPerformanceLocation = value.trim();
|
this.migrationStateModel._skuRecommendationPerformanceLocation = value.trim();
|
||||||
this.dialog!.okButton.enabled = true;
|
this.dialog!.okButton.enabled = true;
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const browseButton = _view.modelBuilder.button().withProps({
|
const browseButton = _view.modelBuilder.button()
|
||||||
|
.withProps({
|
||||||
label: constants.BROWSE,
|
label: constants.BROWSE,
|
||||||
width: 100,
|
width: 100,
|
||||||
CSSStyles: {
|
CSSStyles: { 'margin': '0' }
|
||||||
'margin': '0'
|
|
||||||
}
|
|
||||||
}).component();
|
}).component();
|
||||||
this._disposables.push(browseButton.onDidClick(async (e) => {
|
this._disposables.push(browseButton.onDidClick(async (e) => {
|
||||||
let folder = await utils.promptUserForFolder();
|
let folder = await utils.promptUserForFolder();
|
||||||
@@ -205,74 +198,61 @@ export class GetAzureRecommendationDialog {
|
|||||||
|
|
||||||
selectFolderContainer.addItems([
|
selectFolderContainer.addItems([
|
||||||
this._collectDataFolderInput,
|
this._collectDataFolderInput,
|
||||||
browseButton,
|
browseButton]);
|
||||||
]);
|
|
||||||
|
|
||||||
container.addItems([
|
container.addItems([
|
||||||
instructions,
|
instructions,
|
||||||
selectFolderContainer,
|
selectFolderContainer]);
|
||||||
]);
|
|
||||||
return container;
|
return container;
|
||||||
}
|
}
|
||||||
|
|
||||||
private createOpenExistingContainer(_view: azdata.ModelView): azdata.FlexContainer {
|
private createOpenExistingContainer(_view: azdata.ModelView): azdata.FlexContainer {
|
||||||
const container = _view.modelBuilder.flexContainer().withProps({
|
const container = _view.modelBuilder.flexContainer()
|
||||||
CSSStyles: {
|
.withProps(
|
||||||
'flex-direction': 'column',
|
{ CSSStyles: { 'flex-direction': 'column', 'display': 'none', } })
|
||||||
'display': 'none',
|
.component();
|
||||||
}
|
|
||||||
}).component();
|
|
||||||
|
|
||||||
const instructions = _view.modelBuilder.text().withProps({
|
const instructions = _view.modelBuilder.text()
|
||||||
|
.withProps({
|
||||||
value: constants.AZURE_RECOMMENDATION_OPEN_EXISTING_FOLDER,
|
value: constants.AZURE_RECOMMENDATION_OPEN_EXISTING_FOLDER,
|
||||||
CSSStyles: {
|
CSSStyles: { ...styles.LABEL_CSS, 'margin-bottom': '8px' }
|
||||||
...styles.LABEL_CSS,
|
|
||||||
'margin-bottom': '8px',
|
|
||||||
}
|
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
const selectFolderContainer = _view.modelBuilder.flexContainer().withProps({
|
const selectFolderContainer = _view.modelBuilder.flexContainer()
|
||||||
CSSStyles: {
|
.withProps(
|
||||||
'flex-direction': 'row',
|
{ CSSStyles: { 'flex-direction': 'row', 'align-items': 'center' } })
|
||||||
'align-items': 'center',
|
.component();
|
||||||
}
|
|
||||||
}).component();
|
|
||||||
|
|
||||||
this._openExistingFolderInput = _view.modelBuilder.inputBox().withProps({
|
this._openExistingFolderInput = _view.modelBuilder.inputBox().withProps({
|
||||||
placeHolder: constants.FOLDER_NAME,
|
placeHolder: constants.FOLDER_NAME,
|
||||||
readOnly: true,
|
readOnly: true,
|
||||||
width: 320,
|
width: 320,
|
||||||
CSSStyles: {
|
CSSStyles: { 'margin-right': '12px' },
|
||||||
'margin-right': '12px'
|
|
||||||
},
|
|
||||||
}).component();
|
}).component();
|
||||||
this._disposables.push(this._openExistingFolderInput.onTextChanged(async (value) => {
|
this._disposables.push(
|
||||||
|
this._openExistingFolderInput.onTextChanged(async (value) => {
|
||||||
if (value) {
|
if (value) {
|
||||||
this.migrationStateModel._skuRecommendationPerformanceLocation = value.trim();
|
this.migrationStateModel._skuRecommendationPerformanceLocation = value.trim();
|
||||||
this.dialog!.okButton.enabled = true;
|
this.dialog!.okButton.enabled = true;
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const openButton = _view.modelBuilder.button().withProps({
|
const openButton = _view.modelBuilder.button()
|
||||||
|
.withProps({
|
||||||
label: constants.OPEN,
|
label: constants.OPEN,
|
||||||
width: 100,
|
width: 100,
|
||||||
CSSStyles: {
|
CSSStyles: { 'margin': '0' }
|
||||||
'margin': '0'
|
|
||||||
}
|
|
||||||
}).component();
|
}).component();
|
||||||
this._disposables.push(openButton.onDidClick(async (e) => {
|
this._disposables.push(
|
||||||
let folder = await utils.promptUserForFolder();
|
openButton.onDidClick(
|
||||||
this._openExistingFolderInput.value = folder;
|
async (e) => this._openExistingFolderInput.value = await utils.promptUserForFolder()));
|
||||||
}));
|
|
||||||
|
|
||||||
selectFolderContainer.addItems([
|
selectFolderContainer.addItems([
|
||||||
this._openExistingFolderInput,
|
this._openExistingFolderInput,
|
||||||
openButton,
|
openButton]);
|
||||||
]);
|
|
||||||
container.addItems([
|
container.addItems([
|
||||||
instructions,
|
instructions,
|
||||||
selectFolderContainer,
|
selectFolderContainer]);
|
||||||
]);
|
|
||||||
return container;
|
return container;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,53 +261,56 @@ export class GetAzureRecommendationDialog {
|
|||||||
|
|
||||||
let okButtonEnabled = false;
|
let okButtonEnabled = false;
|
||||||
switch (containerType) {
|
switch (containerType) {
|
||||||
case PerformanceDataSourceOptions.CollectData: {
|
case PerformanceDataSourceOptions.CollectData:
|
||||||
await this._collectDataContainer.updateCssStyles({ 'display': 'inline' });
|
await utils.updateControlDisplay(this._collectDataContainer, true);
|
||||||
await this._openExistingContainer.updateCssStyles({ 'display': 'none' });
|
await utils.updateControlDisplay(this._openExistingContainer, false);
|
||||||
|
|
||||||
if (this._collectDataFolderInput.value) {
|
if (this._collectDataFolderInput.value) {
|
||||||
okButtonEnabled = true;
|
okButtonEnabled = true;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
case PerformanceDataSourceOptions.OpenExisting:
|
||||||
case PerformanceDataSourceOptions.OpenExisting: {
|
await utils.updateControlDisplay(this._collectDataContainer, false);
|
||||||
await this._collectDataContainer.updateCssStyles({ 'display': 'none' });
|
await utils.updateControlDisplay(this._openExistingContainer, true);
|
||||||
await this._openExistingContainer.updateCssStyles({ 'display': 'inline' });
|
|
||||||
|
|
||||||
if (this._openExistingFolderInput.value) {
|
if (this._openExistingFolderInput.value) {
|
||||||
okButtonEnabled = true;
|
okButtonEnabled = true;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
this.dialog!.okButton.enabled = okButtonEnabled;
|
this.dialog!.okButton.enabled = okButtonEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async openDialog(dialogName?: string) {
|
public async openDialog(dialogName?: string) {
|
||||||
if (!this._isOpen) {
|
if (!this._isOpen) {
|
||||||
this._isOpen = true;
|
this._isOpen = true;
|
||||||
this.dialog = azdata.window.createModelViewDialog(constants.GET_AZURE_RECOMMENDATION, 'GetAzureRecommendationsDialog', 'narrow');
|
this.dialog = azdata.window.createModelViewDialog(
|
||||||
|
constants.GET_AZURE_RECOMMENDATION,
|
||||||
|
'GetAzureRecommendationsDialog',
|
||||||
|
'narrow');
|
||||||
|
|
||||||
this.dialog.okButton.label = GetAzureRecommendationDialog.StartButtonText;
|
this.dialog.okButton.label = GetAzureRecommendationDialog.StartButtonText;
|
||||||
this._disposables.push(this.dialog.okButton.onClick(async () => await this.execute()));
|
this._disposables.push(
|
||||||
this._disposables.push(this.dialog.cancelButton.onClick(() => this._isOpen = false));
|
this.dialog.okButton.onClick(
|
||||||
|
async () => await this.execute()));
|
||||||
|
|
||||||
const dialogSetupPromises: Thenable<void>[] = [];
|
this._disposables.push(
|
||||||
dialogSetupPromises.push(this.initializeDialog(this.dialog));
|
this.dialog.cancelButton.onClick(
|
||||||
|
() => this._isOpen = false));
|
||||||
|
|
||||||
|
const promise = this.initializeDialog(this.dialog);
|
||||||
azdata.window.openDialog(this.dialog);
|
azdata.window.openDialog(this.dialog);
|
||||||
await Promise.all(dialogSetupPromises);
|
await promise;
|
||||||
|
|
||||||
// if data source was previously selected, default folder value to previously selected
|
// if data source was previously selected, default folder value to previously selected
|
||||||
switch (this.migrationStateModel._skuRecommendationPerformanceDataSource) {
|
switch (this.migrationStateModel._skuRecommendationPerformanceDataSource) {
|
||||||
case PerformanceDataSourceOptions.CollectData: {
|
case PerformanceDataSourceOptions.CollectData:
|
||||||
this._collectDataFolderInput.value = this.migrationStateModel._skuRecommendationPerformanceLocation;
|
this._collectDataFolderInput.value = this.migrationStateModel._skuRecommendationPerformanceLocation;
|
||||||
break;
|
break;
|
||||||
}
|
case PerformanceDataSourceOptions.OpenExisting:
|
||||||
case PerformanceDataSourceOptions.OpenExisting: {
|
|
||||||
this._openExistingFolderInput.value = this.migrationStateModel._skuRecommendationPerformanceLocation;
|
this._openExistingFolderInput.value = this.migrationStateModel._skuRecommendationPerformanceLocation;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
await this.switchDataSourceContainerFields(this._performanceDataSource);
|
await this.switchDataSourceContainerFields(this._performanceDataSource);
|
||||||
}
|
}
|
||||||
@@ -338,16 +321,14 @@ export class GetAzureRecommendationDialog {
|
|||||||
|
|
||||||
this.migrationStateModel._skuRecommendationPerformanceDataSource = this._performanceDataSource;
|
this.migrationStateModel._skuRecommendationPerformanceDataSource = this._performanceDataSource;
|
||||||
switch (this.migrationStateModel._skuRecommendationPerformanceDataSource) {
|
switch (this.migrationStateModel._skuRecommendationPerformanceDataSource) {
|
||||||
case PerformanceDataSourceOptions.CollectData: {
|
case PerformanceDataSourceOptions.CollectData:
|
||||||
await this.migrationStateModel.startPerfDataCollection(
|
await this.migrationStateModel.startPerfDataCollection(
|
||||||
this.migrationStateModel._skuRecommendationPerformanceLocation,
|
this.migrationStateModel._skuRecommendationPerformanceLocation,
|
||||||
this.migrationStateModel._performanceDataQueryIntervalInSeconds,
|
this.migrationStateModel._performanceDataQueryIntervalInSeconds,
|
||||||
this.migrationStateModel._staticDataQueryIntervalInSeconds,
|
this.migrationStateModel._staticDataQueryIntervalInSeconds,
|
||||||
this.migrationStateModel._numberOfPerformanceDataQueryIterations,
|
this.migrationStateModel._numberOfPerformanceDataQueryIterations,
|
||||||
this.skuRecommendationPage
|
this.skuRecommendationPage);
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
case PerformanceDataSourceOptions.OpenExisting: {
|
case PerformanceDataSourceOptions.OpenExisting: {
|
||||||
const serverName = (await this.migrationStateModel.getSourceConnectionProfile()).serverName;
|
const serverName = (await this.migrationStateModel.getSourceConnectionProfile()).serverName;
|
||||||
const errors: string[] = [];
|
const errors: string[] = [];
|
||||||
|
|||||||
@@ -25,7 +25,10 @@ export class SkuEditParametersDialog {
|
|||||||
private _targetPercentileDropdown!: azdata.DropDownComponent;
|
private _targetPercentileDropdown!: azdata.DropDownComponent;
|
||||||
private _enablePreviewValue!: boolean;
|
private _enablePreviewValue!: boolean;
|
||||||
|
|
||||||
constructor(public skuRecommendationPage: SKURecommendationPage, public migrationStateModel: MigrationStateModel) {
|
constructor(
|
||||||
|
public skuRecommendationPage: SKURecommendationPage,
|
||||||
|
public migrationStateModel: MigrationStateModel) {
|
||||||
|
|
||||||
this._enablePreviewValue = true;
|
this._enablePreviewValue = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,10 +38,10 @@ export class SkuEditParametersDialog {
|
|||||||
try {
|
try {
|
||||||
const flex = this.createContainer(view);
|
const flex = this.createContainer(view);
|
||||||
|
|
||||||
this._disposables.push(view.onClosed(e => {
|
this._disposables.push(
|
||||||
|
view.onClosed(e =>
|
||||||
this._disposables.forEach(
|
this._disposables.forEach(
|
||||||
d => { try { d.dispose(); } catch { } });
|
d => { try { d.dispose(); } catch { } })));
|
||||||
}));
|
|
||||||
|
|
||||||
await view.initializeModel(flex);
|
await view.initializeModel(flex);
|
||||||
resolve();
|
resolve();
|
||||||
@@ -50,38 +53,33 @@ export class SkuEditParametersDialog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private createContainer(_view: azdata.ModelView): azdata.FlexContainer {
|
private createContainer(_view: azdata.ModelView): azdata.FlexContainer {
|
||||||
const container = _view.modelBuilder.flexContainer().withProps({
|
const container = _view.modelBuilder.flexContainer()
|
||||||
CSSStyles: {
|
.withProps(
|
||||||
'margin': '8px 16px',
|
{ CSSStyles: { 'margin': '8px 16px', 'flex-direction': 'column' } })
|
||||||
'flex-direction': 'column',
|
.component();
|
||||||
}
|
|
||||||
}).component();
|
|
||||||
|
|
||||||
const description = _view.modelBuilder.text().withProps({
|
const description = _view.modelBuilder.text()
|
||||||
|
.withProps({
|
||||||
value: constants.EDIT_PARAMETERS_TEXT,
|
value: constants.EDIT_PARAMETERS_TEXT,
|
||||||
CSSStyles: {
|
CSSStyles: { ...styles.BODY_CSS }
|
||||||
...styles.BODY_CSS,
|
})
|
||||||
}
|
.component();
|
||||||
}).component();
|
|
||||||
|
|
||||||
const WIZARD_INPUT_COMPONENT_WIDTH = '300px';
|
const WIZARD_INPUT_COMPONENT_WIDTH = '300px';
|
||||||
const scaleFactorLabel = _view.modelBuilder.text().withProps({
|
const scaleFactorLabel = _view.modelBuilder.text()
|
||||||
|
.withProps({
|
||||||
value: constants.SCALE_FACTOR,
|
value: constants.SCALE_FACTOR,
|
||||||
description: constants.SCALE_FACTOR_TOOLTIP,
|
description: constants.SCALE_FACTOR_TOOLTIP,
|
||||||
width: WIZARD_INPUT_COMPONENT_WIDTH,
|
width: WIZARD_INPUT_COMPONENT_WIDTH,
|
||||||
requiredIndicator: true,
|
requiredIndicator: true,
|
||||||
CSSStyles: {
|
CSSStyles: { ...styles.LABEL_CSS }
|
||||||
...styles.LABEL_CSS
|
|
||||||
}
|
|
||||||
}).component();
|
}).component();
|
||||||
this._scaleFactorInput = _view.modelBuilder.inputBox().withProps({
|
this._scaleFactorInput = _view.modelBuilder.inputBox()
|
||||||
|
.withProps({
|
||||||
required: true,
|
required: true,
|
||||||
validationErrorMessage: constants.INVALID_SCALE_FACTOR,
|
validationErrorMessage: constants.INVALID_SCALE_FACTOR,
|
||||||
width: WIZARD_INPUT_COMPONENT_WIDTH,
|
width: WIZARD_INPUT_COMPONENT_WIDTH,
|
||||||
CSSStyles: {
|
CSSStyles: { 'margin-top': '-1em', 'margin-bottom': '8px' },
|
||||||
'margin-top': '-1em',
|
|
||||||
'margin-bottom': '8px',
|
|
||||||
},
|
|
||||||
}).withValidation(c => {
|
}).withValidation(c => {
|
||||||
if (Number(c.value) && Number(c.value) > 0) {
|
if (Number(c.value) && Number(c.value) > 0) {
|
||||||
return true;
|
return true;
|
||||||
@@ -89,17 +87,16 @@ export class SkuEditParametersDialog {
|
|||||||
return false;
|
return false;
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
const targetPercentileLabel = _view.modelBuilder.text().withProps({
|
const targetPercentileLabel = _view.modelBuilder.text()
|
||||||
|
.withProps({
|
||||||
value: constants.PERCENTAGE_UTILIZATION,
|
value: constants.PERCENTAGE_UTILIZATION,
|
||||||
description: constants.PERCENTAGE_UTILIZATION_TOOLTIP,
|
description: constants.PERCENTAGE_UTILIZATION_TOOLTIP,
|
||||||
width: WIZARD_INPUT_COMPONENT_WIDTH,
|
width: WIZARD_INPUT_COMPONENT_WIDTH,
|
||||||
requiredIndicator: true,
|
requiredIndicator: true,
|
||||||
CSSStyles: {
|
CSSStyles: { ...styles.LABEL_CSS }
|
||||||
...styles.LABEL_CSS,
|
|
||||||
}
|
|
||||||
}).component();
|
}).component();
|
||||||
const createPercentageValues = () => {
|
const createPercentageValues = () => {
|
||||||
let values: azdata.CategoryValue[] = [];
|
const values: azdata.CategoryValue[] = [];
|
||||||
TARGET_PERCENTILE_VALUES.forEach(n => {
|
TARGET_PERCENTILE_VALUES.forEach(n => {
|
||||||
const val = n.toString();
|
const val = n.toString();
|
||||||
values.push({
|
values.push({
|
||||||
@@ -109,7 +106,8 @@ export class SkuEditParametersDialog {
|
|||||||
});
|
});
|
||||||
return values;
|
return values;
|
||||||
};
|
};
|
||||||
this._targetPercentileDropdown = _view.modelBuilder.dropDown().withProps({
|
this._targetPercentileDropdown = _view.modelBuilder.dropDown()
|
||||||
|
.withProps({
|
||||||
values: createPercentageValues(),
|
values: createPercentageValues(),
|
||||||
ariaLabel: constants.PERCENTAGE_UTILIZATION,
|
ariaLabel: constants.PERCENTAGE_UTILIZATION,
|
||||||
width: WIZARD_INPUT_COMPONENT_WIDTH,
|
width: WIZARD_INPUT_COMPONENT_WIDTH,
|
||||||
@@ -122,13 +120,12 @@ export class SkuEditParametersDialog {
|
|||||||
},
|
},
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
const enablePreviewLabel = _view.modelBuilder.text().withProps({
|
const enablePreviewLabel = _view.modelBuilder.text()
|
||||||
|
.withProps({
|
||||||
value: constants.ENABLE_PREVIEW_SKU,
|
value: constants.ENABLE_PREVIEW_SKU,
|
||||||
width: WIZARD_INPUT_COMPONENT_WIDTH,
|
width: WIZARD_INPUT_COMPONENT_WIDTH,
|
||||||
requiredIndicator: true,
|
requiredIndicator: true,
|
||||||
CSSStyles: {
|
CSSStyles: { ...styles.LABEL_CSS, }
|
||||||
...styles.LABEL_CSS,
|
|
||||||
}
|
|
||||||
}).component();
|
}).component();
|
||||||
const buttonGroup = 'enablePreviewSKUs';
|
const buttonGroup = 'enablePreviewSKUs';
|
||||||
const enablePreviewRadioButtonContainer = _view.modelBuilder.flexContainer()
|
const enablePreviewRadioButtonContainer = _view.modelBuilder.flexContainer()
|
||||||
@@ -151,8 +148,9 @@ export class SkuEditParametersDialog {
|
|||||||
'margin': '0'
|
'margin': '0'
|
||||||
},
|
},
|
||||||
}).component();
|
}).component();
|
||||||
this._disposables.push(enablePreviewButton.onDidChangeCheckedState(async (e) => {
|
this._disposables.push(
|
||||||
if (e) {
|
enablePreviewButton.onDidChangeCheckedState(async checked => {
|
||||||
|
if (checked) {
|
||||||
this._enablePreviewValue = true;
|
this._enablePreviewValue = true;
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
@@ -167,23 +165,21 @@ export class SkuEditParametersDialog {
|
|||||||
'margin': '0 12px',
|
'margin': '0 12px',
|
||||||
}
|
}
|
||||||
}).component();
|
}).component();
|
||||||
this._disposables.push(disablePreviewButton.onDidChangeCheckedState(async (e) => {
|
this._disposables.push(
|
||||||
if (e) {
|
disablePreviewButton.onDidChangeCheckedState(checked => {
|
||||||
|
if (checked) {
|
||||||
this._enablePreviewValue = false;
|
this._enablePreviewValue = false;
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
enablePreviewRadioButtonContainer.addItems([
|
enablePreviewRadioButtonContainer.addItems([
|
||||||
enablePreviewButton,
|
enablePreviewButton,
|
||||||
disablePreviewButton
|
disablePreviewButton]);
|
||||||
]);
|
|
||||||
|
|
||||||
const enablePreviewInfoBox = _view.modelBuilder.infoBox()
|
const enablePreviewInfoBox = _view.modelBuilder.infoBox()
|
||||||
.withProps({
|
.withProps({
|
||||||
text: constants.ENABLE_PREVIEW_SKU_INFO,
|
text: constants.ENABLE_PREVIEW_SKU_INFO,
|
||||||
style: 'information',
|
style: 'information',
|
||||||
CSSStyles: {
|
CSSStyles: { ...styles.BODY_CSS, }
|
||||||
...styles.BODY_CSS,
|
|
||||||
}
|
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
container.addItems([
|
container.addItems([
|
||||||
@@ -202,12 +198,19 @@ export class SkuEditParametersDialog {
|
|||||||
public async openDialog(dialogName?: string) {
|
public async openDialog(dialogName?: string) {
|
||||||
if (!this._isOpen) {
|
if (!this._isOpen) {
|
||||||
this._isOpen = true;
|
this._isOpen = true;
|
||||||
this.dialog = azdata.window.createModelViewDialog(constants.EDIT_RECOMMENDATION_PARAMETERS, 'SkuEditParametersDialog', 'narrow');
|
this.dialog = azdata.window.createModelViewDialog(
|
||||||
|
constants.EDIT_RECOMMENDATION_PARAMETERS,
|
||||||
|
'SkuEditParametersDialog',
|
||||||
|
'narrow');
|
||||||
|
|
||||||
this.dialog.okButton.label = SkuEditParametersDialog.UpdateButtonText;
|
this.dialog.okButton.label = SkuEditParametersDialog.UpdateButtonText;
|
||||||
this._disposables.push(this.dialog.okButton.onClick(async () => await this.execute()));
|
this._disposables.push(
|
||||||
|
this.dialog.okButton.onClick(
|
||||||
|
async () => await this.execute()));
|
||||||
|
|
||||||
this._disposables.push(this.dialog.cancelButton.onClick(() => this._isOpen = false));
|
this._disposables.push(
|
||||||
|
this.dialog.cancelButton.onClick(
|
||||||
|
() => this._isOpen = false));
|
||||||
|
|
||||||
const dialogSetupPromises: Thenable<void>[] = [];
|
const dialogSetupPromises: Thenable<void>[] = [];
|
||||||
dialogSetupPromises.push(this.initializeDialog(this.dialog));
|
dialogSetupPromises.push(this.initializeDialog(this.dialog));
|
||||||
|
|||||||
@@ -34,15 +34,13 @@ export class SkuRecommendationResultsDialog {
|
|||||||
constructor(public model: MigrationStateModel, public _targetType: MigrationTargetType) {
|
constructor(public model: MigrationStateModel, public _targetType: MigrationTargetType) {
|
||||||
switch (this._targetType) {
|
switch (this._targetType) {
|
||||||
case MigrationTargetType.SQLMI:
|
case MigrationTargetType.SQLMI:
|
||||||
this.targetName = constants.AZURE_SQL_DATABASE_MANAGED_INSTANCE;
|
this.targetName = constants.SKU_RECOMMENDATION_MI_CARD_TEXT;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MigrationTargetType.SQLVM:
|
case MigrationTargetType.SQLVM:
|
||||||
this.targetName = constants.AZURE_SQL_DATABASE_VIRTUAL_MACHINE;
|
this.targetName = constants.SKU_RECOMMENDATION_VM_CARD_TEXT;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MigrationTargetType.SQLDB:
|
case MigrationTargetType.SQLDB:
|
||||||
this.targetName = constants.AZURE_SQL_DATABASE;
|
this.targetName = constants.SKU_RECOMMENDATION_SQLDB_CARD_TEXT;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,7 +77,9 @@ export class SkuRecommendationResultsDialog {
|
|||||||
|
|
||||||
this.targetRecommendations?.forEach((recommendation, index) => {
|
this.targetRecommendations?.forEach((recommendation, index) => {
|
||||||
if (index > 0) {
|
if (index > 0) {
|
||||||
const separator = _view.modelBuilder.separator().withProps({ width: 750 }).component();
|
const separator = _view.modelBuilder.separator()
|
||||||
|
.withProps({ width: 750 })
|
||||||
|
.component();
|
||||||
container.addItem(separator);
|
container.addItem(separator);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,7 +101,9 @@ export class SkuRecommendationResultsDialog {
|
|||||||
recommendation = <mssql.IaaSSkuRecommendationResultItem>recommendationItem;
|
recommendation = <mssql.IaaSSkuRecommendationResultItem>recommendationItem;
|
||||||
|
|
||||||
if (recommendation.targetSku) {
|
if (recommendation.targetSku) {
|
||||||
configuration = constants.VM_CONFIGURATION(recommendation.targetSku.virtualMachineSize!.azureSkuName, recommendation.targetSku.virtualMachineSize!.vCPUsAvailable);
|
configuration = constants.VM_CONFIGURATION(
|
||||||
|
recommendation.targetSku.virtualMachineSize!.azureSkuName,
|
||||||
|
recommendation.targetSku.virtualMachineSize!.vCPUsAvailable);
|
||||||
|
|
||||||
storageSection = this.createSqlVmTargetStorageSection(_view, recommendation);
|
storageSection = this.createSqlVmTargetStorageSection(_view, recommendation);
|
||||||
}
|
}
|
||||||
@@ -123,31 +125,31 @@ export class SkuRecommendationResultsDialog {
|
|||||||
: constants.PREMIUM_SERIES_MEMORY_OPTIMIZED;
|
: constants.PREMIUM_SERIES_MEMORY_OPTIMIZED;
|
||||||
|
|
||||||
configuration = this._targetType === MigrationTargetType.SQLDB
|
configuration = this._targetType === MigrationTargetType.SQLDB
|
||||||
? constants.DB_CONFIGURATION(serviceTier, recommendation.targetSku.computeSize!)
|
? constants.SQLDB_CONFIGURATION(serviceTier, recommendation.targetSku.computeSize!)
|
||||||
: constants.MI_CONFIGURATION(hardwareType, serviceTier, recommendation.targetSku.computeSize!);
|
: constants.MI_CONFIGURATION(hardwareType, serviceTier, recommendation.targetSku.computeSize!);
|
||||||
|
|
||||||
const storageLabel = _view.modelBuilder.text().withProps({
|
const storageLabel = _view.modelBuilder.text()
|
||||||
|
.withProps({
|
||||||
value: constants.STORAGE_HEADER,
|
value: constants.STORAGE_HEADER,
|
||||||
CSSStyles: {
|
CSSStyles: {
|
||||||
...styles.LABEL_CSS,
|
...styles.LABEL_CSS,
|
||||||
'margin': '12px 0 0',
|
'margin': '12px 0 0',
|
||||||
}
|
}
|
||||||
}).component();
|
}).component();
|
||||||
const storageValue = _view.modelBuilder.text().withProps({
|
const storageValue = _view.modelBuilder.text()
|
||||||
|
.withProps({
|
||||||
value: constants.STORAGE_GB(recommendation.targetSku.storageMaxSizeInMb! / 1024),
|
value: constants.STORAGE_GB(recommendation.targetSku.storageMaxSizeInMb! / 1024),
|
||||||
CSSStyles: {
|
CSSStyles: { ...styles.BODY_CSS, }
|
||||||
...styles.BODY_CSS,
|
|
||||||
}
|
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
storageSection.addItems([
|
storageSection.addItems([
|
||||||
storageLabel,
|
storageLabel,
|
||||||
storageValue,
|
storageValue]);
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
const recommendationContainer = _view.modelBuilder.flexContainer().withProps({
|
const recommendationContainer = _view.modelBuilder.flexContainer()
|
||||||
|
.withProps({
|
||||||
CSSStyles: {
|
CSSStyles: {
|
||||||
'margin-bottom': '20px',
|
'margin-bottom': '20px',
|
||||||
'flex-direction': 'column',
|
'flex-direction': 'column',
|
||||||
@@ -155,52 +157,41 @@ export class SkuRecommendationResultsDialog {
|
|||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
if (this._targetType === MigrationTargetType.SQLDB) {
|
if (this._targetType === MigrationTargetType.SQLDB) {
|
||||||
const databaseNameLabel = _view.modelBuilder.text().withProps({
|
const databaseNameLabel = _view.modelBuilder.text()
|
||||||
|
.withProps({
|
||||||
value: recommendation.databaseName!,
|
value: recommendation.databaseName!,
|
||||||
CSSStyles: {
|
CSSStyles: { ...styles.SECTION_HEADER_CSS, }
|
||||||
...styles.SECTION_HEADER_CSS,
|
|
||||||
}
|
|
||||||
}).component();
|
}).component();
|
||||||
recommendationContainer.addItem(databaseNameLabel);
|
recommendationContainer.addItem(databaseNameLabel);
|
||||||
}
|
}
|
||||||
|
|
||||||
const targetDeploymentTypeLabel = _view.modelBuilder.text().withProps({
|
const targetDeploymentTypeLabel = _view.modelBuilder.text()
|
||||||
|
.withProps({
|
||||||
value: constants.TARGET_DEPLOYMENT_TYPE,
|
value: constants.TARGET_DEPLOYMENT_TYPE,
|
||||||
CSSStyles: {
|
CSSStyles: { ...styles.LABEL_CSS, 'margin': '0', }
|
||||||
...styles.LABEL_CSS,
|
|
||||||
'margin': '0',
|
|
||||||
}
|
|
||||||
}).component();
|
}).component();
|
||||||
const targetDeploymentTypeValue = _view.modelBuilder.text().withProps({
|
const targetDeploymentTypeValue = _view.modelBuilder.text()
|
||||||
|
.withProps({
|
||||||
value: this.targetName,
|
value: this.targetName,
|
||||||
CSSStyles: {
|
CSSStyles: { ...styles.BODY_CSS, 'margin': '0', }
|
||||||
...styles.BODY_CSS,
|
|
||||||
'margin': '0',
|
|
||||||
}
|
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
const azureConfigurationLabel = _view.modelBuilder.text().withProps({
|
const azureConfigurationLabel = _view.modelBuilder.text()
|
||||||
|
.withProps({
|
||||||
value: constants.AZURE_CONFIGURATION,
|
value: constants.AZURE_CONFIGURATION,
|
||||||
CSSStyles: {
|
CSSStyles: { ...styles.LABEL_CSS, 'margin': '12px 0 0', }
|
||||||
...styles.LABEL_CSS,
|
|
||||||
'margin': '12px 0 0',
|
|
||||||
}
|
|
||||||
}).component();
|
}).component();
|
||||||
const azureConfigurationValue = _view.modelBuilder.text().withProps({
|
const azureConfigurationValue = _view.modelBuilder.text()
|
||||||
|
.withProps({
|
||||||
value: configuration,
|
value: configuration,
|
||||||
CSSStyles: {
|
CSSStyles: { ...styles.BODY_CSS, 'margin': '0', }
|
||||||
...styles.BODY_CSS,
|
|
||||||
'margin': '0',
|
|
||||||
}
|
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
recommendationContainer.addItems([
|
recommendationContainer.addItems([
|
||||||
targetDeploymentTypeLabel,
|
targetDeploymentTypeLabel,
|
||||||
targetDeploymentTypeValue,
|
targetDeploymentTypeValue,
|
||||||
|
|
||||||
targetDeploymentTypeLabel,
|
targetDeploymentTypeLabel,
|
||||||
targetDeploymentTypeValue,
|
targetDeploymentTypeValue,
|
||||||
|
|
||||||
azureConfigurationLabel,
|
azureConfigurationLabel,
|
||||||
azureConfigurationValue,
|
azureConfigurationValue,
|
||||||
|
|
||||||
@@ -209,23 +200,21 @@ export class SkuRecommendationResultsDialog {
|
|||||||
|
|
||||||
const recommendationsReasonSection = _view.modelBuilder.text().withProps({
|
const recommendationsReasonSection = _view.modelBuilder.text().withProps({
|
||||||
value: constants.RECOMMENDATION_REASON,
|
value: constants.RECOMMENDATION_REASON,
|
||||||
CSSStyles: {
|
CSSStyles: { ...styles.SECTION_HEADER_CSS, 'margin': '12px 0 0' }
|
||||||
...styles.SECTION_HEADER_CSS,
|
|
||||||
'margin': '12px 0 0'
|
|
||||||
}
|
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
const reasonsContainer = _view.modelBuilder.flexContainer().withLayout({
|
const reasonsContainer = _view.modelBuilder.flexContainer()
|
||||||
flexFlow: 'column'
|
.withLayout({ flexFlow: 'column' })
|
||||||
}).component();
|
.component();
|
||||||
const justifications: string[] = recommendation?.positiveJustifications?.concat(recommendation?.negativeJustifications) || [constants.SKU_RECOMMENDATION_NO_RECOMMENDATION_REASON];
|
|
||||||
|
const justifications: string[] = recommendation?.positiveJustifications?.concat(recommendation?.negativeJustifications)
|
||||||
|
|| [constants.SKU_RECOMMENDATION_NO_RECOMMENDATION_REASON];
|
||||||
|
|
||||||
justifications?.forEach(text => {
|
justifications?.forEach(text => {
|
||||||
reasonsContainer.addItem(
|
reasonsContainer.addItem(
|
||||||
_view.modelBuilder.text().withProps({
|
_view.modelBuilder.text().withProps({
|
||||||
value: text,
|
value: text,
|
||||||
CSSStyles: {
|
CSSStyles: { ...styles.BODY_CSS, }
|
||||||
...styles.BODY_CSS,
|
|
||||||
}
|
|
||||||
}).component()
|
}).component()
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -235,25 +224,22 @@ export class SkuRecommendationResultsDialog {
|
|||||||
recommendationContainer.addItems([
|
recommendationContainer.addItems([
|
||||||
recommendationsReasonSection,
|
recommendationsReasonSection,
|
||||||
reasonsContainer,
|
reasonsContainer,
|
||||||
storagePropertiesContainer,
|
storagePropertiesContainer]);
|
||||||
]);
|
|
||||||
|
|
||||||
return recommendationContainer;
|
return recommendationContainer;
|
||||||
}
|
}
|
||||||
|
|
||||||
private createSqlVmTargetStorageSection(_view: azdata.ModelView, recommendation: mssql.IaaSSkuRecommendationResultItem): azdata.FlexContainer {
|
private createSqlVmTargetStorageSection(_view: azdata.ModelView, recommendation: mssql.IaaSSkuRecommendationResultItem): azdata.FlexContainer {
|
||||||
const recommendedTargetStorageSection = _view.modelBuilder.text().withProps({
|
const recommendedTargetStorageSection = _view.modelBuilder.text()
|
||||||
|
.withProps({
|
||||||
value: constants.RECOMMENDED_TARGET_STORAGE_CONFIGURATION,
|
value: constants.RECOMMENDED_TARGET_STORAGE_CONFIGURATION,
|
||||||
CSSStyles: {
|
CSSStyles: { ...styles.SECTION_HEADER_CSS, 'margin-top': '12px' }
|
||||||
...styles.SECTION_HEADER_CSS,
|
|
||||||
'margin-top': '12px'
|
|
||||||
}
|
|
||||||
}).component();
|
}).component();
|
||||||
const recommendedTargetStorageInfo = _view.modelBuilder.text().withProps({
|
|
||||||
|
const recommendedTargetStorageInfo = _view.modelBuilder.text()
|
||||||
|
.withProps({
|
||||||
value: constants.RECOMMENDED_TARGET_STORAGE_CONFIGURATION_INFO,
|
value: constants.RECOMMENDED_TARGET_STORAGE_CONFIGURATION_INFO,
|
||||||
CSSStyles: {
|
CSSStyles: { ...styles.BODY_CSS, }
|
||||||
...styles.BODY_CSS,
|
|
||||||
}
|
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
const headerCssStyle = {
|
const headerCssStyle = {
|
||||||
@@ -333,20 +319,21 @@ export class SkuRecommendationResultsDialog {
|
|||||||
logDiskTableRow,
|
logDiskTableRow,
|
||||||
];
|
];
|
||||||
|
|
||||||
const storageConfigurationTable: azdata.DeclarativeTableComponent = _view.modelBuilder.declarativeTable().withProps({
|
const storageConfigurationTable: azdata.DeclarativeTableComponent = _view.modelBuilder.declarativeTable()
|
||||||
|
.withProps({
|
||||||
ariaLabel: constants.RECOMMENDED_TARGET_STORAGE_CONFIGURATION,
|
ariaLabel: constants.RECOMMENDED_TARGET_STORAGE_CONFIGURATION,
|
||||||
columns: columns,
|
columns: columns,
|
||||||
dataValues: storageConfigurationTableRows,
|
dataValues: storageConfigurationTableRows,
|
||||||
width: 700
|
width: 700
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
const container = _view.modelBuilder.flexContainer().withLayout({
|
const container = _view.modelBuilder.flexContainer()
|
||||||
flexFlow: 'column'
|
.withLayout({ flexFlow: 'column' })
|
||||||
}).withItems([
|
.withItems([
|
||||||
recommendedTargetStorageSection,
|
recommendedTargetStorageSection,
|
||||||
recommendedTargetStorageInfo,
|
recommendedTargetStorageInfo,
|
||||||
storageConfigurationTable,
|
storageConfigurationTable])
|
||||||
]).component();
|
.component();
|
||||||
return container;
|
return container;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -375,18 +362,15 @@ export class SkuRecommendationResultsDialog {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case MigrationTargetType.SQLDB:
|
case MigrationTargetType.SQLDB:
|
||||||
instanceRequirements = this.instanceRequirements?.databaseLevelRequirements.filter(d => {
|
instanceRequirements = this.instanceRequirements?.databaseLevelRequirements
|
||||||
return databaseName === d.databaseName;
|
.filter((d) => databaseName === d.databaseName)[0]!;
|
||||||
})[0]!;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const storagePropertiesSection = _view.modelBuilder.text().withProps({
|
const storagePropertiesSection = _view.modelBuilder.text()
|
||||||
|
.withProps({
|
||||||
value: constants.SOURCE_PROPERTIES,
|
value: constants.SOURCE_PROPERTIES,
|
||||||
CSSStyles: {
|
CSSStyles: { ...styles.SECTION_HEADER_CSS, 'margin-top': '12px' }
|
||||||
...styles.SECTION_HEADER_CSS,
|
|
||||||
'margin-top': '12px'
|
|
||||||
}
|
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
const headerCssStyle = {
|
const headerCssStyle = {
|
||||||
@@ -407,7 +391,7 @@ export class SkuRecommendationResultsDialog {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const columnWidth = 80;
|
const columnWidth = 80;
|
||||||
let columns: azdata.DeclarativeTableColumn[] = [
|
const columns: azdata.DeclarativeTableColumn[] = [
|
||||||
{
|
{
|
||||||
valueType: azdata.DeclarativeDataType.string,
|
valueType: azdata.DeclarativeDataType.string,
|
||||||
displayName: constants.DIMENSION,
|
displayName: constants.DIMENSION,
|
||||||
@@ -450,19 +434,18 @@ export class SkuRecommendationResultsDialog {
|
|||||||
ioLatencyRow,
|
ioLatencyRow,
|
||||||
];
|
];
|
||||||
|
|
||||||
const storagePropertiesTable: azdata.DeclarativeTableComponent = _view.modelBuilder.declarativeTable().withProps({
|
const storagePropertiesTable: azdata.DeclarativeTableComponent = _view.modelBuilder.declarativeTable()
|
||||||
|
.withProps({
|
||||||
ariaLabel: constants.RECOMMENDED_TARGET_STORAGE_CONFIGURATION,
|
ariaLabel: constants.RECOMMENDED_TARGET_STORAGE_CONFIGURATION,
|
||||||
columns: columns,
|
columns: columns,
|
||||||
dataValues: storagePropertiesTableRows,
|
dataValues: storagePropertiesTableRows,
|
||||||
width: 300
|
width: 300
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
const container = _view.modelBuilder.flexContainer().withLayout({
|
const container = _view.modelBuilder.flexContainer()
|
||||||
flexFlow: 'column'
|
.withLayout({ flexFlow: 'column' })
|
||||||
}).withItems([
|
.withItems([storagePropertiesSection, storagePropertiesTable])
|
||||||
storagePropertiesSection,
|
.component();
|
||||||
storagePropertiesTable,
|
|
||||||
]).component();
|
|
||||||
return container;
|
return container;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -537,10 +520,9 @@ export class SkuRecommendationResultsDialog {
|
|||||||
}));
|
}));
|
||||||
this.dialog.customButtons = [this._saveButton];
|
this.dialog.customButtons = [this._saveButton];
|
||||||
|
|
||||||
const dialogSetupPromises: Thenable<void>[] = [];
|
const promise = this.initializeDialog(this.dialog);
|
||||||
dialogSetupPromises.push(this.initializeDialog(this.dialog));
|
|
||||||
azdata.window.openDialog(this.dialog);
|
azdata.window.openDialog(this.dialog);
|
||||||
await Promise.all(dialogSetupPromises);
|
await promise;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,324 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* 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 constants from '../../constants/strings';
|
||||||
|
import { AzureSqlDatabaseServer } from '../../api/azure';
|
||||||
|
import { collectSourceDatabaseTableInfo, collectTargetDatabaseTableInfo, TableInfo } from '../../api/sqlUtils';
|
||||||
|
import { MigrationStateModel } from '../../models/stateMachine';
|
||||||
|
|
||||||
|
const DialogName = 'TableMigrationSelection';
|
||||||
|
|
||||||
|
export class TableMigrationSelectionDialog {
|
||||||
|
private _dialog: azdata.window.Dialog | undefined;
|
||||||
|
private _headingText!: azdata.TextComponent;
|
||||||
|
private _filterInputBox!: azdata.InputBoxComponent;
|
||||||
|
private _tableSelectionTable!: azdata.TableComponent;
|
||||||
|
private _tableLoader!: azdata.LoadingComponent;
|
||||||
|
private _disposables: vscode.Disposable[] = [];
|
||||||
|
private _isOpen: boolean = false;
|
||||||
|
private _model: MigrationStateModel;
|
||||||
|
private _sourceDatabaseName: string;
|
||||||
|
private _tableSelectionMap!: Map<string, TableInfo>;
|
||||||
|
private _targetTableMap!: Map<string, TableInfo>;
|
||||||
|
private _onSaveCallback: () => Promise<void>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
model: MigrationStateModel,
|
||||||
|
sourceDatabaseName: string,
|
||||||
|
onSaveCallback: () => Promise<void>
|
||||||
|
) {
|
||||||
|
this._model = model;
|
||||||
|
this._sourceDatabaseName = sourceDatabaseName;
|
||||||
|
this._onSaveCallback = onSaveCallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _loadData(): Promise<void> {
|
||||||
|
try {
|
||||||
|
this._tableLoader.loading = true;
|
||||||
|
const targetDatabaseInfo = this._model._sourceTargetMapping.get(this._sourceDatabaseName);
|
||||||
|
if (targetDatabaseInfo) {
|
||||||
|
const sourceTableList: TableInfo[] = await collectSourceDatabaseTableInfo(
|
||||||
|
this._model.sourceConnectionId,
|
||||||
|
this._sourceDatabaseName);
|
||||||
|
|
||||||
|
this._tableSelectionMap = new Map();
|
||||||
|
sourceTableList.forEach(table => {
|
||||||
|
const sourceTable = targetDatabaseInfo.sourceTables.get(table.tableName);
|
||||||
|
const isSelected = sourceTable?.selectedForMigration === true;
|
||||||
|
const tableInfo: TableInfo = {
|
||||||
|
databaseName: table.databaseName,
|
||||||
|
rowCount: table.rowCount,
|
||||||
|
selectedForMigration: isSelected,
|
||||||
|
tableName: table.tableName,
|
||||||
|
};
|
||||||
|
this._tableSelectionMap.set(table.tableName, tableInfo);
|
||||||
|
});
|
||||||
|
|
||||||
|
const targetTableList: TableInfo[] = await collectTargetDatabaseTableInfo(
|
||||||
|
this._model._targetServerInstance as AzureSqlDatabaseServer,
|
||||||
|
targetDatabaseInfo.databaseName,
|
||||||
|
this._model._azureTenant.id,
|
||||||
|
this._model._targetUserName,
|
||||||
|
this._model._targetPassword);
|
||||||
|
|
||||||
|
this._targetTableMap = new Map();
|
||||||
|
targetTableList.forEach(table =>
|
||||||
|
this._targetTableMap.set(
|
||||||
|
table.tableName, {
|
||||||
|
databaseName: table.databaseName,
|
||||||
|
rowCount: table.rowCount,
|
||||||
|
selectedForMigration: false,
|
||||||
|
tableName: table.tableName,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this._dialog!.message = {
|
||||||
|
text: constants.DATABASE_TABLE_CONNECTION_ERROR,
|
||||||
|
description: constants.DATABASE_TABLE_CONNECTION_ERROR_MESSAGE(error.message),
|
||||||
|
level: azdata.window.MessageLevel.Error
|
||||||
|
};
|
||||||
|
} finally {
|
||||||
|
this._tableLoader.loading = false;
|
||||||
|
await this._loadControls();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _loadControls(): Promise<void> {
|
||||||
|
const data: any[][] = [];
|
||||||
|
const filterText = this._filterInputBox.value ?? '';
|
||||||
|
const selectedItems: number[] = [];
|
||||||
|
let tableRow = 0;
|
||||||
|
this._tableSelectionMap.forEach(sourceTable => {
|
||||||
|
if (filterText?.length === 0 || sourceTable.tableName.indexOf(filterText) > -1) {
|
||||||
|
let tableStatus = constants.TARGET_TABLE_MISSING;
|
||||||
|
const targetTable = this._targetTableMap.get(sourceTable.tableName);
|
||||||
|
if (targetTable) {
|
||||||
|
const targetTableRowCount = targetTable?.rowCount ?? 0;
|
||||||
|
tableStatus = targetTableRowCount > 0
|
||||||
|
? constants.TARGET_TABLE_NOT_EMPTY
|
||||||
|
: '--';
|
||||||
|
}
|
||||||
|
|
||||||
|
data.push([
|
||||||
|
sourceTable.selectedForMigration,
|
||||||
|
sourceTable.tableName,
|
||||||
|
tableStatus]);
|
||||||
|
if (sourceTable.selectedForMigration && targetTable) {
|
||||||
|
selectedItems.push(tableRow);
|
||||||
|
}
|
||||||
|
tableRow++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await this._tableSelectionTable.updateProperty('data', data);
|
||||||
|
this._tableSelectionTable.selectedRows = selectedItems;
|
||||||
|
this._updateRowSelection();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _initializeDialog(dialog: azdata.window.Dialog): Promise<void> {
|
||||||
|
dialog.registerContent(async (view) => {
|
||||||
|
this._filterInputBox = view.modelBuilder.inputBox()
|
||||||
|
.withProps({
|
||||||
|
inputType: 'search',
|
||||||
|
placeHolder: constants.TABLE_SELECTION_FILTER,
|
||||||
|
width: 268,
|
||||||
|
}).component();
|
||||||
|
|
||||||
|
this._disposables.push(
|
||||||
|
this._filterInputBox.onTextChanged(
|
||||||
|
async e => await this._loadControls()));
|
||||||
|
|
||||||
|
this._headingText = view.modelBuilder.text()
|
||||||
|
.withProps({ value: constants.DATABASE_LOADING_TABLES })
|
||||||
|
.component();
|
||||||
|
|
||||||
|
this._tableSelectionTable = await this._createSelectionTable(view);
|
||||||
|
this._tableLoader = view.modelBuilder.loadingComponent()
|
||||||
|
.withItem(this._tableSelectionTable)
|
||||||
|
.withProps({
|
||||||
|
loading: false,
|
||||||
|
loadingText: constants.DATABASE_TABLE_DATA_LOADING
|
||||||
|
}).component();
|
||||||
|
|
||||||
|
const flex = view.modelBuilder.flexContainer()
|
||||||
|
.withItems([
|
||||||
|
this._filterInputBox,
|
||||||
|
this._headingText,
|
||||||
|
this._tableLoader],
|
||||||
|
{ flex: '0 0 auto' })
|
||||||
|
.withProps({ CSSStyles: { 'margin': '0 0 0 15px' } })
|
||||||
|
.withLayout({
|
||||||
|
flexFlow: 'column',
|
||||||
|
height: '100%',
|
||||||
|
width: 565,
|
||||||
|
}).component();
|
||||||
|
|
||||||
|
this._disposables.push(
|
||||||
|
view.onClosed(e =>
|
||||||
|
this._disposables.forEach(
|
||||||
|
d => { try { d.dispose(); } catch { } })));
|
||||||
|
|
||||||
|
await view.initializeModel(flex);
|
||||||
|
await this._loadData();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async openDialog(dialogTitle: string) {
|
||||||
|
if (!this._isOpen) {
|
||||||
|
this._isOpen = true;
|
||||||
|
this._dialog = azdata.window.createModelViewDialog(
|
||||||
|
dialogTitle,
|
||||||
|
DialogName,
|
||||||
|
600);
|
||||||
|
|
||||||
|
this._dialog.okButton.label = constants.TABLE_SELECTION_UPDATE_BUTTON;
|
||||||
|
this._disposables.push(
|
||||||
|
this._dialog.okButton.onClick(
|
||||||
|
async () => this._save()));
|
||||||
|
|
||||||
|
this._dialog.cancelButton.label = constants.TABLE_SELECTION_CANCEL_BUTTON;
|
||||||
|
this._disposables.push(
|
||||||
|
this._dialog.cancelButton.onClick(
|
||||||
|
async () => this._isOpen = false));
|
||||||
|
|
||||||
|
const promise = this._initializeDialog(this._dialog);
|
||||||
|
azdata.window.openDialog(this._dialog);
|
||||||
|
await promise;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _createSelectionTable(view: azdata.ModelView): Promise<azdata.TableComponent> {
|
||||||
|
const cssClass = 'no-borders';
|
||||||
|
const table = view.modelBuilder.table()
|
||||||
|
.withProps({
|
||||||
|
data: [],
|
||||||
|
width: 565,
|
||||||
|
height: '600px',
|
||||||
|
forceFitColumns: azdata.ColumnSizingMode.ForceFit,
|
||||||
|
columns: [
|
||||||
|
<azdata.CheckboxColumn>{
|
||||||
|
value: '',
|
||||||
|
width: 10,
|
||||||
|
type: azdata.ColumnType.checkBox,
|
||||||
|
action: azdata.ActionOnCellCheckboxCheck.selectRow,
|
||||||
|
resizable: false,
|
||||||
|
cssClass: cssClass,
|
||||||
|
headerCssClass: cssClass,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: constants.TABLE_SELECTION_TABLENAME_COLUMN,
|
||||||
|
value: 'tableName',
|
||||||
|
type: azdata.ColumnType.text,
|
||||||
|
width: 300,
|
||||||
|
cssClass: cssClass,
|
||||||
|
headerCssClass: cssClass,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: constants.TABLE_SELECTION_HASROWS_COLUMN,
|
||||||
|
value: 'hasRows',
|
||||||
|
type: azdata.ColumnType.text,
|
||||||
|
width: 255,
|
||||||
|
cssClass: cssClass,
|
||||||
|
headerCssClass: cssClass,
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
.withValidation(() => true)
|
||||||
|
.component();
|
||||||
|
|
||||||
|
let updating: boolean = false;
|
||||||
|
this._disposables.push(
|
||||||
|
table.onRowSelected(e => {
|
||||||
|
if (updating) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
updating = true;
|
||||||
|
|
||||||
|
// collect table list selected for migration
|
||||||
|
const selectedRows = this._tableSelectionTable.selectedRows ?? [];
|
||||||
|
const keepSelectedRows: number[] = [];
|
||||||
|
// determine if selected rows have a matching target and can be selected
|
||||||
|
selectedRows.forEach(rowIndex => {
|
||||||
|
// get selected source table name
|
||||||
|
const sourceTableName = this._tableSelectionTable.data[rowIndex][1] as string;
|
||||||
|
// get source table info
|
||||||
|
const sourceTableInfo = this._tableSelectionMap.get(sourceTableName);
|
||||||
|
if (sourceTableInfo) {
|
||||||
|
// see if source table exists on target database
|
||||||
|
const targetTableInfo = this._targetTableMap.get(sourceTableName);
|
||||||
|
// keep source table selected
|
||||||
|
sourceTableInfo.selectedForMigration = targetTableInfo !== undefined;
|
||||||
|
// update table selection map with new selectedForMigration value
|
||||||
|
this._tableSelectionMap.set(sourceTableName, sourceTableInfo);
|
||||||
|
// keep row selected
|
||||||
|
if (sourceTableInfo.selectedForMigration) {
|
||||||
|
keepSelectedRows.push(rowIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// if the selected rows are different, update the selectedRows property
|
||||||
|
if (!this._areEqual(this._tableSelectionTable.selectedRows ?? [], keepSelectedRows)) {
|
||||||
|
this._tableSelectionTable.selectedRows = keepSelectedRows;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._updateRowSelection();
|
||||||
|
updating = false;
|
||||||
|
}));
|
||||||
|
|
||||||
|
return table;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _areEqual(source: number[], target: number[]): boolean {
|
||||||
|
if (source.length === target.length) {
|
||||||
|
for (let i = 0; i < source.length; i++) {
|
||||||
|
if (source[i] !== target[i]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _updateRowSelection(): void {
|
||||||
|
this._headingText.value = this._tableSelectionTable.data.length > 0
|
||||||
|
? constants.TABLE_SELECTED_COUNT(
|
||||||
|
this._tableSelectionTable.selectedRows?.length ?? 0,
|
||||||
|
this._tableSelectionTable.data.length)
|
||||||
|
: this._tableLoader.loading
|
||||||
|
? constants.DATABASE_LOADING_TABLES
|
||||||
|
: constants.DATABASE_MISSING_TABLES;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _save(): Promise<void> {
|
||||||
|
const targetDatabaseInfo = this._model._sourceTargetMapping.get(this._sourceDatabaseName);
|
||||||
|
if (targetDatabaseInfo) {
|
||||||
|
// collect table list selected for migration
|
||||||
|
const selectedRows = this._tableSelectionTable.selectedRows ?? [];
|
||||||
|
const selectedTables = new Map<String, TableInfo>();
|
||||||
|
selectedRows.forEach(rowIndex => {
|
||||||
|
const tableName = this._tableSelectionTable.data[rowIndex][1] as string;
|
||||||
|
const tableInfo = this._tableSelectionMap.get(tableName);
|
||||||
|
if (tableInfo) {
|
||||||
|
selectedTables.set(tableName, tableInfo);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// copy table map selection status from grid
|
||||||
|
this._tableSelectionMap.forEach(tableInfo => {
|
||||||
|
const selectedTableInfo = selectedTables.get(tableInfo.tableName);
|
||||||
|
tableInfo.selectedForMigration = selectedTableInfo?.selectedForMigration === true;
|
||||||
|
this._tableSelectionMap.set(tableInfo.tableName, tableInfo);
|
||||||
|
});
|
||||||
|
|
||||||
|
// save table selection changes to migration source target map
|
||||||
|
targetDatabaseInfo.sourceTables = this._tableSelectionMap;
|
||||||
|
this._model._sourceTargetMapping.set(this._sourceDatabaseName, targetDatabaseInfo);
|
||||||
|
}
|
||||||
|
await this._onSaveCallback();
|
||||||
|
this._isOpen = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import * as azdata from 'azdata';
|
import * as azdata from 'azdata';
|
||||||
import { MigrationMode, MigrationStateModel, NetworkContainerType } from '../../models/stateMachine';
|
import { MigrationMode, MigrationStateModel, MigrationTargetType, NetworkContainerType } from '../../models/stateMachine';
|
||||||
import * as constants from '../../constants/strings';
|
import * as constants from '../../constants/strings';
|
||||||
import * as styles from '../../constants/styles';
|
import * as styles from '../../constants/styles';
|
||||||
|
|
||||||
@@ -25,21 +25,19 @@ export class TargetDatabaseSummaryDialog {
|
|||||||
this._dialogObject = azdata.window.createModelViewDialog(
|
this._dialogObject = azdata.window.createModelViewDialog(
|
||||||
constants.DATABASE_TO_BE_MIGRATED,
|
constants.DATABASE_TO_BE_MIGRATED,
|
||||||
'TargetDatabaseSummaryDialog',
|
'TargetDatabaseSummaryDialog',
|
||||||
dialogWidth
|
dialogWidth);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async initialize(): Promise<void> {
|
async initialize(): Promise<void> {
|
||||||
let tab = azdata.window.createTab('sql.migration.CreateResourceGroupDialog');
|
const tab = azdata.window.createTab('sql.migration.CreateResourceGroupDialog');
|
||||||
tab.registerContent(async (view: azdata.ModelView) => {
|
tab.registerContent(async (view: azdata.ModelView) => {
|
||||||
this._view = view;
|
this._view = view;
|
||||||
|
|
||||||
const databaseCount = this._view.modelBuilder.text().withProps({
|
const isSqlDbMigration = this._model._targetType === MigrationTargetType.SQLDB;
|
||||||
|
const databaseCount = this._view.modelBuilder.text()
|
||||||
|
.withProps({
|
||||||
value: constants.COUNT_DATABASES(this._model._databasesForMigration.length),
|
value: constants.COUNT_DATABASES(this._model._databasesForMigration.length),
|
||||||
CSSStyles: {
|
CSSStyles: { ...styles.BODY_CSS, 'margin-bottom': '20px' }
|
||||||
...styles.BODY_CSS,
|
|
||||||
'margin-bottom': '20px'
|
|
||||||
}
|
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
const headerCssStyle = {
|
const headerCssStyle = {
|
||||||
@@ -61,7 +59,7 @@ export class TargetDatabaseSummaryDialog {
|
|||||||
|
|
||||||
const columnWidth = 150;
|
const columnWidth = 150;
|
||||||
|
|
||||||
let columns: azdata.DeclarativeTableColumn[] = [
|
const columns: azdata.DeclarativeTableColumn[] = [
|
||||||
{
|
{
|
||||||
valueType: azdata.DeclarativeDataType.string,
|
valueType: azdata.DeclarativeDataType.string,
|
||||||
displayName: constants.SOURCE_DATABASE,
|
displayName: constants.SOURCE_DATABASE,
|
||||||
@@ -70,7 +68,6 @@ export class TargetDatabaseSummaryDialog {
|
|||||||
rowCssStyles: rowCssStyle,
|
rowCssStyles: rowCssStyle,
|
||||||
headerCssStyles: headerCssStyle
|
headerCssStyles: headerCssStyle
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
valueType: azdata.DeclarativeDataType.string,
|
valueType: azdata.DeclarativeDataType.string,
|
||||||
displayName: constants.TARGET_DATABASE_NAME,
|
displayName: constants.TARGET_DATABASE_NAME,
|
||||||
@@ -78,39 +75,43 @@ export class TargetDatabaseSummaryDialog {
|
|||||||
width: columnWidth,
|
width: columnWidth,
|
||||||
rowCssStyles: rowCssStyle,
|
rowCssStyles: rowCssStyle,
|
||||||
headerCssStyles: headerCssStyle
|
headerCssStyles: headerCssStyle
|
||||||
}
|
}];
|
||||||
];
|
|
||||||
|
|
||||||
if (this._model._databaseBackup.networkContainerType === NetworkContainerType.BLOB_CONTAINER) {
|
if (this._model._databaseBackup.networkContainerType === NetworkContainerType.BLOB_CONTAINER) {
|
||||||
columns.push({
|
columns.push(
|
||||||
|
{
|
||||||
valueType: azdata.DeclarativeDataType.string,
|
valueType: azdata.DeclarativeDataType.string,
|
||||||
displayName: constants.LOCATION,
|
displayName: constants.LOCATION,
|
||||||
isReadOnly: true,
|
isReadOnly: true,
|
||||||
width: columnWidth,
|
width: columnWidth,
|
||||||
rowCssStyles: rowCssStyle,
|
rowCssStyles: rowCssStyle,
|
||||||
headerCssStyles: headerCssStyle
|
headerCssStyles: headerCssStyle
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
valueType: azdata.DeclarativeDataType.string,
|
valueType: azdata.DeclarativeDataType.string,
|
||||||
displayName: constants.RESOURCE_GROUP,
|
displayName: constants.RESOURCE_GROUP,
|
||||||
isReadOnly: true,
|
isReadOnly: true,
|
||||||
width: columnWidth,
|
width: columnWidth,
|
||||||
rowCssStyles: rowCssStyle,
|
rowCssStyles: rowCssStyle,
|
||||||
headerCssStyles: headerCssStyle
|
headerCssStyles: headerCssStyle
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
valueType: azdata.DeclarativeDataType.string,
|
valueType: azdata.DeclarativeDataType.string,
|
||||||
displayName: constants.SUMMARY_AZURE_STORAGE,
|
displayName: constants.SUMMARY_AZURE_STORAGE,
|
||||||
isReadOnly: true,
|
isReadOnly: true,
|
||||||
width: columnWidth,
|
width: columnWidth,
|
||||||
rowCssStyles: rowCssStyle,
|
rowCssStyles: rowCssStyle,
|
||||||
headerCssStyles: headerCssStyle
|
headerCssStyles: headerCssStyle
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
valueType: azdata.DeclarativeDataType.string,
|
valueType: azdata.DeclarativeDataType.string,
|
||||||
displayName: constants.BLOB_CONTAINER,
|
displayName: constants.BLOB_CONTAINER,
|
||||||
isReadOnly: true,
|
isReadOnly: true,
|
||||||
width: columnWidth,
|
width: columnWidth,
|
||||||
rowCssStyles: rowCssStyle,
|
rowCssStyles: rowCssStyle,
|
||||||
headerCssStyles: headerCssStyle
|
headerCssStyles: headerCssStyle
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
valueType: azdata.DeclarativeDataType.string,
|
valueType: azdata.DeclarativeDataType.string,
|
||||||
displayName: constants.BLOB_CONTAINER_LAST_BACKUP_FILE,
|
displayName: constants.BLOB_CONTAINER_LAST_BACKUP_FILE,
|
||||||
isReadOnly: true,
|
isReadOnly: true,
|
||||||
@@ -119,6 +120,15 @@ export class TargetDatabaseSummaryDialog {
|
|||||||
headerCssStyles: headerCssStyle,
|
headerCssStyles: headerCssStyle,
|
||||||
hidden: this._model._databaseBackup.migrationMode === MigrationMode.ONLINE
|
hidden: this._model._databaseBackup.migrationMode === MigrationMode.ONLINE
|
||||||
});
|
});
|
||||||
|
} else if (isSqlDbMigration) {
|
||||||
|
columns.push({
|
||||||
|
valueType: azdata.DeclarativeDataType.string,
|
||||||
|
displayName: constants.TARGET_TABLE_COUNT_NAME,
|
||||||
|
isReadOnly: true,
|
||||||
|
width: columnWidth,
|
||||||
|
rowCssStyles: rowCssStyle,
|
||||||
|
headerCssStyles: headerCssStyle
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
columns.push({
|
columns.push({
|
||||||
valueType: azdata.DeclarativeDataType.string,
|
valueType: azdata.DeclarativeDataType.string,
|
||||||
@@ -134,59 +144,54 @@ export class TargetDatabaseSummaryDialog {
|
|||||||
|
|
||||||
this._model._databasesForMigration.forEach((db, index) => {
|
this._model._databasesForMigration.forEach((db, index) => {
|
||||||
const tableRow: azdata.DeclarativeTableCellValue[] = [];
|
const tableRow: azdata.DeclarativeTableCellValue[] = [];
|
||||||
tableRow.push({
|
tableRow.push(
|
||||||
value: db
|
{ value: db },
|
||||||
}, {
|
{ value: this._model._targetDatabaseNames[index] });
|
||||||
value: this._model._targetDatabaseNames[index]
|
|
||||||
});
|
|
||||||
if (this._model._databaseBackup.networkContainerType === NetworkContainerType.BLOB_CONTAINER) {
|
if (this._model._databaseBackup.networkContainerType === NetworkContainerType.BLOB_CONTAINER) {
|
||||||
tableRow.push({
|
tableRow.push(
|
||||||
value: this._model._databaseBackup.blobs[index].storageAccount.location
|
{ value: this._model._databaseBackup.blobs[index].storageAccount.location },
|
||||||
}, {
|
{ value: this._model._databaseBackup.blobs[index].storageAccount.resourceGroup! },
|
||||||
value: this._model._databaseBackup.blobs[index].storageAccount.resourceGroup!
|
{ value: this._model._databaseBackup.blobs[index].storageAccount.name },
|
||||||
}, {
|
{ value: this._model._databaseBackup.blobs[index].blobContainer.name });
|
||||||
value: this._model._databaseBackup.blobs[index].storageAccount.name
|
|
||||||
}, {
|
|
||||||
value: this._model._databaseBackup.blobs[index].blobContainer.name
|
|
||||||
});
|
|
||||||
|
|
||||||
if (this._model._databaseBackup.migrationMode === MigrationMode.OFFLINE) {
|
if (this._model._databaseBackup.migrationMode === MigrationMode.OFFLINE) {
|
||||||
tableRow.push({
|
tableRow.push(
|
||||||
value: this._model._databaseBackup.blobs[index].lastBackupFile!
|
{ value: this._model._databaseBackup.blobs[index].lastBackupFile! });
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
} else if (isSqlDbMigration) {
|
||||||
|
const totalTables = this._model._sourceTargetMapping.get(db)?.sourceTables.size ?? 0;
|
||||||
|
let selectedTables = 0;
|
||||||
|
this._model._sourceTargetMapping.get(db)?.sourceTables.forEach(
|
||||||
|
tableInfo => selectedTables += tableInfo.selectedForMigration ? 1 : 0);
|
||||||
|
tableRow.push(
|
||||||
|
{ value: constants.TOTAL_TABLES_SELECTED(selectedTables, totalTables) });
|
||||||
} else {
|
} else {
|
||||||
tableRow.push({
|
tableRow.push(
|
||||||
value: this._model._databaseBackup.networkShares[index].networkShareLocation
|
{ value: this._model._databaseBackup.networkShares[index].networkShareLocation });
|
||||||
});
|
|
||||||
}
|
}
|
||||||
tableRows.push(tableRow);
|
tableRows.push(tableRow);
|
||||||
});
|
});
|
||||||
|
|
||||||
const databaseTable: azdata.DeclarativeTableComponent = this._view.modelBuilder.declarativeTable().withProps({
|
const databaseTable: azdata.DeclarativeTableComponent = this._view.modelBuilder.declarativeTable()
|
||||||
|
.withProps({
|
||||||
ariaLabel: constants.DATABASE_TO_BE_MIGRATED,
|
ariaLabel: constants.DATABASE_TO_BE_MIGRATED,
|
||||||
columns: columns,
|
columns: columns,
|
||||||
dataValues: tableRows,
|
dataValues: tableRows,
|
||||||
width: this._tableLength
|
width: this._tableLength
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
const container = this._view.modelBuilder.flexContainer().withLayout({
|
const container = this._view.modelBuilder.flexContainer()
|
||||||
flexFlow: 'column',
|
.withLayout({ flexFlow: 'column' })
|
||||||
}).withItems([
|
.withItems([databaseCount, databaseTable])
|
||||||
databaseCount,
|
.component();
|
||||||
databaseTable
|
const form = this._view.modelBuilder.formContainer()
|
||||||
]).component();
|
.withFormItems(
|
||||||
const formBuilder = this._view.modelBuilder.formContainer().withFormItems(
|
[{ component: container }],
|
||||||
[
|
{ horizontal: false })
|
||||||
{
|
.withLayout({ width: '100%' })
|
||||||
component: container
|
.component();
|
||||||
}
|
|
||||||
],
|
|
||||||
{
|
|
||||||
horizontal: false
|
|
||||||
}
|
|
||||||
);
|
|
||||||
const form = formBuilder.withLayout({ width: '100%' }).component();
|
|
||||||
return view.initializeModel(form);
|
return view.initializeModel(form);
|
||||||
});
|
});
|
||||||
this._dialogObject.content = [tab];
|
this._dialogObject.content = [tab];
|
||||||
|
|||||||
@@ -4,154 +4,14 @@
|
|||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import * as azdata from 'azdata';
|
|
||||||
import { WizardController } from './wizard/wizardController';
|
|
||||||
import * as mssql from 'mssql';
|
|
||||||
import { promises as fs } from 'fs';
|
|
||||||
import * as loc from './constants/strings';
|
|
||||||
import { MigrationNotebookInfo, NotebookPathHelper } from './constants/notebookPathHelper';
|
|
||||||
import { IconPathHelper } from './constants/iconPathHelper';
|
|
||||||
import { DashboardWidget } from './dashboard/sqlServerDashboard';
|
import { DashboardWidget } from './dashboard/sqlServerDashboard';
|
||||||
import { MigrationLocalStorage } from './models/migrationLocalStorage';
|
|
||||||
import { MigrationStateModel, SavedInfo } from './models/stateMachine';
|
|
||||||
import { SavedAssessmentDialog } from './dialog/assessmentResults/savedAssessmentDialog';
|
|
||||||
|
|
||||||
class SQLMigration {
|
|
||||||
|
|
||||||
public stateModel!: MigrationStateModel;
|
|
||||||
|
|
||||||
constructor(private readonly context: vscode.ExtensionContext) {
|
|
||||||
NotebookPathHelper.setExtensionContext(context);
|
|
||||||
IconPathHelper.setExtensionContext(context);
|
|
||||||
MigrationLocalStorage.setExtensionContext(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
async start(): Promise<void> {
|
|
||||||
await this.registerCommands();
|
|
||||||
}
|
|
||||||
|
|
||||||
async registerCommands(): Promise<void> {
|
|
||||||
const commandDisposables: vscode.Disposable[] = [ // Array of disposables returned by registerCommand
|
|
||||||
vscode.commands.registerCommand(
|
|
||||||
'sqlmigration.start',
|
|
||||||
async () => await this.launchMigrationWizard()),
|
|
||||||
vscode.commands.registerCommand(
|
|
||||||
'sqlmigration.openNotebooks',
|
|
||||||
async () => {
|
|
||||||
const input = vscode.window.createQuickPick<MigrationNotebookInfo>();
|
|
||||||
input.placeholder = loc.NOTEBOOK_QUICK_PICK_PLACEHOLDER;
|
|
||||||
|
|
||||||
input.items = NotebookPathHelper.getAllMigrationNotebooks();
|
|
||||||
|
|
||||||
this.context.subscriptions.push(input.onDidAccept(async (e) => {
|
|
||||||
const selectedNotebook = input.selectedItems[0];
|
|
||||||
if (selectedNotebook) {
|
|
||||||
try {
|
|
||||||
await azdata.nb.showNotebookDocument(vscode.Uri.parse(`untitled: ${selectedNotebook.label}`), {
|
|
||||||
preview: false,
|
|
||||||
initialContent: (await fs.readFile(selectedNotebook.notebookPath)).toString(),
|
|
||||||
initialDirtyState: false
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
void vscode.window.showErrorMessage(`${loc.NOTEBOOK_OPEN_ERROR} - ${e.toString()}`);
|
|
||||||
}
|
|
||||||
input.hide();
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
input.show();
|
|
||||||
}),
|
|
||||||
azdata.tasks.registerTask(
|
|
||||||
'sqlmigration.start',
|
|
||||||
async () => await this.launchMigrationWizard()),
|
|
||||||
azdata.tasks.registerTask(
|
|
||||||
'sqlmigration.newsupportrequest',
|
|
||||||
async () => await this.launchNewSupportRequest()),
|
|
||||||
azdata.tasks.registerTask(
|
|
||||||
'sqlmigration.sendfeedback',
|
|
||||||
async () => {
|
|
||||||
const actionId = 'workbench.action.openIssueReporter';
|
|
||||||
const args = {
|
|
||||||
extensionId: 'microsoft.sql-migration',
|
|
||||||
issueTitle: loc.FEEDBACK_ISSUE_TITLE,
|
|
||||||
};
|
|
||||||
return await vscode.commands.executeCommand(actionId, args);
|
|
||||||
}),
|
|
||||||
azdata.tasks.registerTask(
|
|
||||||
'sqlmigration.refreshmigrations',
|
|
||||||
async (e) => await widget.refresh()),
|
|
||||||
];
|
|
||||||
|
|
||||||
this.context.subscriptions.push(...commandDisposables);
|
|
||||||
}
|
|
||||||
|
|
||||||
async launchMigrationWizard(): Promise<void> {
|
|
||||||
let activeConnection = await azdata.connection.getCurrentConnection();
|
|
||||||
let connectionId: string = '';
|
|
||||||
let serverName: string = '';
|
|
||||||
if (!activeConnection) {
|
|
||||||
const connection = await azdata.connection.openConnectionDialog();
|
|
||||||
if (connection) {
|
|
||||||
connectionId = connection.connectionId;
|
|
||||||
serverName = connection.options.server;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
connectionId = activeConnection.connectionId;
|
|
||||||
serverName = activeConnection.serverName;
|
|
||||||
}
|
|
||||||
if (serverName) {
|
|
||||||
const api = (await vscode.extensions.getExtension(mssql.extension.name)?.activate()) as mssql.IExtension;
|
|
||||||
if (api) {
|
|
||||||
this.stateModel = new MigrationStateModel(this.context, connectionId, api.sqlMigration);
|
|
||||||
this.context.subscriptions.push(this.stateModel);
|
|
||||||
const savedInfo = this.checkSavedInfo(serverName);
|
|
||||||
if (savedInfo) {
|
|
||||||
this.stateModel.savedInfo = savedInfo;
|
|
||||||
this.stateModel.serverName = serverName;
|
|
||||||
const savedAssessmentDialog = new SavedAssessmentDialog(
|
|
||||||
this.context,
|
|
||||||
this.stateModel,
|
|
||||||
async () => await widget?.onDialogClosed());
|
|
||||||
await savedAssessmentDialog.openDialog();
|
|
||||||
} else {
|
|
||||||
const wizardController = new WizardController(
|
|
||||||
this.context,
|
|
||||||
this.stateModel,
|
|
||||||
async () => await widget?.onDialogClosed());
|
|
||||||
await wizardController.openWizard(connectionId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private checkSavedInfo(serverName: string): SavedInfo | undefined {
|
|
||||||
let savedInfo: SavedInfo | undefined = this.context.globalState.get(`${this.stateModel.mementoString}.${serverName}`);
|
|
||||||
if (savedInfo) {
|
|
||||||
return savedInfo;
|
|
||||||
} else {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async launchNewSupportRequest(): Promise<void> {
|
|
||||||
await vscode.env.openExternal(vscode.Uri.parse(
|
|
||||||
`https://portal.azure.com/#blade/Microsoft_Azure_Support/HelpAndSupportBlade/newsupportrequest`));
|
|
||||||
}
|
|
||||||
|
|
||||||
stop(): void {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let sqlMigration: SQLMigration;
|
|
||||||
let widget: DashboardWidget;
|
let widget: DashboardWidget;
|
||||||
export async function activate(context: vscode.ExtensionContext) {
|
export async function activate(context: vscode.ExtensionContext): Promise<DashboardWidget> {
|
||||||
sqlMigration = new SQLMigration(context);
|
|
||||||
await sqlMigration.registerCommands();
|
|
||||||
widget = new DashboardWidget(context);
|
widget = new DashboardWidget(context);
|
||||||
widget.register();
|
await widget.register();
|
||||||
|
return widget;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deactivate(): void {
|
export function deactivate(): void {
|
||||||
sqlMigration.stop();
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import * as azurecore from 'azurecore';
|
|||||||
import { DatabaseMigration, SqlMigrationService, getSubscriptions, getServiceMigrations } from '../api/azure';
|
import { DatabaseMigration, SqlMigrationService, getSubscriptions, getServiceMigrations } from '../api/azure';
|
||||||
import { deepClone } from '../api/utils';
|
import { deepClone } from '../api/utils';
|
||||||
import * as loc from '../constants/strings';
|
import * as loc from '../constants/strings';
|
||||||
|
import { ServiceContextChangeEvent } from '../dashboard/tabBase';
|
||||||
|
|
||||||
export class MigrationLocalStorage {
|
export class MigrationLocalStorage {
|
||||||
private static context: vscode.ExtensionContext;
|
private static context: vscode.ExtensionContext;
|
||||||
@@ -26,15 +27,16 @@ export class MigrationLocalStorage {
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async saveMigrationServiceContext(serviceContext: MigrationServiceContext): Promise<void> {
|
public static async saveMigrationServiceContext(serviceContext: MigrationServiceContext, serviceContextChangedEvent: vscode.EventEmitter<ServiceContextChangeEvent>): Promise<void> {
|
||||||
const connectionProfile = await azdata.connection.getCurrentConnection();
|
const connectionProfile = await azdata.connection.getCurrentConnection();
|
||||||
if (connectionProfile) {
|
if (connectionProfile) {
|
||||||
const serverContextKey = `${this.mementoToken}.${connectionProfile.serverName}.serviceContext`;
|
const serverContextKey = `${this.mementoToken}.${connectionProfile.serverName}.serviceContext`;
|
||||||
return await this.context.globalState.update(serverContextKey, deepClone(serviceContext));
|
await this.context.globalState.update(serverContextKey, deepClone(serviceContext));
|
||||||
|
serviceContextChangedEvent.fire({ connectionId: connectionProfile.connectionId });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async refreshMigrationAzureAccount(serviceContext: MigrationServiceContext, migration: DatabaseMigration): Promise<void> {
|
public static async refreshMigrationAzureAccount(serviceContext: MigrationServiceContext, migration: DatabaseMigration, serviceContextChangedEvent: vscode.EventEmitter<ServiceContextChangeEvent>): Promise<void> {
|
||||||
if (serviceContext.azureAccount?.isStale) {
|
if (serviceContext.azureAccount?.isStale) {
|
||||||
const accounts = await azdata.accounts.getAllAccounts();
|
const accounts = await azdata.accounts.getAllAccounts();
|
||||||
const account = accounts.find(a => !a.isStale && a.key.accountId === serviceContext.azureAccount?.key.accountId);
|
const account = accounts.find(a => !a.isStale && a.key.accountId === serviceContext.azureAccount?.key.accountId);
|
||||||
@@ -43,7 +45,7 @@ export class MigrationLocalStorage {
|
|||||||
const subscription = subscriptions.find(s => s.id === serviceContext.subscription?.id);
|
const subscription = subscriptions.find(s => s.id === serviceContext.subscription?.id);
|
||||||
if (subscription) {
|
if (subscription) {
|
||||||
serviceContext.azureAccount = account;
|
serviceContext.azureAccount = account;
|
||||||
await this.saveMigrationServiceContext(serviceContext);
|
await this.saveMigrationServiceContext(serviceContext, serviceContextChangedEvent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -87,30 +89,3 @@ export interface MigrationServiceContext {
|
|||||||
resourceGroup?: azurecore.azureResource.AzureResourceResourceGroup,
|
resourceGroup?: azurecore.azureResource.AzureResourceResourceGroup,
|
||||||
migrationService?: SqlMigrationService,
|
migrationService?: SqlMigrationService,
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum MigrationStatus {
|
|
||||||
Failed = 'Failed',
|
|
||||||
Succeeded = 'Succeeded',
|
|
||||||
InProgress = 'InProgress',
|
|
||||||
Canceled = 'Canceled',
|
|
||||||
Completing = 'Completing',
|
|
||||||
Creating = 'Creating',
|
|
||||||
Canceling = 'Canceling',
|
|
||||||
Retriable = 'Retriable',
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum ProvisioningState {
|
|
||||||
Failed = 'Failed',
|
|
||||||
Succeeded = 'Succeeded',
|
|
||||||
Creating = 'Creating'
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum BackupFileInfoStatus {
|
|
||||||
Arrived = 'Arrived',
|
|
||||||
Uploading = 'Uploading',
|
|
||||||
Uploaded = 'Uploaded',
|
|
||||||
Restoring = 'Restoring',
|
|
||||||
Restored = 'Restored',
|
|
||||||
Cancelled = 'Cancelled',
|
|
||||||
Ignored = 'Ignored'
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -80,6 +80,4 @@ export abstract class MigrationWizardPage {
|
|||||||
const current = this.wizard.currentPage;
|
const current = this.wizard.currentPage;
|
||||||
await this.wizard.setCurrentPage(current + 1);
|
await this.wizard.setCurrentPage(current + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,13 +7,14 @@ import * as azdata from 'azdata';
|
|||||||
import * as azurecore from 'azurecore';
|
import * as azurecore from 'azurecore';
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import * as mssql from 'mssql';
|
import * as mssql from 'mssql';
|
||||||
import { SqlMigrationService, SqlManagedInstance, startDatabaseMigration, StartDatabaseMigrationRequest, StorageAccount, SqlVMServer, getLocationDisplayName, getSqlManagedInstanceDatabases } from '../api/azure';
|
import { SqlMigrationService, SqlManagedInstance, startDatabaseMigration, StartDatabaseMigrationRequest, StorageAccount, SqlVMServer, getLocationDisplayName, getSqlManagedInstanceDatabases, AzureSqlDatabaseServer } from '../api/azure';
|
||||||
import * as constants from '../constants/strings';
|
import * as constants from '../constants/strings';
|
||||||
import * as nls from 'vscode-nls';
|
import * as nls from 'vscode-nls';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
import { sendSqlMigrationActionEvent, TelemetryAction, TelemetryViews, logError } from '../telemtery';
|
import { sendSqlMigrationActionEvent, TelemetryAction, TelemetryViews, logError } from '../telemtery';
|
||||||
import { hashString, deepClone } from '../api/utils';
|
import { hashString, deepClone } from '../api/utils';
|
||||||
import { SKURecommendationPage } from '../wizard/skuRecommendationPage';
|
import { SKURecommendationPage } from '../wizard/skuRecommendationPage';
|
||||||
|
import { excludeDatabses, TargetDatabaseInfo } from '../api/sqlUtils';
|
||||||
const localize = nls.loadMessageBundle();
|
const localize = nls.loadMessageBundle();
|
||||||
|
|
||||||
export enum State {
|
export enum State {
|
||||||
@@ -136,7 +137,7 @@ export interface SavedInfo {
|
|||||||
subscription: azurecore.azureResource.AzureResourceSubscription | null;
|
subscription: azurecore.azureResource.AzureResourceSubscription | null;
|
||||||
location: azurecore.azureResource.AzureLocation | null;
|
location: azurecore.azureResource.AzureLocation | null;
|
||||||
resourceGroup: azurecore.azureResource.AzureResourceResourceGroup | null;
|
resourceGroup: azurecore.azureResource.AzureResourceResourceGroup | null;
|
||||||
targetServerInstance: azurecore.azureResource.AzureSqlManagedInstance | SqlVMServer | null;
|
targetServerInstance: azurecore.azureResource.AzureSqlManagedInstance | SqlVMServer | AzureSqlDatabaseServer | null;
|
||||||
migrationMode: MigrationMode | null;
|
migrationMode: MigrationMode | null;
|
||||||
networkContainerType: NetworkContainerType | null;
|
networkContainerType: NetworkContainerType | null;
|
||||||
networkShares: NetworkShare[];
|
networkShares: NetworkShare[];
|
||||||
@@ -176,7 +177,8 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
|||||||
public _resourceGroup!: azurecore.azureResource.AzureResourceResourceGroup;
|
public _resourceGroup!: azurecore.azureResource.AzureResourceResourceGroup;
|
||||||
public _targetManagedInstances!: SqlManagedInstance[];
|
public _targetManagedInstances!: SqlManagedInstance[];
|
||||||
public _targetSqlVirtualMachines!: SqlVMServer[];
|
public _targetSqlVirtualMachines!: SqlVMServer[];
|
||||||
public _targetServerInstance!: SqlManagedInstance | SqlVMServer;
|
public _targetSqlDatabaseServers!: AzureSqlDatabaseServer[];
|
||||||
|
public _targetServerInstance!: SqlManagedInstance | SqlVMServer | AzureSqlDatabaseServer;
|
||||||
public _databaseBackup!: DatabaseBackupModel;
|
public _databaseBackup!: DatabaseBackupModel;
|
||||||
public _storageAccounts!: StorageAccount[];
|
public _storageAccounts!: StorageAccount[];
|
||||||
public _fileShares!: azurecore.azureResource.FileShare[];
|
public _fileShares!: azurecore.azureResource.FileShare[];
|
||||||
@@ -185,15 +187,15 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
|||||||
public _sourceDatabaseNames!: string[];
|
public _sourceDatabaseNames!: string[];
|
||||||
public _targetDatabaseNames!: string[];
|
public _targetDatabaseNames!: string[];
|
||||||
|
|
||||||
|
public _targetUserName!: string;
|
||||||
|
public _targetPassword!: string;
|
||||||
|
public _sourceTargetMapping: Map<string, TargetDatabaseInfo | undefined> = new Map();
|
||||||
|
|
||||||
public _sqlMigrationServiceResourceGroup!: azurecore.azureResource.AzureResourceResourceGroup;
|
public _sqlMigrationServiceResourceGroup!: azurecore.azureResource.AzureResourceResourceGroup;
|
||||||
public _sqlMigrationService!: SqlMigrationService | undefined;
|
public _sqlMigrationService!: SqlMigrationService | undefined;
|
||||||
public _sqlMigrationServices!: SqlMigrationService[];
|
public _sqlMigrationServices!: SqlMigrationService[];
|
||||||
public _nodeNames!: string[];
|
public _nodeNames!: string[];
|
||||||
|
|
||||||
private _stateChangeEventEmitter = new vscode.EventEmitter<StateChangeEvent>();
|
|
||||||
private _currentState: State;
|
|
||||||
private _gatheringInformationError: string | undefined;
|
|
||||||
|
|
||||||
public _databasesForAssessment!: string[];
|
public _databasesForAssessment!: string[];
|
||||||
public _assessmentResults!: ServerAssessment;
|
public _assessmentResults!: ServerAssessment;
|
||||||
public _assessedDatabaseList!: string[];
|
public _assessedDatabaseList!: string[];
|
||||||
@@ -204,8 +206,10 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
|||||||
|
|
||||||
public _databasesForMigration: string[] = [];
|
public _databasesForMigration: string[] = [];
|
||||||
public _didUpdateDatabasesForMigration: boolean = false;
|
public _didUpdateDatabasesForMigration: boolean = false;
|
||||||
|
public _didDatabaseMappingChange: boolean = false;
|
||||||
public _vmDbs: string[] = [];
|
public _vmDbs: string[] = [];
|
||||||
public _miDbs: string[] = [];
|
public _miDbs: string[] = [];
|
||||||
|
public _sqldbDbs: string[] = [];
|
||||||
public _targetType!: MigrationTargetType;
|
public _targetType!: MigrationTargetType;
|
||||||
|
|
||||||
public _skuRecommendationResults!: SkuRecommendation;
|
public _skuRecommendationResults!: SkuRecommendation;
|
||||||
@@ -213,10 +217,7 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
|||||||
private _skuRecommendationApiResponse!: mssql.SkuRecommendationResult;
|
private _skuRecommendationApiResponse!: mssql.SkuRecommendationResult;
|
||||||
public _skuRecommendationReportFilePaths: string[];
|
public _skuRecommendationReportFilePaths: string[];
|
||||||
public _skuRecommendationPerformanceLocation!: string;
|
public _skuRecommendationPerformanceLocation!: string;
|
||||||
private _skuRecommendationRecommendedDatabaseList!: string[];
|
|
||||||
private _startPerfDataCollectionApiResponse!: mssql.StartPerfDataCollectionResult;
|
|
||||||
private _stopPerfDataCollectionApiResponse!: mssql.StopPerfDataCollectionResult;
|
|
||||||
private _refreshPerfDataCollectionApiResponse!: mssql.RefreshPerfDataCollectionResult;
|
|
||||||
public _perfDataCollectionStartDate!: Date | undefined;
|
public _perfDataCollectionStartDate!: Date | undefined;
|
||||||
public _perfDataCollectionStopDate!: Date | undefined;
|
public _perfDataCollectionStopDate!: Date | undefined;
|
||||||
public _perfDataCollectionLastRefreshedDate!: Date;
|
public _perfDataCollectionLastRefreshedDate!: Date;
|
||||||
@@ -232,9 +233,7 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
|||||||
public readonly _recommendationTargetPlatforms = [MigrationTargetType.SQLDB, MigrationTargetType.SQLMI, MigrationTargetType.SQLVM];
|
public readonly _recommendationTargetPlatforms = [MigrationTargetType.SQLDB, MigrationTargetType.SQLMI, MigrationTargetType.SQLVM];
|
||||||
|
|
||||||
public refreshPerfDataCollectionFrequency = this._performanceDataQueryIntervalInSeconds * 1000;
|
public refreshPerfDataCollectionFrequency = this._performanceDataQueryIntervalInSeconds * 1000;
|
||||||
private _autoRefreshPerfDataCollectionHandle!: NodeJS.Timeout;
|
|
||||||
public refreshGetSkuRecommendationFrequency = constants.TIME_IN_MINUTES(10);
|
public refreshGetSkuRecommendationFrequency = constants.TIME_IN_MINUTES(10);
|
||||||
private _autoRefreshGetSkuRecommendationHandle!: NodeJS.Timeout;
|
|
||||||
|
|
||||||
public _skuScalingFactor!: number;
|
public _skuScalingFactor!: number;
|
||||||
public _skuTargetPercentile!: number;
|
public _skuTargetPercentile!: number;
|
||||||
@@ -246,15 +245,18 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
|||||||
public savedInfo!: SavedInfo;
|
public savedInfo!: SavedInfo;
|
||||||
public closedPage!: number;
|
public closedPage!: number;
|
||||||
public _sessionId: string = uuidv4();
|
public _sessionId: string = uuidv4();
|
||||||
|
|
||||||
public excludeDbs: string[] = [
|
|
||||||
'master',
|
|
||||||
'tempdb',
|
|
||||||
'msdb',
|
|
||||||
'model'
|
|
||||||
];
|
|
||||||
public serverName!: string;
|
public serverName!: string;
|
||||||
|
|
||||||
|
private _stateChangeEventEmitter = new vscode.EventEmitter<StateChangeEvent>();
|
||||||
|
private _currentState: State;
|
||||||
|
private _gatheringInformationError: string | undefined;
|
||||||
|
private _skuRecommendationRecommendedDatabaseList!: string[];
|
||||||
|
private _startPerfDataCollectionApiResponse!: mssql.StartPerfDataCollectionResult;
|
||||||
|
private _stopPerfDataCollectionApiResponse!: mssql.StopPerfDataCollectionResult;
|
||||||
|
private _refreshPerfDataCollectionApiResponse!: mssql.RefreshPerfDataCollectionResult;
|
||||||
|
private _autoRefreshPerfDataCollectionHandle!: NodeJS.Timeout;
|
||||||
|
private _autoRefreshGetSkuRecommendationHandle!: NodeJS.Timeout;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public extensionContext: vscode.ExtensionContext,
|
public extensionContext: vscode.ExtensionContext,
|
||||||
private readonly _sourceConnectionId: string,
|
private readonly _sourceConnectionId: string,
|
||||||
@@ -288,8 +290,8 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
|||||||
this._stateChangeEventEmitter.fire({ oldState, newState: this.currentState });
|
this._stateChangeEventEmitter.fire({ oldState, newState: this.currentState });
|
||||||
}
|
}
|
||||||
public async getDatabases(): Promise<string[]> {
|
public async getDatabases(): Promise<string[]> {
|
||||||
let temp = await azdata.connection.listDatabases(this.sourceConnectionId);
|
const temp = await azdata.connection.listDatabases(this.sourceConnectionId);
|
||||||
let finalResult = temp.filter((name) => !this.excludeDbs.includes(name));
|
const finalResult = temp.filter((name) => !excludeDatabses.includes(name));
|
||||||
return finalResult;
|
return finalResult;
|
||||||
}
|
}
|
||||||
public hasRecommendedDatabaseListChanged(): boolean {
|
public hasRecommendedDatabaseListChanged(): boolean {
|
||||||
@@ -304,7 +306,7 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getDatabaseAssessments(targetType: MigrationTargetType): Promise<ServerAssessment> {
|
public async getDatabaseAssessments(targetType: MigrationTargetType[]): Promise<ServerAssessment> {
|
||||||
const ownerUri = await azdata.connection.getUriForConnection(this.sourceConnectionId);
|
const ownerUri = await azdata.connection.getUriForConnection(this.sourceConnectionId);
|
||||||
try {
|
try {
|
||||||
const response = (await this.migrationService.getAssessments(ownerUri, this._databasesForAssessment))!;
|
const response = (await this.migrationService.getAssessments(ownerUri, this._databasesForAssessment))!;
|
||||||
@@ -313,11 +315,14 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
|||||||
|
|
||||||
if (response?.assessmentResult) {
|
if (response?.assessmentResult) {
|
||||||
response.assessmentResult.items = response.assessmentResult.items?.filter(
|
response.assessmentResult.items = response.assessmentResult.items?.filter(
|
||||||
issue => issue.appliesToMigrationTargetPlatform === targetType);
|
issue => targetType.includes(
|
||||||
|
<MigrationTargetType>issue.appliesToMigrationTargetPlatform));
|
||||||
|
|
||||||
response.assessmentResult.databases?.forEach(
|
response.assessmentResult.databases?.forEach(
|
||||||
database => database.items = database.items?.filter(
|
database => database.items = database.items?.filter(
|
||||||
issue => issue.appliesToMigrationTargetPlatform === targetType));
|
issue => targetType.includes(
|
||||||
|
<MigrationTargetType>issue.appliesToMigrationTargetPlatform)));
|
||||||
|
|
||||||
this._assessmentResults = {
|
this._assessmentResults = {
|
||||||
issues: this._assessmentApiResponse?.assessmentResult?.items || [],
|
issues: this._assessmentApiResponse?.assessmentResult?.items || [],
|
||||||
databaseAssessments: this._assessmentApiResponse?.assessmentResult?.databases?.map(d => {
|
databaseAssessments: this._assessmentApiResponse?.assessmentResult?.databases?.map(d => {
|
||||||
@@ -447,8 +452,7 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
|||||||
'sessionId': this._sessionId,
|
'sessionId': this._sessionId,
|
||||||
'recommendedSku': JSON.stringify(resultItem?.targetSku)
|
'recommendedSku': JSON.stringify(resultItem?.targetSku)
|
||||||
},
|
},
|
||||||
{}
|
{});
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this._skuRecommendationResults?.recommendations?.sqlVmRecommendationResults?.forEach(resultItem => {
|
this._skuRecommendationResults?.recommendations?.sqlVmRecommendationResults?.forEach(resultItem => {
|
||||||
@@ -460,8 +464,19 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
|||||||
'sessionId': this._sessionId,
|
'sessionId': this._sessionId,
|
||||||
'recommendedSku': JSON.stringify(resultItem?.targetSku)
|
'recommendedSku': JSON.stringify(resultItem?.targetSku)
|
||||||
},
|
},
|
||||||
{}
|
{});
|
||||||
);
|
});
|
||||||
|
|
||||||
|
this._skuRecommendationResults?.recommendations?.sqlDbRecommendationResults?.forEach(resultItem => {
|
||||||
|
// Send telemetry for recommended SQLDB SKU
|
||||||
|
sendSqlMigrationActionEvent(
|
||||||
|
TelemetryViews.SkuRecommendationWizard,
|
||||||
|
TelemetryAction.GetSqlDbSkuRecommendation,
|
||||||
|
{
|
||||||
|
'sessionId': this._sessionId,
|
||||||
|
'recommendedSku': JSON.stringify(resultItem?.targetSku)
|
||||||
|
},
|
||||||
|
{});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Send Instance requirements used for calculating recommendations
|
// Send Instance requirements used for calculating recommendations
|
||||||
@@ -513,8 +528,7 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
|||||||
'tempDBSizeInMB': this._skuRecommendationResults?.recommendations?.instanceRequirements?.tempDBSizeInMB,
|
'tempDBSizeInMB': this._skuRecommendationResults?.recommendations?.instanceRequirements?.tempDBSizeInMB,
|
||||||
'aggregationTargetPercentile': this._skuRecommendationResults?.recommendations?.instanceRequirements?.aggregationTargetPercentile,
|
'aggregationTargetPercentile': this._skuRecommendationResults?.recommendations?.instanceRequirements?.aggregationTargetPercentile,
|
||||||
'numberOfDataPointsAnalyzed': this._skuRecommendationResults?.recommendations?.instanceRequirements?.numberOfDataPointsAnalyzed,
|
'numberOfDataPointsAnalyzed': this._skuRecommendationResults?.recommendations?.instanceRequirements?.numberOfDataPointsAnalyzed,
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logError(TelemetryViews.SkuRecommendationWizard, 'GetSkuRecommendationTelemetryFailed', e);
|
logError(TelemetryViews.SkuRecommendationWizard, 'GetSkuRecommendationTelemetryFailed', e);
|
||||||
@@ -530,7 +544,12 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
|||||||
try {
|
try {
|
||||||
if (!this.performanceCollectionInProgress()) {
|
if (!this.performanceCollectionInProgress()) {
|
||||||
const ownerUri = await azdata.connection.getUriForConnection(this.sourceConnectionId);
|
const ownerUri = await azdata.connection.getUriForConnection(this.sourceConnectionId);
|
||||||
const response = await this.migrationService.startPerfDataCollection(ownerUri, dataFolder, perfQueryIntervalInSec, staticQueryIntervalInSec, numberOfIterations);
|
const response = await this.migrationService.startPerfDataCollection(
|
||||||
|
ownerUri,
|
||||||
|
dataFolder,
|
||||||
|
perfQueryIntervalInSec,
|
||||||
|
staticQueryIntervalInSec,
|
||||||
|
numberOfIterations);
|
||||||
|
|
||||||
this._startPerfDataCollectionApiResponse = response!;
|
this._startPerfDataCollectionApiResponse = response!;
|
||||||
this._perfDataCollectionStartDate = this._startPerfDataCollectionApiResponse.dateTimeStarted;
|
this._perfDataCollectionStartDate = this._startPerfDataCollectionApiResponse.dateTimeStarted;
|
||||||
@@ -560,8 +579,7 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
|||||||
'sessionId': this._sessionId,
|
'sessionId': this._sessionId,
|
||||||
'timeDataCollectionStarted': this._perfDataCollectionStartDate?.toString() || ''
|
'timeDataCollectionStarted': this._perfDataCollectionStartDate?.toString() || ''
|
||||||
},
|
},
|
||||||
{}
|
{});
|
||||||
);
|
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logError(TelemetryViews.DataCollectionWizard, 'StartDataCollectionTelemetryFailed', e);
|
logError(TelemetryViews.DataCollectionWizard, 'StartDataCollectionTelemetryFailed', e);
|
||||||
@@ -574,13 +592,14 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
|||||||
if (!this._autoRefreshPerfDataCollectionHandle) {
|
if (!this._autoRefreshPerfDataCollectionHandle) {
|
||||||
clearInterval(this._autoRefreshPerfDataCollectionHandle);
|
clearInterval(this._autoRefreshPerfDataCollectionHandle);
|
||||||
if (this.refreshPerfDataCollectionFrequency !== -1) {
|
if (this.refreshPerfDataCollectionFrequency !== -1) {
|
||||||
this._autoRefreshPerfDataCollectionHandle = setInterval(async function () {
|
this._autoRefreshPerfDataCollectionHandle = setInterval(
|
||||||
|
async function () {
|
||||||
await classVariable.refreshPerfDataCollection();
|
await classVariable.refreshPerfDataCollection();
|
||||||
|
|
||||||
if (await classVariable.isWaitingForFirstTimeRefresh()) {
|
if (await classVariable.isWaitingForFirstTimeRefresh()) {
|
||||||
await page.refreshSkuRecommendationComponents(); // update timer
|
await page.refreshSkuRecommendationComponents(); // update timer
|
||||||
}
|
}
|
||||||
}, refreshIntervalInMs);
|
},
|
||||||
|
refreshIntervalInMs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -588,9 +607,11 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
|||||||
// start one-time timer to get SKU recommendation
|
// start one-time timer to get SKU recommendation
|
||||||
clearTimeout(this._autoRefreshGetSkuRecommendationHandle);
|
clearTimeout(this._autoRefreshGetSkuRecommendationHandle);
|
||||||
if (this.refreshGetSkuRecommendationFrequency !== -1) {
|
if (this.refreshGetSkuRecommendationFrequency !== -1) {
|
||||||
this._autoRefreshGetSkuRecommendationHandle = setTimeout(async function () {
|
this._autoRefreshGetSkuRecommendationHandle = setTimeout(
|
||||||
|
async function () {
|
||||||
await page.refreshAzureRecommendation();
|
await page.refreshAzureRecommendation();
|
||||||
}, this.refreshGetSkuRecommendationFrequency);
|
},
|
||||||
|
this.refreshGetSkuRecommendationFrequency);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -612,7 +633,8 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Generate telemetry for stop data collection request
|
// Generate telemetry for stop data collection request
|
||||||
this.generateStopDataCollectionTelemetry().catch(e => console.error(e));
|
this.generateStopDataCollectionTelemetry()
|
||||||
|
.catch(e => console.error(e));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -625,8 +647,7 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
|||||||
'sessionId': this._sessionId,
|
'sessionId': this._sessionId,
|
||||||
'timeDataCollectionStopped': this._perfDataCollectionStopDate?.toString() || ''
|
'timeDataCollectionStopped': this._perfDataCollectionStopDate?.toString() || ''
|
||||||
},
|
},
|
||||||
{}
|
{});
|
||||||
);
|
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logError(TelemetryViews.DataCollectionWizard, 'StopDataCollectionTelemetryFailed', e);
|
logError(TelemetryViews.DataCollectionWizard, 'StopDataCollectionTelemetryFailed', e);
|
||||||
@@ -643,7 +664,10 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
|||||||
this._perfDataCollectionIsCollecting = this._refreshPerfDataCollectionApiResponse.isCollecting;
|
this._perfDataCollectionIsCollecting = this._refreshPerfDataCollectionApiResponse.isCollecting;
|
||||||
|
|
||||||
if (this._perfDataCollectionErrors?.length > 0) {
|
if (this._perfDataCollectionErrors?.length > 0) {
|
||||||
void vscode.window.showInformationMessage(constants.PERF_DATA_COLLECTION_ERROR(this._assessmentApiResponse?.assessmentResult?.name, this._perfDataCollectionErrors));
|
void vscode.window.showInformationMessage(
|
||||||
|
constants.PERF_DATA_COLLECTION_ERROR(
|
||||||
|
this._assessmentApiResponse?.assessmentResult?.name,
|
||||||
|
this._perfDataCollectionErrors));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
@@ -661,33 +685,24 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public performanceCollectionNotStarted(): boolean {
|
public performanceCollectionNotStarted(): boolean {
|
||||||
if (!this._perfDataCollectionStartDate
|
return !this._perfDataCollectionStartDate
|
||||||
&& !this._perfDataCollectionStopDate) {
|
&& !this._perfDataCollectionStopDate;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public performanceCollectionInProgress(): boolean {
|
public performanceCollectionInProgress(): boolean {
|
||||||
if (this._perfDataCollectionStartDate
|
return this._perfDataCollectionStartDate !== undefined
|
||||||
&& !this._perfDataCollectionStopDate) {
|
&& this._perfDataCollectionStopDate === undefined;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public performanceCollectionStopped(): boolean {
|
public performanceCollectionStopped(): boolean {
|
||||||
if (this._perfDataCollectionStartDate
|
return this._perfDataCollectionStartDate !== undefined
|
||||||
&& this._perfDataCollectionStopDate) {
|
&& this._perfDataCollectionStopDate !== undefined;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async generateAssessmentTelemetry(): Promise<void> {
|
private async generateAssessmentTelemetry(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
let serverIssues = this._assessmentResults?.issues.map(i => {
|
const serverIssues = this._assessmentResults?.issues.map(i => {
|
||||||
return {
|
return {
|
||||||
ruleId: i.ruleId,
|
ruleId: i.ruleId,
|
||||||
count: i.impactedObjects.length
|
count: i.impactedObjects.length
|
||||||
@@ -696,18 +711,15 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
|||||||
|
|
||||||
const serverAssessmentErrorsMap: Map<number, number> = new Map();
|
const serverAssessmentErrorsMap: Map<number, number> = new Map();
|
||||||
this._assessmentApiResponse?.assessmentResult?.errors?.forEach(e => {
|
this._assessmentApiResponse?.assessmentResult?.errors?.forEach(e => {
|
||||||
serverAssessmentErrorsMap.set(e.errorId, serverAssessmentErrorsMap.get(e.errorId) ?? 0 + 1);
|
serverAssessmentErrorsMap.set(
|
||||||
|
e.errorId,
|
||||||
|
serverAssessmentErrorsMap.get(e.errorId) ?? 0 + 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
let serverErrors: { errorId: number, count: number }[] = [];
|
const serverErrors: { errorId: number, count: number }[] = [];
|
||||||
serverAssessmentErrorsMap.forEach((v, k) => {
|
serverAssessmentErrorsMap.forEach(
|
||||||
serverErrors.push(
|
(v, k) => serverErrors.push(
|
||||||
{
|
{ errorId: k, count: v }));
|
||||||
errorId: k,
|
|
||||||
count: v
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
const startTime = new Date(this._assessmentApiResponse?.startTime);
|
const startTime = new Date(this._assessmentApiResponse?.startTime);
|
||||||
const endTime = new Date(this._assessmentApiResponse?.endedTime);
|
const endTime = new Date(this._assessmentApiResponse?.endedTime);
|
||||||
@@ -759,27 +771,26 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
|||||||
'assessmentTimeMs': d.assessmentTimeInMilliseconds,
|
'assessmentTimeMs': d.assessmentTimeInMilliseconds,
|
||||||
'numberOfBlockerIssues': d.sqlManagedInstanceTargetReadiness.numOfBlockerIssues,
|
'numberOfBlockerIssues': d.sqlManagedInstanceTargetReadiness.numOfBlockerIssues,
|
||||||
'databaseSizeInMb': d.databaseSize
|
'databaseSizeInMb': d.databaseSize
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
d.items.forEach(i => {
|
d.items.forEach(i => {
|
||||||
databaseWarningsMap.set(i.ruleId, databaseWarningsMap.get(i.ruleId) ?? 0 + i.impactedObjects.length);
|
databaseWarningsMap.set(
|
||||||
|
i.ruleId,
|
||||||
|
databaseWarningsMap.get(i.ruleId) ?? 0 + i.impactedObjects.length);
|
||||||
});
|
});
|
||||||
|
|
||||||
d.errors.forEach(e => {
|
d.errors.forEach(
|
||||||
databaseErrorsMap.set(e.errorId, databaseErrorsMap.get(e.errorId) ?? 0 + 1);
|
e => databaseErrorsMap.set(
|
||||||
});
|
e.errorId,
|
||||||
|
databaseErrorsMap.get(e.errorId) ?? 0 + 1));
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let databaseWarnings: { warningId: string, count: number }[] = [];
|
const databaseWarnings: { warningId: string, count: number }[] = [];
|
||||||
|
|
||||||
databaseWarningsMap.forEach((v, k) => {
|
databaseWarningsMap.forEach(
|
||||||
databaseWarnings.push({
|
(v, k) => databaseWarnings.push(
|
||||||
warningId: k,
|
{ warningId: k, count: v }));
|
||||||
count: v
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
sendSqlMigrationActionEvent(
|
sendSqlMigrationActionEvent(
|
||||||
TelemetryViews.MigrationWizardTargetSelectionPage,
|
TelemetryViews.MigrationWizardTargetSelectionPage,
|
||||||
@@ -788,16 +799,12 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
|||||||
'sessionId': this._sessionId,
|
'sessionId': this._sessionId,
|
||||||
'warnings': JSON.stringify(databaseWarnings)
|
'warnings': JSON.stringify(databaseWarnings)
|
||||||
},
|
},
|
||||||
{}
|
{});
|
||||||
);
|
|
||||||
|
|
||||||
let databaseErrors: { errorId: number, count: number }[] = [];
|
const databaseErrors: { errorId: number, count: number }[] = [];
|
||||||
databaseErrorsMap.forEach((v, k) => {
|
databaseErrorsMap.forEach(
|
||||||
databaseErrors.push({
|
(v, k) => databaseErrors.push(
|
||||||
errorId: k,
|
{ errorId: k, count: v }));
|
||||||
count: v
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
sendSqlMigrationActionEvent(
|
sendSqlMigrationActionEvent(
|
||||||
TelemetryViews.MigrationWizardTargetSelectionPage,
|
TelemetryViews.MigrationWizardTargetSelectionPage,
|
||||||
@@ -806,8 +813,7 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
|||||||
'sessionId': this._sessionId,
|
'sessionId': this._sessionId,
|
||||||
'errors': JSON.stringify(databaseErrors)
|
'errors': JSON.stringify(databaseErrors)
|
||||||
},
|
},
|
||||||
{}
|
{});
|
||||||
);
|
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('error during assessment telemetry:');
|
console.log('error during assessment telemetry:');
|
||||||
@@ -837,13 +843,8 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
|||||||
|
|
||||||
public async getSourceConnectionProfile(): Promise<azdata.connection.ConnectionProfile> {
|
public async getSourceConnectionProfile(): Promise<azdata.connection.ConnectionProfile> {
|
||||||
const sqlConnections = await azdata.connection.getConnections();
|
const sqlConnections = await azdata.connection.getConnections();
|
||||||
return sqlConnections.find((value) => {
|
return sqlConnections.find(
|
||||||
if (value.connectionId === this.sourceConnectionId) {
|
value => value.connectionId === this.sourceConnectionId)!;
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
})!;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public getLocationDisplayName(location: string): Promise<string> {
|
public getLocationDisplayName(location: string): Promise<string> {
|
||||||
@@ -851,22 +852,20 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async getManagedDatabases(): Promise<string[]> {
|
public async getManagedDatabases(): Promise<string[]> {
|
||||||
return (await getSqlManagedInstanceDatabases(this._azureAccount,
|
return (
|
||||||
|
await getSqlManagedInstanceDatabases(this._azureAccount,
|
||||||
this._targetSubscription,
|
this._targetSubscription,
|
||||||
<SqlManagedInstance>this._targetServerInstance)).map(t => t.name);
|
<SqlManagedInstance>this._targetServerInstance)
|
||||||
|
).map(t => t.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async startMigration() {
|
public async startMigration() {
|
||||||
const sqlConnections = await azdata.connection.getConnections();
|
const sqlConnections = await azdata.connection.getConnections();
|
||||||
const currentConnection = sqlConnections.find((value) => {
|
const currentConnection = sqlConnections.find(
|
||||||
if (value.connectionId === this.sourceConnectionId) {
|
value => value.connectionId === this.sourceConnectionId);
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const isOfflineMigration = this._databaseBackup.migrationMode === MigrationMode.OFFLINE;
|
const isOfflineMigration = this._databaseBackup.migrationMode === MigrationMode.OFFLINE;
|
||||||
|
const isSqlDbTarget = this._targetType === MigrationTargetType.SQLDB;
|
||||||
|
|
||||||
const requestBody: StartDatabaseMigrationRequest = {
|
const requestBody: StartDatabaseMigrationRequest = {
|
||||||
location: this._sqlMigrationService?.location!,
|
location: this._sqlMigrationService?.location!,
|
||||||
@@ -877,7 +876,7 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
|||||||
sourceSqlConnection: {
|
sourceSqlConnection: {
|
||||||
dataSource: currentConnection?.serverName!,
|
dataSource: currentConnection?.serverName!,
|
||||||
authentication: this._authenticationType,
|
authentication: this._authenticationType,
|
||||||
username: this._sqlServerUsername,
|
userName: this._sqlServerUsername,
|
||||||
password: this._sqlServerPassword
|
password: this._sqlServerPassword
|
||||||
},
|
},
|
||||||
scope: this._targetServerInstance.id,
|
scope: this._targetServerInstance.id,
|
||||||
@@ -926,6 +925,55 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
|
if (isSqlDbTarget) {
|
||||||
|
const sourceDatabaseName = this._databasesForMigration[i];
|
||||||
|
const targetDatabaseInfo = this._sourceTargetMapping.get(sourceDatabaseName);
|
||||||
|
const totalTables = targetDatabaseInfo?.sourceTables.size ?? 0;
|
||||||
|
// skip databases that don't have tables
|
||||||
|
if (totalTables === 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sourceTables: string[] = [];
|
||||||
|
let selectedTables = 0;
|
||||||
|
targetDatabaseInfo?.sourceTables.forEach(sourceTableInfo => {
|
||||||
|
if (sourceTableInfo.selectedForMigration) {
|
||||||
|
selectedTables++;
|
||||||
|
sourceTables.push(sourceTableInfo.tableName);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// skip databases that don't have tables selected
|
||||||
|
if (selectedTables === 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sqlDbTarget = this._targetServerInstance as AzureSqlDatabaseServer;
|
||||||
|
requestBody.properties.offlineConfiguration = undefined;
|
||||||
|
requestBody.properties.sourceSqlConnection = {
|
||||||
|
dataSource: currentConnection?.serverName!,
|
||||||
|
authentication: this._authenticationType,
|
||||||
|
userName: this._sqlServerUsername,
|
||||||
|
password: this._sqlServerPassword,
|
||||||
|
encryptConnection: true,
|
||||||
|
trustServerCertificate: false,
|
||||||
|
};
|
||||||
|
requestBody.properties.targetSqlConnection = {
|
||||||
|
dataSource: sqlDbTarget.properties.fullyQualifiedDomainName,
|
||||||
|
authentication: MigrationSourceAuthenticationType.Sql,
|
||||||
|
userName: this._targetUserName,
|
||||||
|
password: this._targetPassword,
|
||||||
|
encryptConnection: true,
|
||||||
|
trustServerCertificate: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
// send an empty array when 'all' tables are selected for migration
|
||||||
|
requestBody.properties.tableList = selectedTables === totalTables
|
||||||
|
? []
|
||||||
|
: sourceTables;
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
requestBody.properties.sourceDatabaseName = this._databasesForMigration[i];
|
requestBody.properties.sourceDatabaseName = this._databasesForMigration[i];
|
||||||
const response = await startDatabaseMigration(
|
const response = await startDatabaseMigration(
|
||||||
@@ -969,8 +1017,7 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
|||||||
'wizardEntryPoint': wizardEntryPoint,
|
'wizardEntryPoint': wizardEntryPoint,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
void vscode.window.showInformationMessage(
|
void vscode.window.showInformationMessage(
|
||||||
localize(
|
localize(
|
||||||
@@ -982,7 +1029,10 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
|||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
void vscode.window.showErrorMessage(
|
void vscode.window.showErrorMessage(
|
||||||
localize('sql.migration.starting.migration.error', "An error occurred while starting the migration: '{0}'", e.message));
|
localize(
|
||||||
|
'sql.migration.starting.migration.error',
|
||||||
|
"An error occurred while starting the migration: '{0}'",
|
||||||
|
e.message));
|
||||||
logError(TelemetryViews.MigrationLocalStorage, 'StartMigrationFailed', e);
|
logError(TelemetryViews.MigrationLocalStorage, 'StartMigrationFailed', e);
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
@@ -990,15 +1040,15 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
|||||||
await this.refreshPerfDataCollection();
|
await this.refreshPerfDataCollection();
|
||||||
if ((!this.resumeAssessment || this.retryMigration) && this._perfDataCollectionIsCollecting) {
|
if ((!this.resumeAssessment || this.retryMigration) && this._perfDataCollectionIsCollecting) {
|
||||||
void this.stopPerfDataCollection();
|
void this.stopPerfDataCollection();
|
||||||
void vscode.window.showInformationMessage(constants.AZURE_RECOMMENDATION_STOP_POPUP);
|
void vscode.window.showInformationMessage(
|
||||||
|
constants.AZURE_RECOMMENDATION_STOP_POPUP);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async saveInfo(serverName: string, currentPage: Page): Promise<void> {
|
public async saveInfo(serverName: string, currentPage: Page): Promise<void> {
|
||||||
let saveInfo: SavedInfo;
|
const saveInfo: SavedInfo = {
|
||||||
saveInfo = {
|
|
||||||
closedPage: currentPage,
|
closedPage: currentPage,
|
||||||
databaseAssessment: [],
|
databaseAssessment: [],
|
||||||
databaseList: [],
|
databaseList: [],
|
||||||
@@ -1047,7 +1097,7 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
|||||||
saveInfo.serverAssessment = this._assessmentResults;
|
saveInfo.serverAssessment = this._assessmentResults;
|
||||||
|
|
||||||
if (this._skuRecommendationPerformanceDataSource) {
|
if (this._skuRecommendationPerformanceDataSource) {
|
||||||
let skuRecommendation: SkuRecommendationSavedInfo = {
|
const skuRecommendation: SkuRecommendationSavedInfo = {
|
||||||
skuRecommendationPerformanceDataSource: this._skuRecommendationPerformanceDataSource,
|
skuRecommendationPerformanceDataSource: this._skuRecommendationPerformanceDataSource,
|
||||||
skuRecommendationPerformanceLocation: this._skuRecommendationPerformanceLocation,
|
skuRecommendationPerformanceLocation: this._skuRecommendationPerformanceLocation,
|
||||||
perfDataCollectionStartDate: this._perfDataCollectionStartDate,
|
perfDataCollectionStartDate: this._perfDataCollectionStartDate,
|
||||||
@@ -1072,6 +1122,9 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
|||||||
this._databasesForAssessment = this.savedInfo.databaseAssessment;
|
this._databasesForAssessment = this.savedInfo.databaseAssessment;
|
||||||
this._databasesForMigration = this.savedInfo.databaseList;
|
this._databasesForMigration = this.savedInfo.databaseList;
|
||||||
this._didUpdateDatabasesForMigration = true;
|
this._didUpdateDatabasesForMigration = true;
|
||||||
|
this._didDatabaseMappingChange = true;
|
||||||
|
this.refreshDatabaseBackupPage = true;
|
||||||
|
|
||||||
switch (this._targetType) {
|
switch (this._targetType) {
|
||||||
case MigrationTargetType.SQLMI:
|
case MigrationTargetType.SQLMI:
|
||||||
this._miDbs = this._databasesForMigration;
|
this._miDbs = this._databasesForMigration;
|
||||||
@@ -1079,6 +1132,9 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
|||||||
case MigrationTargetType.SQLVM:
|
case MigrationTargetType.SQLVM:
|
||||||
this._vmDbs = this._databasesForMigration;
|
this._vmDbs = this._databasesForMigration;
|
||||||
break;
|
break;
|
||||||
|
case MigrationTargetType.SQLDB:
|
||||||
|
this._sqldbDbs = this._databasesForMigration;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._azureAccount = this.savedInfo.azureAccount || undefined!;
|
this._azureAccount = this.savedInfo.azureAccount || undefined!;
|
||||||
@@ -1091,7 +1147,6 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
|||||||
|
|
||||||
this._databaseBackup.migrationMode = this.savedInfo.migrationMode || undefined!;
|
this._databaseBackup.migrationMode = this.savedInfo.migrationMode || undefined!;
|
||||||
|
|
||||||
this.refreshDatabaseBackupPage = true;
|
|
||||||
this._sourceDatabaseNames = this._databasesForMigration;
|
this._sourceDatabaseNames = this._databasesForMigration;
|
||||||
this._targetDatabaseNames = this.savedInfo.targetDatabaseNames;
|
this._targetDatabaseNames = this.savedInfo.targetDatabaseNames;
|
||||||
this._databaseBackup.networkContainerType = this.savedInfo.networkContainerType || undefined!;
|
this._databaseBackup.networkContainerType = this.savedInfo.networkContainerType || undefined!;
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ export enum TelemetryAction {
|
|||||||
OnPageLeave = 'OnPageLeave',
|
OnPageLeave = 'OnPageLeave',
|
||||||
GetMISkuRecommendation = 'GetMISkuRecommendation',
|
GetMISkuRecommendation = 'GetMISkuRecommendation',
|
||||||
GetVMSkuRecommendation = 'GetVMSkuRecommendation',
|
GetVMSkuRecommendation = 'GetVMSkuRecommendation',
|
||||||
|
GetSqlDbSkuRecommendation = 'GetSqlDbSkuRecommendation',
|
||||||
GetInstanceRequirements = 'GetInstanceRequirements',
|
GetInstanceRequirements = 'GetInstanceRequirements',
|
||||||
StartDataCollection = 'StartDataCollection',
|
StartDataCollection = 'StartDataCollection',
|
||||||
StopDataCollection = 'StopDataCollection'
|
StopDataCollection = 'StopDataCollection'
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -219,9 +219,9 @@ export class DatabaseSelectorPage extends MigrationWizardPage {
|
|||||||
]
|
]
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
this._disposables.push(this._databaseSelectorTable.onRowSelected(async (e) => {
|
this._disposables.push(
|
||||||
await this.updateValuesOnSelection();
|
this._databaseSelectorTable.onRowSelected(
|
||||||
}));
|
async (e) => await this.updateValuesOnSelection()));
|
||||||
|
|
||||||
// load unfiltered table list and pre-select list of databases saved in state
|
// load unfiltered table list and pre-select list of databases saved in state
|
||||||
await this._filterTableList('', this.migrationStateModel._databasesForAssessment);
|
await this._filterTableList('', this.migrationStateModel._databasesForAssessment);
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
import * as azdata from 'azdata';
|
import * as azdata from 'azdata';
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import { MigrationWizardPage } from '../models/migrationWizardPage';
|
import { MigrationWizardPage } from '../models/migrationWizardPage';
|
||||||
import { MigrationStateModel, NetworkContainerType, StateChangeEvent } from '../models/stateMachine';
|
import { MigrationStateModel, MigrationTargetType, NetworkContainerType, StateChangeEvent } from '../models/stateMachine';
|
||||||
import { CreateSqlMigrationServiceDialog } from '../dialog/createSqlMigrationService/createSqlMigrationServiceDialog';
|
import { CreateSqlMigrationServiceDialog } from '../dialog/createSqlMigrationService/createSqlMigrationServiceDialog';
|
||||||
import * as constants from '../constants/strings';
|
import * as constants from '../constants/strings';
|
||||||
import { WIZARD_INPUT_COMPONENT_WIDTH } from './wizardController';
|
import { WIZARD_INPUT_COMPONENT_WIDTH } from './wizardController';
|
||||||
@@ -17,20 +17,17 @@ import * as utils from '../api/utils';
|
|||||||
import * as styles from '../constants/styles';
|
import * as styles from '../constants/styles';
|
||||||
|
|
||||||
export class IntergrationRuntimePage extends MigrationWizardPage {
|
export class IntergrationRuntimePage extends MigrationWizardPage {
|
||||||
|
|
||||||
private _view!: azdata.ModelView;
|
private _view!: azdata.ModelView;
|
||||||
private _statusLoadingComponent!: azdata.LoadingComponent;
|
private _statusLoadingComponent!: azdata.LoadingComponent;
|
||||||
private _subscription!: azdata.TextComponent;
|
private _subscription!: azdata.TextComponent;
|
||||||
private _location!: azdata.TextComponent;
|
private _location!: azdata.TextComponent;
|
||||||
private _resourceGroupDropdown!: azdata.DropDownComponent;
|
private _resourceGroupDropdown!: azdata.DropDownComponent;
|
||||||
private _dmsDropdown!: azdata.DropDownComponent;
|
private _dmsDropdown!: azdata.DropDownComponent;
|
||||||
|
|
||||||
private _dmsInfoContainer!: azdata.FlexContainer;
|
private _dmsInfoContainer!: azdata.FlexContainer;
|
||||||
private _dmsStatusInfoBox!: azdata.InfoBoxComponent;
|
private _dmsStatusInfoBox!: azdata.InfoBoxComponent;
|
||||||
private _authKeyTable!: azdata.DeclarativeTableComponent;
|
private _authKeyTable!: azdata.DeclarativeTableComponent;
|
||||||
private _refreshButton!: azdata.ButtonComponent;
|
private _refreshButton!: azdata.ButtonComponent;
|
||||||
private _connectionStatusLoader!: azdata.LoadingComponent;
|
private _connectionStatusLoader!: azdata.LoadingComponent;
|
||||||
|
|
||||||
private _copy1!: azdata.ButtonComponent;
|
private _copy1!: azdata.ButtonComponent;
|
||||||
private _copy2!: azdata.ButtonComponent;
|
private _copy2!: azdata.ButtonComponent;
|
||||||
private _refresh1!: azdata.ButtonComponent;
|
private _refresh1!: azdata.ButtonComponent;
|
||||||
@@ -55,31 +52,39 @@ export class IntergrationRuntimePage extends MigrationWizardPage {
|
|||||||
const form = view.modelBuilder.formContainer()
|
const form = view.modelBuilder.formContainer()
|
||||||
.withFormItems([
|
.withFormItems([
|
||||||
{ component: this.migrationServiceDropdownContainer() },
|
{ component: this.migrationServiceDropdownContainer() },
|
||||||
{ component: this._dmsInfoContainer }
|
{ component: this._dmsInfoContainer }])
|
||||||
])
|
|
||||||
.withProps({ CSSStyles: { 'padding-top': '0' } })
|
.withProps({ CSSStyles: { 'padding-top': '0' } })
|
||||||
.component();
|
.component();
|
||||||
|
|
||||||
this._disposables.push(this._view.onClosed(e => {
|
this._disposables.push(
|
||||||
|
this._view.onClosed(e =>
|
||||||
this._disposables.forEach(
|
this._disposables.forEach(
|
||||||
d => { try { d.dispose(); } catch { } });
|
d => { try { d.dispose(); } catch { } })));
|
||||||
}));
|
|
||||||
|
|
||||||
await view.initializeModel(form);
|
await view.initializeModel(form);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async onPageEnter(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise<void> {
|
public async onPageEnter(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise<void> {
|
||||||
|
if (pageChangeInfo.newPage < pageChangeInfo.lastPage) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this._subscription.value = this.migrationStateModel._targetSubscription.name;
|
this._subscription.value = this.migrationStateModel._targetSubscription.name;
|
||||||
this._location.value = await getLocationDisplayName(this.migrationStateModel._targetServerInstance.location);
|
this._location.value = await getLocationDisplayName(
|
||||||
this._dmsInfoContainer.display = (this.migrationStateModel._databaseBackup.networkContainerType === NetworkContainerType.NETWORK_SHARE && this.migrationStateModel._sqlMigrationService) ? 'inline' : 'none';
|
this.migrationStateModel._targetServerInstance.location);
|
||||||
|
|
||||||
|
await utils.updateControlDisplay(
|
||||||
|
this._dmsInfoContainer,
|
||||||
|
this.migrationStateModel._targetType === MigrationTargetType.SQLDB ||
|
||||||
|
this.migrationStateModel._databaseBackup.networkContainerType === NetworkContainerType.NETWORK_SHARE);
|
||||||
|
|
||||||
await this.loadResourceGroupDropdown();
|
await this.loadResourceGroupDropdown();
|
||||||
this.wizard.registerNavigationValidator((pageChangeInfo) => {
|
this.wizard.registerNavigationValidator((pageChangeInfo) => {
|
||||||
|
this.wizard.message = { text: '' };
|
||||||
if (pageChangeInfo.newPage < pageChangeInfo.lastPage) {
|
if (pageChangeInfo.newPage < pageChangeInfo.lastPage) {
|
||||||
this.wizard.message = {
|
|
||||||
text: ''
|
|
||||||
};
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const state = this.migrationStateModel._sqlMigrationService?.properties?.integrationRuntimeState;
|
const state = this.migrationStateModel._sqlMigrationService?.properties?.integrationRuntimeState;
|
||||||
if (!this.migrationStateModel._sqlMigrationService) {
|
if (!this.migrationStateModel._sqlMigrationService) {
|
||||||
this.wizard.message = {
|
this.wizard.message = {
|
||||||
@@ -88,153 +93,150 @@ export class IntergrationRuntimePage extends MigrationWizardPage {
|
|||||||
};
|
};
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (this.migrationStateModel._databaseBackup.networkContainerType === NetworkContainerType.NETWORK_SHARE && state !== 'Online') {
|
if ((this.migrationStateModel._targetType === MigrationTargetType.SQLDB ||
|
||||||
|
this.migrationStateModel._databaseBackup.networkContainerType === NetworkContainerType.NETWORK_SHARE)
|
||||||
|
&& state !== 'Online') {
|
||||||
|
|
||||||
this.wizard.message = {
|
this.wizard.message = {
|
||||||
level: azdata.window.MessageLevel.Error,
|
level: azdata.window.MessageLevel.Error,
|
||||||
text: constants.SERVICE_OFFLINE_ERROR
|
text: constants.SERVICE_OFFLINE_ERROR
|
||||||
};
|
};
|
||||||
return false;
|
return false;
|
||||||
} else {
|
|
||||||
this.wizard.message = {
|
|
||||||
text: ''
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async onPageLeave(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise<void> {
|
public async onPageLeave(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise<void> {
|
||||||
this.wizard.registerNavigationValidator((pageChangeInfo) => {
|
this.wizard.registerNavigationValidator((pageChangeInfo) => true);
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async handleStateChange(e: StateChangeEvent): Promise<void> {
|
protected async handleStateChange(e: StateChangeEvent): Promise<void> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private migrationServiceDropdownContainer(): azdata.FlexContainer {
|
private migrationServiceDropdownContainer(): azdata.FlexContainer {
|
||||||
const descriptionText = this._view.modelBuilder.text().withProps({
|
const descriptionText = this._view.modelBuilder.text()
|
||||||
|
.withProps({
|
||||||
value: constants.IR_PAGE_DESCRIPTION,
|
value: constants.IR_PAGE_DESCRIPTION,
|
||||||
width: WIZARD_INPUT_COMPONENT_WIDTH,
|
width: WIZARD_INPUT_COMPONENT_WIDTH,
|
||||||
CSSStyles: {
|
CSSStyles: { ...styles.BODY_CSS, 'margin-bottom': '16px' }
|
||||||
...styles.BODY_CSS,
|
|
||||||
'margin-bottom': '16px'
|
|
||||||
}
|
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
const subscriptionLabel = this._view.modelBuilder.text().withProps({
|
const subscriptionLabel = this._view.modelBuilder.text()
|
||||||
|
.withProps({
|
||||||
value: constants.SUBSCRIPTION,
|
value: constants.SUBSCRIPTION,
|
||||||
CSSStyles: {
|
CSSStyles: { ...styles.LABEL_CSS }
|
||||||
...styles.LABEL_CSS
|
|
||||||
}
|
|
||||||
}).component();
|
}).component();
|
||||||
this._subscription = this._view.modelBuilder.text().withProps({
|
this._subscription = this._view.modelBuilder.text()
|
||||||
|
.withProps({
|
||||||
enabled: false,
|
enabled: false,
|
||||||
width: WIZARD_INPUT_COMPONENT_WIDTH,
|
width: WIZARD_INPUT_COMPONENT_WIDTH,
|
||||||
CSSStyles: {
|
CSSStyles: { 'margin': '0' }
|
||||||
'margin': '0'
|
|
||||||
}
|
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
const locationLabel = this._view.modelBuilder.text().withProps({
|
const locationLabel = this._view.modelBuilder.text()
|
||||||
|
.withProps({
|
||||||
value: constants.LOCATION,
|
value: constants.LOCATION,
|
||||||
CSSStyles: {
|
CSSStyles: { ...styles.LABEL_CSS, 'margin-top': '1em' }
|
||||||
...styles.LABEL_CSS,
|
|
||||||
'margin-top': '1em'
|
|
||||||
}
|
|
||||||
}).component();
|
}).component();
|
||||||
this._location = this._view.modelBuilder.text().withProps({
|
this._location = this._view.modelBuilder.text()
|
||||||
|
.withProps({
|
||||||
enabled: false,
|
enabled: false,
|
||||||
width: WIZARD_INPUT_COMPONENT_WIDTH,
|
width: WIZARD_INPUT_COMPONENT_WIDTH,
|
||||||
CSSStyles: {
|
CSSStyles: { 'margin': '0' }
|
||||||
'margin': '0'
|
|
||||||
}
|
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
const resourceGroupLabel = this._view.modelBuilder.text().withProps({
|
const resourceGroupLabel = this._view.modelBuilder.text()
|
||||||
|
.withProps({
|
||||||
value: constants.RESOURCE_GROUP,
|
value: constants.RESOURCE_GROUP,
|
||||||
requiredIndicator: true,
|
requiredIndicator: true,
|
||||||
CSSStyles: {
|
CSSStyles: { ...styles.LABEL_CSS }
|
||||||
...styles.LABEL_CSS
|
|
||||||
}
|
|
||||||
}).component();
|
}).component();
|
||||||
this._resourceGroupDropdown = this._view.modelBuilder.dropDown().withProps({
|
this._resourceGroupDropdown = this._view.modelBuilder.dropDown()
|
||||||
|
.withProps({
|
||||||
ariaLabel: constants.RESOURCE_GROUP,
|
ariaLabel: constants.RESOURCE_GROUP,
|
||||||
placeholder: constants.SELECT_RESOURCE_GROUP,
|
placeholder: constants.SELECT_RESOURCE_GROUP,
|
||||||
width: WIZARD_INPUT_COMPONENT_WIDTH,
|
width: WIZARD_INPUT_COMPONENT_WIDTH,
|
||||||
editable: true,
|
editable: true,
|
||||||
required: true,
|
required: true,
|
||||||
fireOnTextChange: true,
|
fireOnTextChange: true,
|
||||||
CSSStyles: {
|
CSSStyles: { 'margin-top': '-1em' }
|
||||||
'margin-top': '-1em'
|
|
||||||
}
|
|
||||||
}).component();
|
}).component();
|
||||||
this._disposables.push(this._resourceGroupDropdown.onValueChanged(async (value) => {
|
this._disposables.push(
|
||||||
|
this._resourceGroupDropdown.onValueChanged(
|
||||||
|
async (value) => {
|
||||||
if (value && value !== 'undefined' && value !== constants.RESOURCE_GROUP_NOT_FOUND) {
|
if (value && value !== 'undefined' && value !== constants.RESOURCE_GROUP_NOT_FOUND) {
|
||||||
const selectedResourceGroup = this.migrationStateModel._resourceGroups.find(rg => rg.name === value);
|
const selectedResourceGroup = this.migrationStateModel._resourceGroups.find(rg => rg.name === value);
|
||||||
this.migrationStateModel._sqlMigrationServiceResourceGroup = (selectedResourceGroup)
|
this.migrationStateModel._sqlMigrationServiceResourceGroup = (selectedResourceGroup)
|
||||||
? selectedResourceGroup
|
? selectedResourceGroup
|
||||||
: undefined!;
|
: undefined!;
|
||||||
await this.populateDms();
|
this.populateDms();
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const migrationServiceDropdownLabel = this._view.modelBuilder.text().withProps({
|
const migrationServiceDropdownLabel = this._view.modelBuilder.text()
|
||||||
|
.withProps({
|
||||||
value: constants.IR_PAGE_TITLE,
|
value: constants.IR_PAGE_TITLE,
|
||||||
requiredIndicator: true,
|
requiredIndicator: true,
|
||||||
CSSStyles: {
|
CSSStyles: { ...styles.LABEL_CSS }
|
||||||
...styles.LABEL_CSS
|
|
||||||
}
|
|
||||||
}).component();
|
}).component();
|
||||||
this._dmsDropdown = this._view.modelBuilder.dropDown().withProps({
|
this._dmsDropdown = this._view.modelBuilder.dropDown()
|
||||||
|
.withProps({
|
||||||
ariaLabel: constants.IR_PAGE_TITLE,
|
ariaLabel: constants.IR_PAGE_TITLE,
|
||||||
placeholder: constants.SELECT_RESOURCE_GROUP_PROMPT,
|
placeholder: constants.SELECT_RESOURCE_GROUP_PROMPT,
|
||||||
width: WIZARD_INPUT_COMPONENT_WIDTH,
|
width: WIZARD_INPUT_COMPONENT_WIDTH,
|
||||||
editable: true,
|
editable: true,
|
||||||
required: true,
|
required: true,
|
||||||
fireOnTextChange: true,
|
fireOnTextChange: true,
|
||||||
CSSStyles: {
|
CSSStyles: { 'margin-top': '-1em' }
|
||||||
'margin-top': '-1em'
|
|
||||||
}
|
|
||||||
}).component();
|
}).component();
|
||||||
this._disposables.push(this._dmsDropdown.onValueChanged(async (value) => {
|
this._disposables.push(
|
||||||
|
this._dmsDropdown.onValueChanged(
|
||||||
|
async (value) => {
|
||||||
if (value && value !== 'undefined' && value !== constants.SQL_MIGRATION_SERVICE_NOT_FOUND_ERROR) {
|
if (value && value !== 'undefined' && value !== constants.SQL_MIGRATION_SERVICE_NOT_FOUND_ERROR) {
|
||||||
if (this.migrationStateModel._databaseBackup.networkContainerType === NetworkContainerType.NETWORK_SHARE) {
|
this.wizard.message = { text: '' };
|
||||||
this._dmsInfoContainer.display = 'inline';
|
|
||||||
}
|
await utils.updateControlDisplay(
|
||||||
this.wizard.message = {
|
this._dmsInfoContainer,
|
||||||
text: ''
|
this.migrationStateModel._targetType === MigrationTargetType.SQLDB ||
|
||||||
};
|
this.migrationStateModel._databaseBackup.networkContainerType === NetworkContainerType.NETWORK_SHARE);
|
||||||
const selectedDms = this.migrationStateModel._sqlMigrationServices.find(dms => dms.name === value && dms.properties.resourceGroup.toLowerCase() === this.migrationStateModel._sqlMigrationServiceResourceGroup.name.toLowerCase());
|
|
||||||
|
const selectedDms = this.migrationStateModel._sqlMigrationServices.find(
|
||||||
|
dms => dms.name === value && dms.properties.resourceGroup.toLowerCase() === this.migrationStateModel._sqlMigrationServiceResourceGroup.name.toLowerCase());
|
||||||
if (selectedDms) {
|
if (selectedDms) {
|
||||||
this.migrationStateModel._sqlMigrationService = selectedDms;
|
this.migrationStateModel._sqlMigrationService = selectedDms;
|
||||||
await this.loadMigrationServiceStatus();
|
await this.loadStatus();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.migrationStateModel._sqlMigrationService = undefined;
|
this.migrationStateModel._sqlMigrationService = undefined;
|
||||||
this._dmsInfoContainer.display = 'none';
|
await utils.updateControlDisplay(this._dmsInfoContainer, false);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const createNewMigrationService = this._view.modelBuilder.hyperlink().withProps({
|
const createNewMigrationService = this._view.modelBuilder.hyperlink()
|
||||||
|
.withProps({
|
||||||
label: constants.CREATE_NEW,
|
label: constants.CREATE_NEW,
|
||||||
ariaLabel: constants.CREATE_NEW_MIGRATION_SERVICE,
|
ariaLabel: constants.CREATE_NEW_MIGRATION_SERVICE,
|
||||||
url: '',
|
url: '',
|
||||||
CSSStyles: {
|
CSSStyles: { ...styles.BODY_CSS }
|
||||||
...styles.BODY_CSS
|
|
||||||
}
|
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
this._disposables.push(createNewMigrationService.onDidClick(async (e) => {
|
this._disposables.push(
|
||||||
|
createNewMigrationService.onDidClick(
|
||||||
|
async (e) => {
|
||||||
const dialog = new CreateSqlMigrationServiceDialog();
|
const dialog = new CreateSqlMigrationServiceDialog();
|
||||||
const createdDmsResult = await dialog.createNewDms(this.migrationStateModel, (<azdata.CategoryValue>this._resourceGroupDropdown.value).displayName);
|
const createdDmsResult = await dialog.createNewDms(
|
||||||
|
this.migrationStateModel,
|
||||||
|
(<azdata.CategoryValue>this._resourceGroupDropdown.value).displayName);
|
||||||
|
|
||||||
this.migrationStateModel._sqlMigrationServiceResourceGroup = createdDmsResult.resourceGroup;
|
this.migrationStateModel._sqlMigrationServiceResourceGroup = createdDmsResult.resourceGroup;
|
||||||
this.migrationStateModel._sqlMigrationService = createdDmsResult.service;
|
this.migrationStateModel._sqlMigrationService = createdDmsResult.service;
|
||||||
await this.loadResourceGroupDropdown();
|
await this.loadResourceGroupDropdown();
|
||||||
await this.populateDms();
|
this.populateDms();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const flexContainer = this._view.modelBuilder.flexContainer().withItems([
|
return this._view.modelBuilder.flexContainer()
|
||||||
|
.withItems([
|
||||||
descriptionText,
|
descriptionText,
|
||||||
subscriptionLabel,
|
subscriptionLabel,
|
||||||
this._subscription,
|
this._subscription,
|
||||||
@@ -244,27 +246,24 @@ export class IntergrationRuntimePage extends MigrationWizardPage {
|
|||||||
this._resourceGroupDropdown,
|
this._resourceGroupDropdown,
|
||||||
migrationServiceDropdownLabel,
|
migrationServiceDropdownLabel,
|
||||||
this._dmsDropdown,
|
this._dmsDropdown,
|
||||||
createNewMigrationService
|
createNewMigrationService])
|
||||||
]).withLayout({
|
.withLayout({ flexFlow: 'column' })
|
||||||
flexFlow: 'column'
|
.component();
|
||||||
}).component();
|
|
||||||
return flexContainer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private createDMSDetailsContainer(): azdata.FlexContainer {
|
private createDMSDetailsContainer(): azdata.FlexContainer {
|
||||||
const container = this._view.modelBuilder.flexContainer().withLayout({
|
const container = this._view.modelBuilder.flexContainer()
|
||||||
flexFlow: 'column'
|
.withLayout({ flexFlow: 'column' })
|
||||||
}).component();
|
.component();
|
||||||
|
|
||||||
const connectionStatusLabel = this._view.modelBuilder.text().withProps({
|
const connectionStatusLabel = this._view.modelBuilder.text()
|
||||||
|
.withProps({
|
||||||
value: constants.SERVICE_CONNECTION_STATUS,
|
value: constants.SERVICE_CONNECTION_STATUS,
|
||||||
CSSStyles: {
|
CSSStyles: { ...styles.LABEL_CSS, 'width': '130px' }
|
||||||
...styles.LABEL_CSS,
|
|
||||||
'width': '130px'
|
|
||||||
}
|
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
this._refreshButton = this._view.modelBuilder.button().withProps({
|
this._refreshButton = this._view.modelBuilder.button()
|
||||||
|
.withProps({
|
||||||
iconWidth: '18px',
|
iconWidth: '18px',
|
||||||
iconHeight: '18px',
|
iconHeight: '18px',
|
||||||
iconPath: IconPathHelper.refresh,
|
iconPath: IconPathHelper.refresh,
|
||||||
@@ -273,73 +272,73 @@ export class IntergrationRuntimePage extends MigrationWizardPage {
|
|||||||
ariaLabel: constants.REFRESH,
|
ariaLabel: constants.REFRESH,
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
this._disposables.push(this._refreshButton.onDidClick(async (e) => {
|
this._disposables.push(
|
||||||
this._connectionStatusLoader.loading = true;
|
this._refreshButton.onDidClick(
|
||||||
try {
|
async (e) => this.loadStatus()));
|
||||||
await this.loadStatus();
|
|
||||||
} finally {
|
|
||||||
this._connectionStatusLoader.loading = false;
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
const connectionLabelContainer = this._view.modelBuilder.flexContainer().component();
|
const connectionLabelContainer = this._view.modelBuilder.flexContainer()
|
||||||
connectionLabelContainer.addItem(connectionStatusLabel, {
|
.component();
|
||||||
flex: '0'
|
connectionLabelContainer.addItem(
|
||||||
});
|
connectionStatusLabel,
|
||||||
connectionLabelContainer.addItem(this._refreshButton, {
|
{ flex: '0' });
|
||||||
flex: '0',
|
connectionLabelContainer.addItem(
|
||||||
CSSStyles: { 'margin-right': '10px' }
|
this._refreshButton,
|
||||||
});
|
{ flex: '0', CSSStyles: { 'margin-right': '10px' } });
|
||||||
|
|
||||||
const statusContainer = this._view.modelBuilder.flexContainer().withLayout({
|
const statusContainer = this._view.modelBuilder.flexContainer()
|
||||||
flexFlow: 'column'
|
.withLayout({ flexFlow: 'column' })
|
||||||
}).component();
|
.component();
|
||||||
|
|
||||||
this._dmsStatusInfoBox = this._view.modelBuilder.infoBox().withProps({
|
this._dmsStatusInfoBox = this._view.modelBuilder.infoBox()
|
||||||
|
.withProps({
|
||||||
width: WIZARD_INPUT_COMPONENT_WIDTH,
|
width: WIZARD_INPUT_COMPONENT_WIDTH,
|
||||||
style: 'error',
|
style: 'error',
|
||||||
text: '',
|
text: '',
|
||||||
CSSStyles: {
|
CSSStyles: { ...styles.BODY_CSS }
|
||||||
...styles.BODY_CSS
|
|
||||||
}
|
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
const authenticationKeysLabel = this._view.modelBuilder.text().withProps({
|
const authenticationKeysLabel = this._view.modelBuilder.text()
|
||||||
|
.withProps({
|
||||||
value: constants.AUTHENTICATION_KEYS,
|
value: constants.AUTHENTICATION_KEYS,
|
||||||
CSSStyles: {
|
CSSStyles: { ...styles.LABEL_CSS }
|
||||||
...styles.LABEL_CSS
|
|
||||||
}
|
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
this._copy1 = this._view.modelBuilder.button().withProps({
|
this._copy1 = this._view.modelBuilder.button()
|
||||||
|
.withProps({
|
||||||
title: constants.COPY_KEY1,
|
title: constants.COPY_KEY1,
|
||||||
iconPath: IconPathHelper.copy,
|
iconPath: IconPathHelper.copy,
|
||||||
ariaLabel: constants.COPY_KEY1,
|
ariaLabel: constants.COPY_KEY1,
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
this._disposables.push(this._copy1.onDidClick(async (e) => {
|
this._disposables.push(
|
||||||
|
this._copy1.onDidClick(
|
||||||
|
async (e) => {
|
||||||
await vscode.env.clipboard.writeText(<string>this._authKeyTable.dataValues![0][1].value);
|
await vscode.env.clipboard.writeText(<string>this._authKeyTable.dataValues![0][1].value);
|
||||||
void vscode.window.showInformationMessage(constants.SERVICE_KEY1_COPIED_HELP);
|
void vscode.window.showInformationMessage(constants.SERVICE_KEY1_COPIED_HELP);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this._copy2 = this._view.modelBuilder.button().withProps({
|
this._copy2 = this._view.modelBuilder.button()
|
||||||
|
.withProps({
|
||||||
title: constants.COPY_KEY2,
|
title: constants.COPY_KEY2,
|
||||||
iconPath: IconPathHelper.copy,
|
iconPath: IconPathHelper.copy,
|
||||||
ariaLabel: constants.COPY_KEY2,
|
ariaLabel: constants.COPY_KEY2,
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
this._disposables.push(this._copy2.onDidClick(async (e) => {
|
this._disposables.push(
|
||||||
|
this._copy2.onDidClick(async (e) => {
|
||||||
await vscode.env.clipboard.writeText(<string>this._authKeyTable.dataValues![1][1].value);
|
await vscode.env.clipboard.writeText(<string>this._authKeyTable.dataValues![1][1].value);
|
||||||
void vscode.window.showInformationMessage(constants.SERVICE_KEY2_COPIED_HELP);
|
void vscode.window.showInformationMessage(constants.SERVICE_KEY2_COPIED_HELP);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this._refresh1 = this._view.modelBuilder.button().withProps({
|
this._refresh1 = this._view.modelBuilder.button()
|
||||||
|
.withProps({
|
||||||
title: constants.REFRESH_KEY1,
|
title: constants.REFRESH_KEY1,
|
||||||
iconPath: IconPathHelper.refresh,
|
iconPath: IconPathHelper.refresh,
|
||||||
ariaLabel: constants.REFRESH_KEY1,
|
ariaLabel: constants.REFRESH_KEY1,
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
this._refresh2 = this._view.modelBuilder.button().withProps({
|
this._refresh2 = this._view.modelBuilder.button()
|
||||||
|
.withProps({
|
||||||
title: constants.REFRESH_KEY2,
|
title: constants.REFRESH_KEY2,
|
||||||
iconPath: IconPathHelper.refresh,
|
iconPath: IconPathHelper.refresh,
|
||||||
ariaLabel: constants.REFRESH_KEY2,
|
ariaLabel: constants.REFRESH_KEY2,
|
||||||
@@ -350,65 +349,69 @@ export class IntergrationRuntimePage extends MigrationWizardPage {
|
|||||||
statusContainer.addItems([
|
statusContainer.addItems([
|
||||||
this._dmsStatusInfoBox,
|
this._dmsStatusInfoBox,
|
||||||
authenticationKeysLabel,
|
authenticationKeysLabel,
|
||||||
this._authKeyTable
|
this._authKeyTable]);
|
||||||
]);
|
|
||||||
|
|
||||||
this._connectionStatusLoader = this._view.modelBuilder.loadingComponent().withItem(
|
this._connectionStatusLoader = this._view.modelBuilder.loadingComponent()
|
||||||
statusContainer
|
.withItem(statusContainer)
|
||||||
).withProps({
|
.withProps({ loading: false })
|
||||||
loading: false
|
.component();
|
||||||
}).component();
|
|
||||||
|
|
||||||
container.addItems(
|
container.addItems([
|
||||||
[
|
|
||||||
connectionLabelContainer,
|
connectionLabelContainer,
|
||||||
this._connectionStatusLoader
|
this._connectionStatusLoader]);
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
return container;
|
return container;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async loadResourceGroupDropdown(): Promise<void> {
|
public async loadResourceGroupDropdown(): Promise<void> {
|
||||||
|
try {
|
||||||
this._resourceGroupDropdown.loading = true;
|
this._resourceGroupDropdown.loading = true;
|
||||||
this._dmsDropdown.loading = true;
|
this._dmsDropdown.loading = true;
|
||||||
try {
|
|
||||||
this.migrationStateModel._sqlMigrationServices = await utils.getAzureSqlMigrationServices(this.migrationStateModel._azureAccount, this.migrationStateModel._targetSubscription);
|
this.migrationStateModel._sqlMigrationServices = await utils.getAzureSqlMigrationServices(
|
||||||
this.migrationStateModel._resourceGroups = await utils.getSqlMigrationServiceResourceGroups(this.migrationStateModel._sqlMigrationServices, this.migrationStateModel._location);
|
this.migrationStateModel._azureAccount,
|
||||||
this._resourceGroupDropdown.values = await utils.getAzureResourceGroupsDropdownValues(this.migrationStateModel._resourceGroups);
|
this.migrationStateModel._targetSubscription);
|
||||||
const resourceGroup = (this.migrationStateModel._sqlMigrationService)
|
|
||||||
|
this.migrationStateModel._resourceGroups = utils.getServiceResourceGroupsByLocation(
|
||||||
|
this.migrationStateModel._sqlMigrationServices,
|
||||||
|
this.migrationStateModel._location);
|
||||||
|
|
||||||
|
this._resourceGroupDropdown.values = utils.getResourceDropdownValues(
|
||||||
|
this.migrationStateModel._resourceGroups,
|
||||||
|
constants.RESOURCE_GROUP_NOT_FOUND);
|
||||||
|
|
||||||
|
const resourceGroup = this.migrationStateModel._sqlMigrationService
|
||||||
? getFullResourceGroupFromId(this.migrationStateModel._sqlMigrationService?.id)
|
? getFullResourceGroupFromId(this.migrationStateModel._sqlMigrationService?.id)
|
||||||
: undefined;
|
: undefined;
|
||||||
utils.selectDefaultDropdownValue(this._resourceGroupDropdown, resourceGroup, false);
|
utils.selectDefaultDropdownValue(this._resourceGroupDropdown, resourceGroup, false);
|
||||||
} finally {
|
} finally {
|
||||||
|
this._dmsDropdown.loading = false;
|
||||||
this._resourceGroupDropdown.loading = false;
|
this._resourceGroupDropdown.loading = false;
|
||||||
this._dmsDropdown.loading = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async populateDms(): Promise<void> {
|
public populateDms(): void {
|
||||||
|
try {
|
||||||
this._dmsDropdown.loading = true;
|
this._dmsDropdown.loading = true;
|
||||||
try {
|
this._dmsDropdown.values = utils.getAzureResourceDropdownValues(
|
||||||
this._dmsDropdown.values = await utils.getAzureSqlMigrationServicesDropdownValues(this.migrationStateModel._sqlMigrationServices, this.migrationStateModel._location, this.migrationStateModel._sqlMigrationServiceResourceGroup);
|
this.migrationStateModel._sqlMigrationServices,
|
||||||
utils.selectDefaultDropdownValue(this._dmsDropdown, this.migrationStateModel._sqlMigrationService?.id, false);
|
this.migrationStateModel._location,
|
||||||
|
this.migrationStateModel._sqlMigrationServiceResourceGroup.name,
|
||||||
|
constants.SQL_MIGRATION_SERVICE_NOT_FOUND_ERROR);
|
||||||
|
|
||||||
|
utils.selectDefaultDropdownValue(
|
||||||
|
this._dmsDropdown,
|
||||||
|
this.migrationStateModel._sqlMigrationService?.id,
|
||||||
|
false);
|
||||||
} finally {
|
} finally {
|
||||||
this._dmsDropdown.loading = false;
|
this._dmsDropdown.loading = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async loadMigrationServiceStatus(): Promise<void> {
|
|
||||||
this._statusLoadingComponent.loading = true;
|
|
||||||
try {
|
|
||||||
await this.loadStatus();
|
|
||||||
} catch (error) {
|
|
||||||
logError(TelemetryViews.MigrationWizardIntegrationRuntimePage, 'ErrorLoadingMigrationServiceStatus', error);
|
|
||||||
} finally {
|
|
||||||
this._statusLoadingComponent.loading = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async loadStatus(): Promise<void> {
|
private async loadStatus(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
|
this._statusLoadingComponent.loading = true;
|
||||||
|
|
||||||
if (this.migrationStateModel._sqlMigrationService) {
|
if (this.migrationStateModel._sqlMigrationService) {
|
||||||
const migrationService = await getSqlMigrationService(
|
const migrationService = await getSqlMigrationService(
|
||||||
this.migrationStateModel._azureAccount,
|
this.migrationStateModel._azureAccount,
|
||||||
@@ -436,12 +439,15 @@ export class IntergrationRuntimePage extends MigrationWizardPage {
|
|||||||
const state = migrationService.properties.integrationRuntimeState;
|
const state = migrationService.properties.integrationRuntimeState;
|
||||||
if (state === 'Online') {
|
if (state === 'Online') {
|
||||||
await this._dmsStatusInfoBox.updateProperties(<azdata.InfoBoxComponentProperties>{
|
await this._dmsStatusInfoBox.updateProperties(<azdata.InfoBoxComponentProperties>{
|
||||||
text: constants.SERVICE_READY(this.migrationStateModel._sqlMigrationService!.name, this.migrationStateModel._nodeNames.join(', ')),
|
text: constants.SERVICE_READY(
|
||||||
|
this.migrationStateModel._sqlMigrationService!.name,
|
||||||
|
this.migrationStateModel._nodeNames.join(', ')),
|
||||||
style: 'success'
|
style: 'success'
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await this._dmsStatusInfoBox.updateProperties(<azdata.InfoBoxComponentProperties>{
|
await this._dmsStatusInfoBox.updateProperties(<azdata.InfoBoxComponentProperties>{
|
||||||
text: constants.SERVICE_NOT_READY(this.migrationStateModel._sqlMigrationService!.name),
|
text: constants.SERVICE_NOT_READY(
|
||||||
|
this.migrationStateModel._sqlMigrationService!.name),
|
||||||
style: 'error'
|
style: 'error'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -464,19 +470,21 @@ export class IntergrationRuntimePage extends MigrationWizardPage {
|
|||||||
.withItems([this._copy2, this._refresh2])
|
.withItems([this._copy2, this._refresh2])
|
||||||
.component()
|
.component()
|
||||||
}
|
}
|
||||||
]
|
]];
|
||||||
];
|
|
||||||
|
|
||||||
await this._authKeyTable.setDataValues(data);
|
await this._authKeyTable.setDataValues(data);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logError(TelemetryViews.IntegrationRuntimePage, 'ErrorLoadingStatus', e);
|
logError(TelemetryViews.IntegrationRuntimePage, 'ErrorLoadingStatus', e);
|
||||||
|
} finally {
|
||||||
|
this._statusLoadingComponent.loading = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createAuthenticationKeyTable(view: azdata.ModelView,): azdata.DeclarativeTableComponent {
|
export function createAuthenticationKeyTable(view: azdata.ModelView,): azdata.DeclarativeTableComponent {
|
||||||
const authKeyTable = view.modelBuilder.declarativeTable().withProps({
|
const authKeyTable = view.modelBuilder.declarativeTable()
|
||||||
|
.withProps({
|
||||||
ariaLabel: constants.DATABASE_MIGRATION_SERVICE_AUTHENTICATION_KEYS,
|
ariaLabel: constants.DATABASE_MIGRATION_SERVICE_AUTHENTICATION_KEYS,
|
||||||
columns: [
|
columns: [
|
||||||
{
|
{
|
||||||
@@ -484,45 +492,27 @@ export function createAuthenticationKeyTable(view: azdata.ModelView,): azdata.De
|
|||||||
valueType: azdata.DeclarativeDataType.string,
|
valueType: azdata.DeclarativeDataType.string,
|
||||||
width: '50px',
|
width: '50px',
|
||||||
isReadOnly: true,
|
isReadOnly: true,
|
||||||
rowCssStyles: {
|
rowCssStyles: { ...styles.BODY_CSS },
|
||||||
...styles.BODY_CSS
|
headerCssStyles: { ...styles.BODY_CSS, 'font-weight': '600' }
|
||||||
},
|
|
||||||
headerCssStyles: {
|
|
||||||
...styles.BODY_CSS,
|
|
||||||
'font-weight': '600'
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
displayName: constants.AUTH_KEY_COLUMN_HEADER,
|
displayName: constants.AUTH_KEY_COLUMN_HEADER,
|
||||||
valueType: azdata.DeclarativeDataType.string,
|
valueType: azdata.DeclarativeDataType.string,
|
||||||
width: '500px',
|
width: '500px',
|
||||||
isReadOnly: true,
|
isReadOnly: true,
|
||||||
rowCssStyles: {
|
rowCssStyles: { ...styles.BODY_CSS },
|
||||||
...styles.BODY_CSS,
|
headerCssStyles: { ...styles.BODY_CSS, 'font-weight': '600' }
|
||||||
|
|
||||||
},
|
|
||||||
headerCssStyles: {
|
|
||||||
...styles.BODY_CSS,
|
|
||||||
'font-weight': '600'
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
displayName: '',
|
displayName: '',
|
||||||
valueType: azdata.DeclarativeDataType.component,
|
valueType: azdata.DeclarativeDataType.component,
|
||||||
width: '30px',
|
width: '30px',
|
||||||
isReadOnly: true,
|
isReadOnly: true,
|
||||||
rowCssStyles: {
|
rowCssStyles: { ...styles.BODY_CSS },
|
||||||
...styles.BODY_CSS
|
headerCssStyles: { ...styles.BODY_CSS }
|
||||||
},
|
|
||||||
headerCssStyles: {
|
|
||||||
...styles.BODY_CSS
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
CSSStyles: {
|
CSSStyles: { 'margin-top': '5px', 'width': WIZARD_INPUT_COMPONENT_WIDTH }
|
||||||
'margin-top': '5px',
|
|
||||||
'width': WIZARD_INPUT_COMPONENT_WIDTH
|
|
||||||
}
|
|
||||||
}).component();
|
}).component();
|
||||||
return authKeyTable;
|
return authKeyTable;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,17 +6,22 @@
|
|||||||
import * as azdata from 'azdata';
|
import * as azdata from 'azdata';
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import { MigrationWizardPage } from '../models/migrationWizardPage';
|
import { MigrationWizardPage } from '../models/migrationWizardPage';
|
||||||
import { MigrationMode, MigrationStateModel, StateChangeEvent } from '../models/stateMachine';
|
import { MigrationMode, MigrationStateModel, MigrationTargetType, StateChangeEvent } from '../models/stateMachine';
|
||||||
import * as constants from '../constants/strings';
|
import * as constants from '../constants/strings';
|
||||||
import * as styles from '../constants/styles';
|
import * as styles from '../constants/styles';
|
||||||
|
|
||||||
export class MigrationModePage extends MigrationWizardPage {
|
export class MigrationModePage extends MigrationWizardPage {
|
||||||
private _view!: azdata.ModelView;
|
private _view!: azdata.ModelView;
|
||||||
private originalMigrationMode!: MigrationMode;
|
private _onlineButton!: azdata.RadioButtonComponent;
|
||||||
|
private _offlineButton!: azdata.RadioButtonComponent;
|
||||||
|
private _originalMigrationMode!: MigrationMode;
|
||||||
private _disposables: vscode.Disposable[] = [];
|
private _disposables: vscode.Disposable[] = [];
|
||||||
|
|
||||||
constructor(wizard: azdata.window.Wizard, migrationStateModel: MigrationStateModel) {
|
constructor(wizard: azdata.window.Wizard, migrationStateModel: MigrationStateModel) {
|
||||||
super(wizard, azdata.window.createWizardPage(constants.DATABASE_BACKUP_MIGRATION_MODE_LABEL, 'MigrationModePage'), migrationStateModel);
|
super(
|
||||||
|
wizard,
|
||||||
|
azdata.window.createWizardPage(constants.DATABASE_BACKUP_MIGRATION_MODE_LABEL, 'MigrationModePage'),
|
||||||
|
migrationStateModel);
|
||||||
this.migrationStateModel._databaseBackup.migrationMode = this.migrationStateModel._databaseBackup.migrationMode || MigrationMode.ONLINE;
|
this.migrationStateModel._databaseBackup.migrationMode = this.migrationStateModel._databaseBackup.migrationMode || MigrationMode.ONLINE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,115 +30,103 @@ export class MigrationModePage extends MigrationWizardPage {
|
|||||||
|
|
||||||
const pageDescription = {
|
const pageDescription = {
|
||||||
title: '',
|
title: '',
|
||||||
component: view.modelBuilder.text().withProps({
|
component: view.modelBuilder.text()
|
||||||
|
.withProps({
|
||||||
value: constants.DATABASE_BACKUP_MIGRATION_MODE_DESCRIPTION,
|
value: constants.DATABASE_BACKUP_MIGRATION_MODE_DESCRIPTION,
|
||||||
CSSStyles: {
|
CSSStyles: { ...styles.BODY_CSS, 'margin': '0' }
|
||||||
...styles.BODY_CSS,
|
|
||||||
'margin': '0'
|
|
||||||
}
|
|
||||||
}).component()
|
}).component()
|
||||||
};
|
};
|
||||||
|
|
||||||
const form = view.modelBuilder.formContainer()
|
const form = view.modelBuilder.formContainer()
|
||||||
.withFormItems(
|
.withFormItems([
|
||||||
[
|
|
||||||
pageDescription,
|
pageDescription,
|
||||||
this.migrationModeContainer(),
|
this.migrationModeContainer()])
|
||||||
]
|
.withProps({ CSSStyles: { 'padding-top': '0' } })
|
||||||
).withProps({
|
.component();
|
||||||
CSSStyles: {
|
|
||||||
'padding-top': '0'
|
this._disposables.push(
|
||||||
}
|
this._view.onClosed(
|
||||||
}).component();
|
e => this._disposables.forEach(
|
||||||
|
d => { try { d.dispose(); } catch { } })));
|
||||||
|
|
||||||
this._disposables.push(this._view.onClosed(e => {
|
|
||||||
this._disposables.forEach(
|
|
||||||
d => { try { d.dispose(); } catch { } });
|
|
||||||
}));
|
|
||||||
await view.initializeModel(form);
|
await view.initializeModel(form);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async onPageEnter(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise<void> {
|
public async onPageEnter(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise<void> {
|
||||||
this.originalMigrationMode = this.migrationStateModel._databaseBackup.migrationMode;
|
if (pageChangeInfo.newPage < pageChangeInfo.lastPage) {
|
||||||
this.wizard.registerNavigationValidator((e) => {
|
return;
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
public async onPageLeave(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise<void> {
|
|
||||||
if (this.originalMigrationMode !== this.migrationStateModel._databaseBackup.migrationMode) {
|
|
||||||
this.migrationStateModel.refreshDatabaseBackupPage = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.wizard.registerNavigationValidator((e) => {
|
const isSqlDbTarget = this.migrationStateModel._targetType === MigrationTargetType.SQLDB;
|
||||||
return true;
|
this._onlineButton.enabled = !isSqlDbTarget;
|
||||||
});
|
if (isSqlDbTarget) {
|
||||||
|
this.migrationStateModel._databaseBackup.migrationMode = MigrationMode.OFFLINE;
|
||||||
|
this._offlineButton.checked = true;
|
||||||
|
await this._offlineButton.focus();
|
||||||
}
|
}
|
||||||
|
this._originalMigrationMode = this.migrationStateModel._databaseBackup.migrationMode;
|
||||||
|
this.wizard.registerNavigationValidator((e) => true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async onPageLeave(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise<void> {
|
||||||
|
if (this._originalMigrationMode !== this.migrationStateModel._databaseBackup.migrationMode) {
|
||||||
|
this.migrationStateModel.refreshDatabaseBackupPage = true;
|
||||||
|
}
|
||||||
|
this.wizard.registerNavigationValidator((e) => true);
|
||||||
|
}
|
||||||
|
|
||||||
protected async handleStateChange(e: StateChangeEvent): Promise<void> {
|
protected async handleStateChange(e: StateChangeEvent): Promise<void> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private migrationModeContainer(): azdata.FormComponent {
|
private migrationModeContainer(): azdata.FormComponent {
|
||||||
const buttonGroup = 'migrationMode';
|
const buttonGroup = 'migrationMode';
|
||||||
|
this._onlineButton = this._view.modelBuilder.radioButton()
|
||||||
const onlineButton = this._view.modelBuilder.radioButton().withProps({
|
.withProps({
|
||||||
label: constants.DATABASE_BACKUP_MIGRATION_MODE_ONLINE_LABEL,
|
label: constants.DATABASE_BACKUP_MIGRATION_MODE_ONLINE_LABEL,
|
||||||
name: buttonGroup,
|
name: buttonGroup,
|
||||||
checked: this.migrationStateModel._databaseBackup.migrationMode === MigrationMode.ONLINE,
|
checked: this.migrationStateModel._databaseBackup.migrationMode === MigrationMode.ONLINE,
|
||||||
CSSStyles: {
|
CSSStyles: { ...styles.LABEL_CSS, },
|
||||||
...styles.LABEL_CSS,
|
|
||||||
},
|
|
||||||
}).component();
|
}).component();
|
||||||
|
const onlineDescription = this._view.modelBuilder.text()
|
||||||
const onlineDescription = this._view.modelBuilder.text().withProps({
|
.withProps({
|
||||||
value: constants.DATABASE_BACKUP_MIGRATION_MODE_ONLINE_DESCRIPTION,
|
value: constants.DATABASE_BACKUP_MIGRATION_MODE_ONLINE_DESCRIPTION,
|
||||||
CSSStyles: {
|
CSSStyles: { ...styles.NOTE_CSS, 'margin-left': '20px' }
|
||||||
...styles.NOTE_CSS,
|
|
||||||
'margin-left': '20px'
|
|
||||||
}
|
|
||||||
}).component();
|
}).component();
|
||||||
|
this._disposables.push(
|
||||||
this._disposables.push(onlineButton.onDidChangeCheckedState((e) => {
|
this._onlineButton.onDidChangeCheckedState(checked => {
|
||||||
if (e) {
|
if (checked) {
|
||||||
this.migrationStateModel._databaseBackup.migrationMode = MigrationMode.ONLINE;
|
this.migrationStateModel._databaseBackup.migrationMode = MigrationMode.ONLINE;
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const offlineButton = this._view.modelBuilder.radioButton().withProps({
|
this._offlineButton = this._view.modelBuilder.radioButton()
|
||||||
|
.withProps({
|
||||||
label: constants.DATABASE_BACKUP_MIGRATION_MODE_OFFLINE_LABEL,
|
label: constants.DATABASE_BACKUP_MIGRATION_MODE_OFFLINE_LABEL,
|
||||||
name: buttonGroup,
|
name: buttonGroup,
|
||||||
checked: this.migrationStateModel._databaseBackup.migrationMode === MigrationMode.OFFLINE,
|
checked: this.migrationStateModel._databaseBackup.migrationMode === MigrationMode.OFFLINE,
|
||||||
CSSStyles: {
|
CSSStyles: { ...styles.LABEL_CSS, 'margin-top': '12px' },
|
||||||
...styles.LABEL_CSS,
|
|
||||||
'margin-top': '12px'
|
|
||||||
},
|
|
||||||
}).component();
|
}).component();
|
||||||
|
const offlineDescription = this._view.modelBuilder.text()
|
||||||
const offlineDescription = this._view.modelBuilder.text().withProps({
|
.withProps({
|
||||||
value: constants.DATABASE_BACKUP_MIGRATION_MODE_OFFLINE_DESCRIPTION,
|
value: constants.DATABASE_BACKUP_MIGRATION_MODE_OFFLINE_DESCRIPTION,
|
||||||
CSSStyles: {
|
CSSStyles: { ...styles.NOTE_CSS, 'margin-left': '20px' }
|
||||||
...styles.NOTE_CSS,
|
|
||||||
'margin-left': '20px'
|
|
||||||
}
|
|
||||||
}).component();
|
}).component();
|
||||||
|
this._disposables.push(
|
||||||
this._disposables.push(offlineButton.onDidChangeCheckedState((e) => {
|
this._offlineButton.onDidChangeCheckedState(checked => {
|
||||||
if (e) {
|
if (checked) {
|
||||||
this.migrationStateModel._databaseBackup.migrationMode = MigrationMode.OFFLINE;
|
this.migrationStateModel._databaseBackup.migrationMode = MigrationMode.OFFLINE;
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const flexContainer = this._view.modelBuilder.flexContainer().withItems(
|
const flexContainer = this._view.modelBuilder.flexContainer()
|
||||||
[
|
.withItems([
|
||||||
onlineButton,
|
this._onlineButton,
|
||||||
onlineDescription,
|
onlineDescription,
|
||||||
offlineButton,
|
this._offlineButton,
|
||||||
offlineDescription
|
offlineDescription]
|
||||||
]
|
).withLayout({ flexFlow: 'column' })
|
||||||
).withLayout({
|
.component();
|
||||||
flexFlow: 'column'
|
|
||||||
}).component();
|
|
||||||
|
|
||||||
return {
|
return { component: flexContainer };
|
||||||
component: flexContainer
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
import * as azdata from 'azdata';
|
import * as azdata from 'azdata';
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
|
import * as utils from '../api/utils';
|
||||||
import * as mssql from 'mssql';
|
import * as mssql from 'mssql';
|
||||||
import { MigrationWizardPage } from '../models/migrationWizardPage';
|
import { MigrationWizardPage } from '../models/migrationWizardPage';
|
||||||
import { MigrationStateModel, MigrationTargetType, PerformanceDataSourceOptions, StateChangeEvent } from '../models/stateMachine';
|
import { MigrationStateModel, MigrationTargetType, PerformanceDataSourceOptions, StateChangeEvent } from '../models/stateMachine';
|
||||||
@@ -83,11 +84,11 @@ export class SKURecommendationPage extends MigrationWizardPage {
|
|||||||
name: constants.SKU_RECOMMENDATION_VM_CARD_TEXT,
|
name: constants.SKU_RECOMMENDATION_VM_CARD_TEXT,
|
||||||
icon: IconPathHelper.sqlVmLogo
|
icon: IconPathHelper.sqlVmLogo
|
||||||
},
|
},
|
||||||
// {
|
{
|
||||||
// type: MigrationTargetType.SQLDB,
|
type: MigrationTargetType.SQLDB,
|
||||||
// name: constants.SKU_RECOMMENDATION_DB_CARD_TEXT,
|
name: constants.SKU_RECOMMENDATION_SQLDB_CARD_TEXT,
|
||||||
// icon: IconPathHelper.sqlDatabaseLogo
|
icon: IconPathHelper.sqlDatabaseLogo
|
||||||
// }
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
constructor(wizard: azdata.window.Wizard, migrationStateModel: MigrationStateModel) {
|
constructor(wizard: azdata.window.Wizard, migrationStateModel: MigrationStateModel) {
|
||||||
@@ -97,27 +98,23 @@ export class SKURecommendationPage extends MigrationWizardPage {
|
|||||||
protected async registerContent(view: azdata.ModelView) {
|
protected async registerContent(view: azdata.ModelView) {
|
||||||
this._view = view;
|
this._view = view;
|
||||||
this._igComponent = this.createStatusComponent(view); // The first component giving basic information
|
this._igComponent = this.createStatusComponent(view); // The first component giving basic information
|
||||||
this._assessmentStatusIcon = this._view.modelBuilder.image().withProps({
|
this._assessmentStatusIcon = this._view.modelBuilder.image()
|
||||||
|
.withProps({
|
||||||
iconPath: IconPathHelper.completedMigration,
|
iconPath: IconPathHelper.completedMigration,
|
||||||
iconHeight: 17,
|
iconHeight: 17,
|
||||||
iconWidth: 17,
|
iconWidth: 17,
|
||||||
width: 20,
|
width: 20,
|
||||||
height: 20
|
height: 20
|
||||||
}).component();
|
}).component();
|
||||||
const igContainer = this._view.modelBuilder.flexContainer().withProps({
|
const igContainer = this._view.modelBuilder.flexContainer()
|
||||||
CSSStyles: {
|
.withProps({ CSSStyles: { 'align-items': 'center' } })
|
||||||
'align-items': 'center'
|
.component();
|
||||||
}
|
igContainer.addItem(this._assessmentStatusIcon, { flex: '0 0 auto' });
|
||||||
}).component();
|
igContainer.addItem(this._igComponent, { flex: '0 0 auto' });
|
||||||
igContainer.addItem(this._assessmentStatusIcon, {
|
|
||||||
flex: '0 0 auto'
|
|
||||||
});
|
|
||||||
igContainer.addItem(this._igComponent, {
|
|
||||||
flex: '0 0 auto'
|
|
||||||
});
|
|
||||||
|
|
||||||
this._detailsComponent = this.createDetailsComponent(view); // The details of what can be moved
|
this._detailsComponent = this.createDetailsComponent(view); // The details of what can be moved
|
||||||
this._skipAssessmentCheckbox = view.modelBuilder.checkBox().withProps({
|
this._skipAssessmentCheckbox = view.modelBuilder.checkBox()
|
||||||
|
.withProps({
|
||||||
label: constants.SKU_RECOMMENDATION_ASSESSMENT_ERROR_BYPASS,
|
label: constants.SKU_RECOMMENDATION_ASSESSMENT_ERROR_BYPASS,
|
||||||
checked: false,
|
checked: false,
|
||||||
CSSStyles: {
|
CSSStyles: {
|
||||||
@@ -126,7 +123,8 @@ export class SKURecommendationPage extends MigrationWizardPage {
|
|||||||
'display': 'none'
|
'display': 'none'
|
||||||
},
|
},
|
||||||
}).component();
|
}).component();
|
||||||
this._skipAssessmentSubText = view.modelBuilder.text().withProps({
|
this._skipAssessmentSubText = view.modelBuilder.text()
|
||||||
|
.withProps({
|
||||||
value: constants.SKU_RECOMMENDATION_ASSESSMENT_ERROR_DETAIL,
|
value: constants.SKU_RECOMMENDATION_ASSESSMENT_ERROR_DETAIL,
|
||||||
CSSStyles: {
|
CSSStyles: {
|
||||||
'margin': '0 0 0 15px',
|
'margin': '0 0 0 15px',
|
||||||
@@ -137,11 +135,12 @@ export class SKURecommendationPage extends MigrationWizardPage {
|
|||||||
},
|
},
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
this._disposables.push(this._skipAssessmentCheckbox.onChanged(async (value) => {
|
this._disposables.push(
|
||||||
await this._setAssessmentState(false, true);
|
this._skipAssessmentCheckbox.onChanged(
|
||||||
}));
|
async (value) => await this._setAssessmentState(false, true)));
|
||||||
|
|
||||||
const refreshAssessmentButton = this._view.modelBuilder.button().withProps({
|
const refreshAssessmentButton = this._view.modelBuilder.button()
|
||||||
|
.withProps({
|
||||||
iconPath: IconPathHelper.refresh,
|
iconPath: IconPathHelper.refresh,
|
||||||
label: constants.REFRESH_ASSESSMENT_BUTTON_LABEL,
|
label: constants.REFRESH_ASSESSMENT_BUTTON_LABEL,
|
||||||
width: 160,
|
width: 160,
|
||||||
@@ -158,78 +157,57 @@ export class SKURecommendationPage extends MigrationWizardPage {
|
|||||||
await this.constructDetails();
|
await this.constructDetails();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const statusContainer = this._view.modelBuilder.flexContainer().withLayout({
|
const statusContainer = this._view.modelBuilder.flexContainer()
|
||||||
flexFlow: 'column',
|
.withLayout({ flexFlow: 'column' })
|
||||||
}).withItems(
|
.withItems([
|
||||||
[
|
|
||||||
igContainer,
|
igContainer,
|
||||||
this._detailsComponent,
|
this._detailsComponent,
|
||||||
refreshAssessmentButton,
|
refreshAssessmentButton,
|
||||||
this._skipAssessmentCheckbox,
|
this._skipAssessmentCheckbox,
|
||||||
this._skipAssessmentSubText,
|
this._skipAssessmentSubText])
|
||||||
]
|
.withProps({ CSSStyles: { 'margin': '0' } })
|
||||||
).withProps({
|
.component();
|
||||||
CSSStyles: {
|
|
||||||
'margin': '0'
|
|
||||||
}
|
|
||||||
}).component();
|
|
||||||
this._chooseTargetComponent = await this.createChooseTargetComponent(view);
|
this._chooseTargetComponent = await this.createChooseTargetComponent(view);
|
||||||
const _azureRecommendationsContainer = this.createAzureRecommendationContainer(view);
|
const _azureRecommendationsContainer = this.createAzureRecommendationContainer(view);
|
||||||
this.assessmentGroupContainer = await this.createViewAssessmentsContainer();
|
this.assessmentGroupContainer = await this.createViewAssessmentsContainer();
|
||||||
this._formContainer = view.modelBuilder.formContainer().withFormItems(
|
this._formContainer = view.modelBuilder.formContainer()
|
||||||
[
|
.withFormItems([
|
||||||
{
|
{ component: statusContainer, title: '' },
|
||||||
title: '',
|
{ component: this._chooseTargetComponent },
|
||||||
component: statusContainer
|
{ component: _azureRecommendationsContainer },
|
||||||
},
|
{ component: this.assessmentGroupContainer }])
|
||||||
{
|
.withProps({
|
||||||
component: this._chooseTargetComponent
|
|
||||||
},
|
|
||||||
{
|
|
||||||
component: _azureRecommendationsContainer
|
|
||||||
},
|
|
||||||
{
|
|
||||||
component: this.assessmentGroupContainer
|
|
||||||
},
|
|
||||||
]
|
|
||||||
).withProps({
|
|
||||||
CSSStyles: {
|
CSSStyles: {
|
||||||
'display': 'none',
|
'display': 'none',
|
||||||
'padding-top': '0',
|
'padding-top': '0',
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this._assessmentComponent = this._view.modelBuilder.flexContainer().withLayout({
|
this._assessmentComponent = this._view.modelBuilder.flexContainer()
|
||||||
height: '100%',
|
.withLayout({ height: '100%', flexFlow: 'column' })
|
||||||
flexFlow: 'column'
|
.withProps({ CSSStyles: { 'margin-left': '30px' } })
|
||||||
}).withProps({
|
.component();
|
||||||
CSSStyles: {
|
|
||||||
'margin-left': '30px'
|
|
||||||
}
|
|
||||||
}).component();
|
|
||||||
|
|
||||||
this._assessmentComponent.addItem(this.createAssessmentProgress(), { flex: '0 0 auto' });
|
this._assessmentComponent.addItem(this.createAssessmentProgress(), { flex: '0 0 auto' });
|
||||||
this._assessmentComponent.addItem(await this.createAssessmentInfo(), { flex: '0 0 auto' });
|
this._assessmentComponent.addItem(await this.createAssessmentInfo(), { flex: '0 0 auto' });
|
||||||
|
|
||||||
this._rootContainer = this._view.modelBuilder.flexContainer().withLayout({
|
this._rootContainer = this._view.modelBuilder.flexContainer()
|
||||||
height: '100%',
|
.withLayout({ height: '100%', flexFlow: 'column' })
|
||||||
flexFlow: 'column'
|
.withProps({ ariaLive: 'polite' })
|
||||||
}).withProps({
|
.component();
|
||||||
ariaLive: 'polite',
|
|
||||||
}).component();
|
|
||||||
this._rootContainer.addItem(this._assessmentComponent, { flex: '0 0 auto' });
|
this._rootContainer.addItem(this._assessmentComponent, { flex: '0 0 auto' });
|
||||||
this._rootContainer.addItem(this._formContainer.component(), { flex: '0 0 auto' });
|
this._rootContainer.addItem(this._formContainer.component(), { flex: '0 0 auto' });
|
||||||
|
|
||||||
this._disposables.push(this._view.onClosed(e => {
|
this._disposables.push(this._view.onClosed(
|
||||||
this._disposables.forEach(
|
e => this._disposables.forEach(
|
||||||
d => { try { d.dispose(); } catch { } });
|
d => { try { d.dispose(); } catch { } })));
|
||||||
}));
|
|
||||||
|
|
||||||
await this._view.initializeModel(this._rootContainer);
|
await this._view.initializeModel(this._rootContainer);
|
||||||
}
|
}
|
||||||
|
|
||||||
private createStatusComponent(view: azdata.ModelView): azdata.TextComponent {
|
private createStatusComponent(view: azdata.ModelView): azdata.TextComponent {
|
||||||
const component = view.modelBuilder.text().withProps({
|
const component = view.modelBuilder.text()
|
||||||
|
.withProps({
|
||||||
CSSStyles: {
|
CSSStyles: {
|
||||||
...styles.SECTION_HEADER_CSS,
|
...styles.SECTION_HEADER_CSS,
|
||||||
'margin-left': '8px'
|
'margin-left': '8px'
|
||||||
@@ -239,16 +217,13 @@ export class SKURecommendationPage extends MigrationWizardPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private createDetailsComponent(view: azdata.ModelView): azdata.TextComponent {
|
private createDetailsComponent(view: azdata.ModelView): azdata.TextComponent {
|
||||||
const component = view.modelBuilder.text().withProps({
|
const component = view.modelBuilder.text()
|
||||||
CSSStyles: {
|
.withProps({ CSSStyles: { ...styles.BODY_CSS } })
|
||||||
...styles.BODY_CSS
|
.component();
|
||||||
}
|
|
||||||
}).component();
|
|
||||||
return component;
|
return component;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async createChooseTargetComponent(view: azdata.ModelView): Promise<azdata.DivContainer> {
|
private async createChooseTargetComponent(view: azdata.ModelView): Promise<azdata.DivContainer> {
|
||||||
|
|
||||||
const chooseYourTargetText = this._view.modelBuilder.text().withProps({
|
const chooseYourTargetText = this._view.modelBuilder.text().withProps({
|
||||||
value: constants.SKU_RECOMMENDATION_CHOOSE_A_TARGET,
|
value: constants.SKU_RECOMMENDATION_CHOOSE_A_TARGET,
|
||||||
CSSStyles: {
|
CSSStyles: {
|
||||||
@@ -339,11 +314,14 @@ export class SKURecommendationPage extends MigrationWizardPage {
|
|||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
this._disposables.push(this._rbg.onLinkClick(async (e: azdata.RadioCardLinkClickEvent) => {
|
this._disposables.push(
|
||||||
|
this._rbg.onLinkClick(async (e: azdata.RadioCardLinkClickEvent) => {
|
||||||
if (this.hasRecommendations()) {
|
if (this.hasRecommendations()) {
|
||||||
|
if (e.cardId === product.type) {
|
||||||
const skuRecommendationResultsDialog = new SkuRecommendationResultsDialog(this.migrationStateModel, product.type);
|
const skuRecommendationResultsDialog = new SkuRecommendationResultsDialog(this.migrationStateModel, product.type);
|
||||||
if (e.cardId === skuRecommendationResultsDialog._targetType) {
|
await skuRecommendationResultsDialog.openDialog(
|
||||||
await skuRecommendationResultsDialog.openDialog(e.cardId, this.migrationStateModel._skuRecommendationResults.recommendations);
|
e.cardId,
|
||||||
|
this.migrationStateModel._skuRecommendationResults.recommendations);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
@@ -352,63 +330,61 @@ export class SKURecommendationPage extends MigrationWizardPage {
|
|||||||
this._disposables.push(this._rbg.onSelectionChanged(async (value) => {
|
this._disposables.push(this._rbg.onSelectionChanged(async (value) => {
|
||||||
if (value) {
|
if (value) {
|
||||||
this.assessmentGroupContainer.display = 'inline';
|
this.assessmentGroupContainer.display = 'inline';
|
||||||
await this.changeTargetType(value.cardId);
|
this.changeTargetType(value.cardId);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this._rbgLoader = this._view.modelBuilder.loadingComponent().withItem(
|
this._rbgLoader = this._view.modelBuilder.loadingComponent()
|
||||||
this._rbg
|
.withItem(this._rbg)
|
||||||
).component();
|
.component();
|
||||||
|
|
||||||
const component = this._view.modelBuilder.divContainer().withItems(
|
const component = this._view.modelBuilder.divContainer()
|
||||||
[
|
.withItems([chooseYourTargetText, this._rbgLoader])
|
||||||
chooseYourTargetText,
|
.component();
|
||||||
this._rbgLoader
|
|
||||||
]
|
|
||||||
).component();
|
|
||||||
return component;
|
return component;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async createViewAssessmentsContainer(): Promise<azdata.FlexContainer> {
|
private async createViewAssessmentsContainer(): Promise<azdata.FlexContainer> {
|
||||||
this._viewAssessmentsHelperText = this._view.modelBuilder.text().withProps({
|
this._viewAssessmentsHelperText = this._view.modelBuilder.text().withProps({
|
||||||
value: constants.SKU_RECOMMENDATION_VIEW_ASSESSMENT_MI,
|
value: constants.SKU_RECOMMENDATION_VIEW_ASSESSMENT_MI,
|
||||||
CSSStyles: {
|
CSSStyles: { ...styles.SECTION_HEADER_CSS },
|
||||||
...styles.SECTION_HEADER_CSS
|
|
||||||
},
|
|
||||||
width: WIZARD_INPUT_COMPONENT_WIDTH
|
width: WIZARD_INPUT_COMPONENT_WIDTH
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
const button = this._view.modelBuilder.button().withProps({
|
const button = this._view.modelBuilder.button().withProps({
|
||||||
label: constants.VIEW_SELECT_BUTTON_LABEL,
|
label: constants.VIEW_SELECT_BUTTON_LABEL,
|
||||||
width: 100,
|
width: 100,
|
||||||
CSSStyles: {
|
CSSStyles: { 'margin': '12px 0' }
|
||||||
'margin': '12px 0'
|
|
||||||
}
|
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
let serverName = this.migrationStateModel.serverName || (await this.migrationStateModel.getSourceConnectionProfile()).serverName;
|
const serverName = this.migrationStateModel.serverName || (await this.migrationStateModel.getSourceConnectionProfile()).serverName;
|
||||||
|
|
||||||
let miDialog = new AssessmentResultsDialog('ownerUri', this.migrationStateModel, constants.ASSESSMENT_TILE(serverName), this, MigrationTargetType.SQLMI);
|
const miDialog = new AssessmentResultsDialog('ownerUri', this.migrationStateModel, constants.ASSESSMENT_TILE(serverName), this, MigrationTargetType.SQLMI);
|
||||||
let vmDialog = new AssessmentResultsDialog('ownerUri', this.migrationStateModel, constants.ASSESSMENT_TILE(serverName), this, MigrationTargetType.SQLVM);
|
const vmDialog = new AssessmentResultsDialog('ownerUri', this.migrationStateModel, constants.ASSESSMENT_TILE(serverName), this, MigrationTargetType.SQLVM);
|
||||||
|
const dbDialog = new AssessmentResultsDialog('ownerUri', this.migrationStateModel, constants.ASSESSMENT_TILE(serverName), this, MigrationTargetType.SQLDB);
|
||||||
|
|
||||||
this._disposables.push(button.onDidClick(async (e) => {
|
this._disposables.push(button.onDidClick(async (e) => {
|
||||||
if (this._rbg.selectedCardId === MigrationTargetType.SQLVM) {
|
switch (this._rbg.selectedCardId) {
|
||||||
|
case MigrationTargetType.SQLVM:
|
||||||
this._rbg.selectedCardId = MigrationTargetType.SQLVM;
|
this._rbg.selectedCardId = MigrationTargetType.SQLVM;
|
||||||
await vmDialog.openDialog();
|
return await vmDialog.openDialog();
|
||||||
} else if (this._rbg.selectedCardId === MigrationTargetType.SQLMI) {
|
case MigrationTargetType.SQLMI:
|
||||||
this._rbg.selectedCardId = MigrationTargetType.SQLMI;
|
this._rbg.selectedCardId = MigrationTargetType.SQLMI;
|
||||||
await miDialog.openDialog();
|
return await miDialog.openDialog();
|
||||||
|
case MigrationTargetType.SQLDB:
|
||||||
|
this._rbg.selectedCardId = MigrationTargetType.SQLDB;
|
||||||
|
return await dbDialog.openDialog();
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this._databaseSelectedHelperText = this._view.modelBuilder.text().withProps({
|
this._databaseSelectedHelperText = this._view.modelBuilder.text()
|
||||||
CSSStyles: {
|
.withProps({
|
||||||
...styles.BODY_CSS,
|
CSSStyles: { ...styles.BODY_CSS },
|
||||||
},
|
ariaLive: 'polite',
|
||||||
ariaLive: 'polite'
|
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
const container = this._view.modelBuilder.flexContainer().withItems([
|
const container = this._view.modelBuilder.flexContainer()
|
||||||
|
.withItems([
|
||||||
this._viewAssessmentsHelperText,
|
this._viewAssessmentsHelperText,
|
||||||
button,
|
button,
|
||||||
this._databaseSelectedHelperText
|
this._databaseSelectedHelperText
|
||||||
@@ -418,9 +394,9 @@ export class SKURecommendationPage extends MigrationWizardPage {
|
|||||||
return container;
|
return container;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async changeTargetType(newTargetType: string) {
|
private changeTargetType(newTargetType: string): void {
|
||||||
switch (newTargetType) {
|
switch (newTargetType) {
|
||||||
case MigrationTargetType.SQLMI: {
|
case MigrationTargetType.SQLMI:
|
||||||
const miDbs = this.migrationStateModel._miDbs.filter(
|
const miDbs = this.migrationStateModel._miDbs.filter(
|
||||||
db => this.migrationStateModel._databasesForAssessment.findIndex(
|
db => this.migrationStateModel._databasesForAssessment.findIndex(
|
||||||
dba => dba === db) >= 0);
|
dba => dba === db) >= 0);
|
||||||
@@ -429,9 +405,7 @@ export class SKURecommendationPage extends MigrationWizardPage {
|
|||||||
this.migrationStateModel._targetType = MigrationTargetType.SQLMI;
|
this.migrationStateModel._targetType = MigrationTargetType.SQLMI;
|
||||||
this.migrationStateModel._databasesForMigration = miDbs;
|
this.migrationStateModel._databasesForMigration = miDbs;
|
||||||
break;
|
break;
|
||||||
}
|
case MigrationTargetType.SQLVM:
|
||||||
|
|
||||||
case MigrationTargetType.SQLVM: {
|
|
||||||
const vmDbs = this.migrationStateModel._vmDbs.filter(
|
const vmDbs = this.migrationStateModel._vmDbs.filter(
|
||||||
db => this.migrationStateModel._databasesForAssessment.findIndex(
|
db => this.migrationStateModel._databasesForAssessment.findIndex(
|
||||||
dba => dba === db) >= 0);
|
dba => dba === db) >= 0);
|
||||||
@@ -440,10 +414,20 @@ export class SKURecommendationPage extends MigrationWizardPage {
|
|||||||
this.migrationStateModel._targetType = MigrationTargetType.SQLVM;
|
this.migrationStateModel._targetType = MigrationTargetType.SQLVM;
|
||||||
this.migrationStateModel._databasesForMigration = vmDbs;
|
this.migrationStateModel._databasesForMigration = vmDbs;
|
||||||
break;
|
break;
|
||||||
}
|
case MigrationTargetType.SQLDB:
|
||||||
|
const dbDbs = this.migrationStateModel._sqldbDbs.filter(
|
||||||
|
db => this.migrationStateModel._databasesForAssessment.findIndex(
|
||||||
|
dba => dba === db) >= 0);
|
||||||
|
|
||||||
|
this._viewAssessmentsHelperText.value = constants.SKU_RECOMMENDATION_VIEW_ASSESSMENT_SQLDB;
|
||||||
|
this.migrationStateModel._targetType = MigrationTargetType.SQLDB;
|
||||||
|
this.migrationStateModel._databasesForMigration = dbDbs;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._databaseSelectedHelperText.value = constants.TOTAL_DATABASES_SELECTED(this.migrationStateModel._databasesForMigration.length, this.migrationStateModel._databasesForAssessment.length);
|
this._databaseSelectedHelperText.value = constants.TOTAL_DATABASES_SELECTED(
|
||||||
|
this.migrationStateModel._databasesForMigration.length,
|
||||||
|
this.migrationStateModel._databasesForAssessment.length);
|
||||||
this.migrationStateModel.refreshDatabaseBackupPage = true;
|
this.migrationStateModel.refreshDatabaseBackupPage = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -459,7 +443,7 @@ export class SKURecommendationPage extends MigrationWizardPage {
|
|||||||
const errors: string[] = [];
|
const errors: string[] = [];
|
||||||
await this._setAssessmentState(true, false);
|
await this._setAssessmentState(true, false);
|
||||||
try {
|
try {
|
||||||
await this.migrationStateModel.getDatabaseAssessments(MigrationTargetType.SQLMI);
|
await this.migrationStateModel.getDatabaseAssessments([MigrationTargetType.SQLMI, MigrationTargetType.SQLDB]);
|
||||||
const assessmentError = this.migrationStateModel._assessmentResults?.assessmentError;
|
const assessmentError = this.migrationStateModel._assessmentResults?.assessmentError;
|
||||||
if (assessmentError) {
|
if (assessmentError) {
|
||||||
errors.push(`message: ${assessmentError.message}${EOL}stack: ${assessmentError.stack}`);
|
errors.push(`message: ${assessmentError.message}${EOL}stack: ${assessmentError.stack}`);
|
||||||
@@ -485,14 +469,16 @@ export class SKURecommendationPage extends MigrationWizardPage {
|
|||||||
} else {
|
} else {
|
||||||
this._assessmentStatusIcon.iconPath = IconPathHelper.completedMigration;
|
this._assessmentStatusIcon.iconPath = IconPathHelper.completedMigration;
|
||||||
this._igComponent.value = constants.ASSESSMENT_COMPLETED(serverName);
|
this._igComponent.value = constants.ASSESSMENT_COMPLETED(serverName);
|
||||||
this._detailsComponent.value = constants.SKU_RECOMMENDATION_ALL_SUCCESSFUL(this.migrationStateModel._assessmentResults?.databaseAssessments?.length);
|
this._detailsComponent.value = constants.SKU_RECOMMENDATION_ALL_SUCCESSFUL(
|
||||||
|
this.migrationStateModel._assessmentResults?.databaseAssessments?.length);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// use prior assessment results
|
// use prior assessment results
|
||||||
this._assessmentStatusIcon.iconPath = IconPathHelper.completedMigration;
|
this._assessmentStatusIcon.iconPath = IconPathHelper.completedMigration;
|
||||||
this._igComponent.value = constants.ASSESSMENT_COMPLETED(serverName);
|
this._igComponent.value = constants.ASSESSMENT_COMPLETED(serverName);
|
||||||
this._detailsComponent.value = constants.SKU_RECOMMENDATION_ALL_SUCCESSFUL(this.migrationStateModel._assessmentResults?.databaseAssessments?.length);
|
this._detailsComponent.value = constants.SKU_RECOMMENDATION_ALL_SUCCESSFUL(
|
||||||
|
this.migrationStateModel._assessmentResults?.databaseAssessments?.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.migrationStateModel.savedInfo?.migrationTargetType) {
|
if (this.migrationStateModel.savedInfo?.migrationTargetType) {
|
||||||
@@ -543,46 +529,36 @@ export class SKURecommendationPage extends MigrationWizardPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async _setAssessmentState(assessing: boolean, failedAssessment: boolean): Promise<void> {
|
private async _setAssessmentState(assessing: boolean, failedAssessment: boolean): Promise<void> {
|
||||||
let display: azdata.DisplayType = assessing ? 'block' : 'none';
|
await utils.updateControlDisplay(this._assessmentComponent, assessing);
|
||||||
await this._assessmentComponent.updateCssStyles({ 'display': display });
|
await utils.updateControlDisplay(this._skipAssessmentCheckbox, !assessing && failedAssessment);
|
||||||
this._assessmentComponent.display = display;
|
await utils.updateControlDisplay(
|
||||||
|
this._skipAssessmentSubText,
|
||||||
|
!assessing && failedAssessment,
|
||||||
|
'block');
|
||||||
|
await utils.updateControlDisplay(this._formContainer.component(), !assessing);
|
||||||
|
await utils.updateControlDisplay(
|
||||||
|
this._chooseTargetComponent,
|
||||||
|
!failedAssessment || this._skipAssessmentCheckbox.checked === true);
|
||||||
|
|
||||||
display = !assessing && failedAssessment ? 'block' : 'none';
|
await utils.updateControlDisplay(
|
||||||
await this._skipAssessmentCheckbox.updateCssStyles({ 'display': display });
|
this.assessmentGroupContainer,
|
||||||
this._skipAssessmentCheckbox.display = display;
|
this._rbg.selectedCardId !== undefined && (!failedAssessment || this._skipAssessmentCheckbox.checked === true));
|
||||||
await this._skipAssessmentSubText.updateCssStyles({ 'display': display });
|
|
||||||
this._skipAssessmentSubText.display = display;
|
|
||||||
|
|
||||||
await this._formContainer.component().updateCssStyles({ 'display': !assessing ? 'block' : 'none' });
|
|
||||||
|
|
||||||
display = failedAssessment && !this._skipAssessmentCheckbox.checked ? 'none' : 'block';
|
|
||||||
await this._chooseTargetComponent.updateCssStyles({ 'display': display });
|
|
||||||
this._chooseTargetComponent.display = display;
|
|
||||||
|
|
||||||
display = !this._rbg.selectedCardId || failedAssessment && !this._skipAssessmentCheckbox.checked ? 'none' : 'inline';
|
|
||||||
await this.assessmentGroupContainer.updateCssStyles({ 'display': display });
|
|
||||||
this.assessmentGroupContainer.display = display;
|
|
||||||
|
|
||||||
display = (this._rbg.selectedCardId
|
|
||||||
&& (!failedAssessment || this._skipAssessmentCheckbox.checked)
|
|
||||||
&& this.migrationStateModel._databasesForMigration?.length > 0)
|
|
||||||
? 'inline'
|
|
||||||
: 'none';
|
|
||||||
|
|
||||||
this._assessmentLoader.loading = assessing;
|
this._assessmentLoader.loading = assessing;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async onPageEnter(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise<void> {
|
public async onPageEnter(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise<void> {
|
||||||
|
if (pageChangeInfo.newPage < pageChangeInfo.lastPage) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.wizard.registerNavigationValidator((pageChangeInfo) => {
|
this.wizard.registerNavigationValidator((pageChangeInfo) => {
|
||||||
const errors: string[] = [];
|
this.wizard.message = { text: '' };
|
||||||
this.wizard.message = {
|
|
||||||
text: '',
|
|
||||||
level: azdata.window.MessageLevel.Error
|
|
||||||
};
|
|
||||||
if (pageChangeInfo.newPage < pageChangeInfo.lastPage) {
|
if (pageChangeInfo.newPage < pageChangeInfo.lastPage) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const errors: string[] = [];
|
||||||
if (this._rbg.selectedCardId === undefined || this._rbg.selectedCardId === '') {
|
if (this._rbg.selectedCardId === undefined || this._rbg.selectedCardId === '') {
|
||||||
errors.push(constants.SELECT_TARGET_TO_CONTINUE);
|
errors.push(constants.SELECT_TARGET_TO_CONTINUE);
|
||||||
}
|
}
|
||||||
@@ -620,9 +596,12 @@ export class SKURecommendationPage extends MigrationWizardPage {
|
|||||||
|
|
||||||
public async refreshCardText(showLoadingIcon: boolean = true): Promise<void> {
|
public async refreshCardText(showLoadingIcon: boolean = true): Promise<void> {
|
||||||
this._rbgLoader.loading = showLoadingIcon && true;
|
this._rbgLoader.loading = showLoadingIcon && true;
|
||||||
if (this._rbg.selectedCardId === MigrationTargetType.SQLMI) {
|
switch (this._rbg.selectedCardId) {
|
||||||
|
case MigrationTargetType.SQLMI:
|
||||||
this.migrationStateModel._databasesForMigration = this.migrationStateModel._miDbs;
|
this.migrationStateModel._databasesForMigration = this.migrationStateModel._miDbs;
|
||||||
} else {
|
case MigrationTargetType.SQLDB:
|
||||||
|
this.migrationStateModel._databasesForMigration = this.migrationStateModel._sqldbDbs;
|
||||||
|
case MigrationTargetType.SQLVM:
|
||||||
this.migrationStateModel._databasesForMigration = this.migrationStateModel._vmDbs;
|
this.migrationStateModel._databasesForMigration = this.migrationStateModel._vmDbs;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -650,21 +629,24 @@ export class SKURecommendationPage extends MigrationWizardPage {
|
|||||||
this._rbg.cards[index].descriptions[CardDescriptionIndex.SKU_RECOMMENDATION].textValue =
|
this._rbg.cards[index].descriptions[CardDescriptionIndex.SKU_RECOMMENDATION].textValue =
|
||||||
constants.AZURE_RECOMMENDATION_CARD_IN_PROGRESS;
|
constants.AZURE_RECOMMENDATION_CARD_IN_PROGRESS;
|
||||||
} else {
|
} else {
|
||||||
this._rbg.cards[index].descriptions[CardDescriptionIndex.SKU_RECOMMENDATION].textValue = constants.AZURE_RECOMMENDATION_CARD_NOT_ENABLED;
|
this._rbg.cards[index].descriptions[CardDescriptionIndex.SKU_RECOMMENDATION].textValue =
|
||||||
|
constants.AZURE_RECOMMENDATION_CARD_NOT_ENABLED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let recommendation;
|
let recommendation;
|
||||||
switch (product.type) {
|
switch (product.type) {
|
||||||
case MigrationTargetType.SQLMI:
|
case MigrationTargetType.SQLMI:
|
||||||
this._rbg.cards[index].descriptions[CardDescriptionIndex.ASSESSMENT_STATUS].textValue = constants.CAN_BE_MIGRATED(dbWithoutIssuesCount, dbCount);
|
this._rbg.cards[index].descriptions[CardDescriptionIndex.ASSESSMENT_STATUS].textValue =
|
||||||
|
constants.CAN_BE_MIGRATED(dbWithoutIssuesCount, dbCount);
|
||||||
|
|
||||||
if (this.hasRecommendations()) {
|
if (this.hasRecommendations()) {
|
||||||
recommendation = this.migrationStateModel._skuRecommendationResults.recommendations.sqlMiRecommendationResults[0];
|
recommendation = this.migrationStateModel._skuRecommendationResults.recommendations.sqlMiRecommendationResults[0];
|
||||||
|
|
||||||
// result returned but no SKU recommended
|
// result returned but no SKU recommended
|
||||||
if (!recommendation.targetSku) {
|
if (!recommendation?.targetSku) {
|
||||||
this._rbg.cards[index].descriptions[CardDescriptionIndex.SKU_RECOMMENDATION].textValue = constants.SKU_RECOMMENDATION_NO_RECOMMENDATION;
|
this._rbg.cards[index].descriptions[CardDescriptionIndex.SKU_RECOMMENDATION].textValue =
|
||||||
|
constants.SKU_RECOMMENDATION_NO_RECOMMENDATION;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
const serviceTier = recommendation.targetSku.category?.sqlServiceTier === mssql.AzureSqlPaaSServiceTier.GeneralPurpose
|
const serviceTier = recommendation.targetSku.category?.sqlServiceTier === mssql.AzureSqlPaaSServiceTier.GeneralPurpose
|
||||||
@@ -675,41 +657,62 @@ export class SKURecommendationPage extends MigrationWizardPage {
|
|||||||
: recommendation.targetSku.category?.hardwareType === mssql.AzureSqlPaaSHardwareType.PremiumSeries
|
: recommendation.targetSku.category?.hardwareType === mssql.AzureSqlPaaSHardwareType.PremiumSeries
|
||||||
? constants.PREMIUM_SERIES
|
? constants.PREMIUM_SERIES
|
||||||
: constants.PREMIUM_SERIES_MEMORY_OPTIMIZED;
|
: constants.PREMIUM_SERIES_MEMORY_OPTIMIZED;
|
||||||
this._rbg.cards[index].descriptions[CardDescriptionIndex.SKU_RECOMMENDATION].textValue = constants.MI_CONFIGURATION_PREVIEW(hardwareType, serviceTier, recommendation.targetSku.computeSize!, recommendation.targetSku.storageMaxSizeInMb! / 1024);
|
this._rbg.cards[index].descriptions[CardDescriptionIndex.SKU_RECOMMENDATION].textValue =
|
||||||
|
constants.MI_CONFIGURATION_PREVIEW(
|
||||||
|
hardwareType,
|
||||||
|
serviceTier,
|
||||||
|
recommendation.targetSku.computeSize!,
|
||||||
|
recommendation.targetSku.storageMaxSizeInMb! / 1024);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MigrationTargetType.SQLVM:
|
case MigrationTargetType.SQLVM:
|
||||||
this._rbg.cards[index].descriptions[CardDescriptionIndex.ASSESSMENT_STATUS].textValue = constants.CAN_BE_MIGRATED(dbCount, dbCount);
|
this._rbg.cards[index].descriptions[CardDescriptionIndex.ASSESSMENT_STATUS].textValue =
|
||||||
|
constants.CAN_BE_MIGRATED(dbCount, dbCount);
|
||||||
|
|
||||||
if (this.hasRecommendations()) {
|
if (this.hasRecommendations()) {
|
||||||
recommendation = this.migrationStateModel._skuRecommendationResults.recommendations.sqlVmRecommendationResults[0];
|
recommendation = this.migrationStateModel._skuRecommendationResults.recommendations.sqlVmRecommendationResults[0];
|
||||||
|
|
||||||
// result returned but no SKU recommended
|
// result returned but no SKU recommended
|
||||||
if (!recommendation.targetSku) {
|
if (!recommendation?.targetSku) {
|
||||||
this._rbg.cards[index].descriptions[CardDescriptionIndex.SKU_RECOMMENDATION].textValue = constants.SKU_RECOMMENDATION_NO_RECOMMENDATION;
|
this._rbg.cards[index].descriptions[CardDescriptionIndex.SKU_RECOMMENDATION].textValue =
|
||||||
|
constants.SKU_RECOMMENDATION_NO_RECOMMENDATION;
|
||||||
this._rbg.cards[index].descriptions[CardDescriptionIndex.VM_CONFIGURATIONS].textValue = '';
|
this._rbg.cards[index].descriptions[CardDescriptionIndex.VM_CONFIGURATIONS].textValue = '';
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this._rbg.cards[index].descriptions[CardDescriptionIndex.SKU_RECOMMENDATION].textValue = constants.VM_CONFIGURATION(recommendation.targetSku.virtualMachineSize!.sizeName, recommendation.targetSku.virtualMachineSize!.vCPUsAvailable);
|
this._rbg.cards[index].descriptions[CardDescriptionIndex.SKU_RECOMMENDATION].textValue =
|
||||||
|
constants.VM_CONFIGURATION(
|
||||||
|
recommendation.targetSku.virtualMachineSize!.sizeName,
|
||||||
|
recommendation.targetSku.virtualMachineSize!.vCPUsAvailable);
|
||||||
|
|
||||||
const dataDisk = constants.STORAGE_CONFIGURATION(recommendation.targetSku.dataDiskSizes![0].size, recommendation.targetSku.dataDiskSizes!.length);
|
const dataDisk = constants.STORAGE_CONFIGURATION(
|
||||||
const storageDisk = constants.STORAGE_CONFIGURATION(recommendation.targetSku.logDiskSizes![0].size, recommendation.targetSku.logDiskSizes!.length);
|
recommendation.targetSku.dataDiskSizes![0].size,
|
||||||
|
recommendation.targetSku.dataDiskSizes!.length);
|
||||||
|
const storageDisk = constants.STORAGE_CONFIGURATION(
|
||||||
|
recommendation.targetSku.logDiskSizes![0].size,
|
||||||
|
recommendation.targetSku.logDiskSizes!.length);
|
||||||
const tempDb = recommendation.targetSku.tempDbDiskSizes!.length > 0
|
const tempDb = recommendation.targetSku.tempDbDiskSizes!.length > 0
|
||||||
? constants.STORAGE_CONFIGURATION(recommendation.targetSku.logDiskSizes![0].size, recommendation.targetSku.logDiskSizes!.length)
|
? constants.STORAGE_CONFIGURATION(
|
||||||
|
recommendation.targetSku.logDiskSizes![0].size,
|
||||||
|
recommendation.targetSku.logDiskSizes!.length)
|
||||||
: constants.LOCAL_SSD;
|
: constants.LOCAL_SSD;
|
||||||
this._rbg.cards[index].descriptions[CardDescriptionIndex.VM_CONFIGURATIONS].textValue = constants.VM_CONFIGURATION_PREVIEW(dataDisk, storageDisk, tempDb);
|
this._rbg.cards[index].descriptions[CardDescriptionIndex.VM_CONFIGURATIONS].textValue =
|
||||||
|
constants.VM_CONFIGURATION_PREVIEW(dataDisk, storageDisk, tempDb);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MigrationTargetType.SQLDB:
|
case MigrationTargetType.SQLDB:
|
||||||
this._rbg.cards[index].descriptions[CardDescriptionIndex.ASSESSMENT_STATUS].textValue = constants.CAN_BE_MIGRATED(dbWithoutIssuesCount, dbCount);
|
this._rbg.cards[index].descriptions[CardDescriptionIndex.ASSESSMENT_STATUS].textValue =
|
||||||
|
constants.CAN_BE_MIGRATED(dbWithoutIssuesCount, dbCount);
|
||||||
|
|
||||||
if (this.hasRecommendations()) {
|
if (this.hasRecommendations()) {
|
||||||
const successfulRecommendationsCount = this.migrationStateModel._skuRecommendationResults.recommendations.sqlDbRecommendationResults.filter(r => r.targetSku !== null).length;
|
const successfulRecommendationsCount =
|
||||||
this._rbg.cards[index].descriptions[CardDescriptionIndex.SKU_RECOMMENDATION].textValue = constants.RECOMMENDATIONS_AVAILABLE(successfulRecommendationsCount);
|
this.migrationStateModel._skuRecommendationResults.recommendations.sqlDbRecommendationResults
|
||||||
|
.filter(r => r.targetSku !== null).length;
|
||||||
|
this._rbg.cards[index].descriptions[CardDescriptionIndex.SKU_RECOMMENDATION].textValue =
|
||||||
|
constants.RECOMMENDATIONS_AVAILABLE(successfulRecommendationsCount);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -719,10 +722,10 @@ export class SKURecommendationPage extends MigrationWizardPage {
|
|||||||
await this._rbg.updateProperties({ cards: this._rbg.cards });
|
await this._rbg.updateProperties({ cards: this._rbg.cards });
|
||||||
|
|
||||||
if (this._rbg.selectedCardId) {
|
if (this._rbg.selectedCardId) {
|
||||||
await this.changeTargetType(this._rbg.selectedCardId);
|
this.changeTargetType(this._rbg.selectedCardId);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._rbgLoader.loading = showLoadingIcon && false;
|
this._rbgLoader.loading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async startCardLoading(): Promise<void> {
|
public async startCardLoading(): Promise<void> {
|
||||||
@@ -730,9 +733,7 @@ export class SKURecommendationPage extends MigrationWizardPage {
|
|||||||
// but updating the card text will do for now
|
// but updating the card text will do for now
|
||||||
this._supportedProducts.forEach((product, index) => {
|
this._supportedProducts.forEach((product, index) => {
|
||||||
this._rbg.cards[index].descriptions[CardDescriptionIndex.SKU_RECOMMENDATION].textValue = constants.LOADING_RECOMMENDATIONS;
|
this._rbg.cards[index].descriptions[CardDescriptionIndex.SKU_RECOMMENDATION].textValue = constants.LOADING_RECOMMENDATIONS;
|
||||||
this._rbg.cards[index].descriptions[CardDescriptionIndex.SKU_RECOMMENDATION].textStyles = {
|
this._rbg.cards[index].descriptions[CardDescriptionIndex.SKU_RECOMMENDATION].textStyles = { ...styles.BODY_CSS };
|
||||||
...styles.BODY_CSS,
|
|
||||||
};
|
|
||||||
this._rbg.cards[index].descriptions[CardDescriptionIndex.VM_CONFIGURATIONS].textValue = '';
|
this._rbg.cards[index].descriptions[CardDescriptionIndex.VM_CONFIGURATIONS].textValue = '';
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -740,10 +741,11 @@ export class SKURecommendationPage extends MigrationWizardPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private createAssessmentProgress(): azdata.FlexContainer {
|
private createAssessmentProgress(): azdata.FlexContainer {
|
||||||
|
this._assessmentLoader = this._view.modelBuilder.loadingComponent()
|
||||||
|
.component();
|
||||||
|
|
||||||
this._assessmentLoader = this._view.modelBuilder.loadingComponent().component();
|
this._assessmentProgress = this._view.modelBuilder.text()
|
||||||
|
.withProps({
|
||||||
this._assessmentProgress = this._view.modelBuilder.text().withProps({
|
|
||||||
value: constants.ASSESSMENT_IN_PROGRESS,
|
value: constants.ASSESSMENT_IN_PROGRESS,
|
||||||
CSSStyles: {
|
CSSStyles: {
|
||||||
...styles.PAGE_TITLE_CSS,
|
...styles.PAGE_TITLE_CSS,
|
||||||
@@ -751,7 +753,8 @@ export class SKURecommendationPage extends MigrationWizardPage {
|
|||||||
}
|
}
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
this._progressContainer = this._view.modelBuilder.flexContainer().withLayout({
|
this._progressContainer = this._view.modelBuilder.flexContainer()
|
||||||
|
.withLayout({
|
||||||
height: '100%',
|
height: '100%',
|
||||||
flexFlow: 'row',
|
flexFlow: 'row',
|
||||||
alignItems: 'center'
|
alignItems: 'center'
|
||||||
@@ -763,7 +766,8 @@ export class SKURecommendationPage extends MigrationWizardPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async createAssessmentInfo(): Promise<azdata.TextComponent> {
|
private async createAssessmentInfo(): Promise<azdata.TextComponent> {
|
||||||
this._assessmentInfo = this._view.modelBuilder.text().withProps({
|
this._assessmentInfo = this._view.modelBuilder.text()
|
||||||
|
.withProps({
|
||||||
value: constants.ASSESSMENT_IN_PROGRESS_CONTENT((await this.migrationStateModel.getSourceConnectionProfile()).serverName),
|
value: constants.ASSESSMENT_IN_PROGRESS_CONTENT((await this.migrationStateModel.getSourceConnectionProfile()).serverName),
|
||||||
CSSStyles: {
|
CSSStyles: {
|
||||||
...styles.BODY_CSS,
|
...styles.BODY_CSS,
|
||||||
@@ -774,14 +778,16 @@ export class SKURecommendationPage extends MigrationWizardPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private createAzureRecommendationContainer(_view: azdata.ModelView): azdata.FlexContainer {
|
private createAzureRecommendationContainer(_view: azdata.ModelView): azdata.FlexContainer {
|
||||||
const container = _view.modelBuilder.flexContainer().withProps({
|
const container = _view.modelBuilder.flexContainer()
|
||||||
|
.withProps({
|
||||||
CSSStyles: {
|
CSSStyles: {
|
||||||
'flex-direction': 'column',
|
'flex-direction': 'column',
|
||||||
'max-width': '700px',
|
'max-width': '700px',
|
||||||
'margin-bottom': '1em',
|
'margin-bottom': '1em',
|
||||||
}
|
}
|
||||||
}).component();
|
}).component();
|
||||||
this._azureRecommendationSectionText = _view.modelBuilder.text().withProps({
|
this._azureRecommendationSectionText = _view.modelBuilder.text()
|
||||||
|
.withProps({
|
||||||
value: constants.AZURE_RECOMMENDATION,
|
value: constants.AZURE_RECOMMENDATION,
|
||||||
description: '',
|
description: '',
|
||||||
CSSStyles: {
|
CSSStyles: {
|
||||||
@@ -789,14 +795,16 @@ export class SKURecommendationPage extends MigrationWizardPage {
|
|||||||
'margin': '12px 0 8px',
|
'margin': '12px 0 8px',
|
||||||
}
|
}
|
||||||
}).component();
|
}).component();
|
||||||
this._azureRecommendationInfoText = _view.modelBuilder.text().withProps({
|
this._azureRecommendationInfoText = _view.modelBuilder.text()
|
||||||
|
.withProps({
|
||||||
value: constants.AZURE_RECOMMENDATION_STATUS_NOT_ENABLED,
|
value: constants.AZURE_RECOMMENDATION_STATUS_NOT_ENABLED,
|
||||||
CSSStyles: {
|
CSSStyles: {
|
||||||
...styles.BODY_CSS,
|
...styles.BODY_CSS,
|
||||||
'margin': '0',
|
'margin': '0',
|
||||||
}
|
}
|
||||||
}).component();
|
}).component();
|
||||||
const learnMoreLink = _view.modelBuilder.hyperlink().withProps({
|
const learnMoreLink = _view.modelBuilder.hyperlink()
|
||||||
|
.withProps({
|
||||||
label: constants.LEARN_MORE,
|
label: constants.LEARN_MORE,
|
||||||
ariaLabel: constants.LEARN_MORE,
|
ariaLabel: constants.LEARN_MORE,
|
||||||
url: 'https://aka.ms/ads-sql-sku-recommend',
|
url: 'https://aka.ms/ads-sql-sku-recommend',
|
||||||
@@ -822,19 +830,16 @@ export class SKURecommendationPage extends MigrationWizardPage {
|
|||||||
}
|
}
|
||||||
}).component();
|
}).component();
|
||||||
const getAzureRecommendationDialog = new GetAzureRecommendationDialog(this, this.wizard, this.migrationStateModel);
|
const getAzureRecommendationDialog = new GetAzureRecommendationDialog(this, this.wizard, this.migrationStateModel);
|
||||||
this._disposables.push(this._getAzureRecommendationButton.onDidClick(async (e) => {
|
this._disposables.push(this._getAzureRecommendationButton.onDidClick(
|
||||||
await getAzureRecommendationDialog.openDialog();
|
async (e) => await getAzureRecommendationDialog.openDialog()));
|
||||||
}));
|
|
||||||
|
this._skuGetRecommendationContainer = _view.modelBuilder.flexContainer()
|
||||||
|
.withProps({ CSSStyles: { 'flex-direction': 'column', } })
|
||||||
|
.component();
|
||||||
|
|
||||||
this._skuGetRecommendationContainer = _view.modelBuilder.flexContainer().withProps({
|
|
||||||
CSSStyles: {
|
|
||||||
'flex-direction': 'column',
|
|
||||||
}
|
|
||||||
}).component();
|
|
||||||
this._skuGetRecommendationContainer.addItems([
|
this._skuGetRecommendationContainer.addItems([
|
||||||
azureRecommendationsInfoContainer,
|
azureRecommendationsInfoContainer,
|
||||||
this._getAzureRecommendationButton,
|
this._getAzureRecommendationButton]);
|
||||||
]);
|
|
||||||
|
|
||||||
this._skuDataCollectionStatusContainer = this.createPerformanceCollectionStatusContainer(_view);
|
this._skuDataCollectionStatusContainer = this.createPerformanceCollectionStatusContainer(_view);
|
||||||
this._skuEditParametersContainer = this.createSkuEditParameters(_view);
|
this._skuEditParametersContainer = this.createSkuEditParameters(_view);
|
||||||
@@ -848,29 +853,27 @@ export class SKURecommendationPage extends MigrationWizardPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private createPerformanceCollectionStatusContainer(_view: azdata.ModelView): azdata.FlexContainer {
|
private createPerformanceCollectionStatusContainer(_view: azdata.ModelView): azdata.FlexContainer {
|
||||||
const container = _view.modelBuilder.flexContainer().withProps({
|
const container = _view.modelBuilder.flexContainer()
|
||||||
|
.withProps({
|
||||||
CSSStyles: {
|
CSSStyles: {
|
||||||
'flex-direction': 'column',
|
'flex-direction': 'column',
|
||||||
'display': this.migrationStateModel.performanceCollectionNotStarted() ? 'none' : 'block',
|
'display': this.migrationStateModel.performanceCollectionNotStarted() ? 'none' : 'block',
|
||||||
}
|
}
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
this._skuDataCollectionStatusIcon = _view.modelBuilder.image().withProps({
|
this._skuDataCollectionStatusIcon = _view.modelBuilder.image()
|
||||||
|
.withProps({
|
||||||
iconPath: IconPathHelper.inProgressMigration,
|
iconPath: IconPathHelper.inProgressMigration,
|
||||||
iconHeight: 16,
|
iconHeight: 16,
|
||||||
iconWidth: 16,
|
iconWidth: 16,
|
||||||
width: 16,
|
width: 16,
|
||||||
height: 16,
|
height: 16,
|
||||||
CSSStyles: {
|
CSSStyles: { 'margin-right': '4px' }
|
||||||
'margin-right': '4px',
|
|
||||||
}
|
|
||||||
}).component();
|
}).component();
|
||||||
this._skuDataCollectionStatusText = _view.modelBuilder.text().withProps({
|
this._skuDataCollectionStatusText = _view.modelBuilder.text()
|
||||||
|
.withProps({
|
||||||
value: '',
|
value: '',
|
||||||
CSSStyles: {
|
CSSStyles: { ...styles.BODY_CSS, 'margin': '0' }
|
||||||
...styles.BODY_CSS,
|
|
||||||
'margin': '0'
|
|
||||||
}
|
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
const statusIconTextContainer = _view.modelBuilder.flexContainer()
|
const statusIconTextContainer = _view.modelBuilder.flexContainer()
|
||||||
@@ -887,7 +890,8 @@ export class SKURecommendationPage extends MigrationWizardPage {
|
|||||||
}
|
}
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
this._skuDataCollectionTimerText = _view.modelBuilder.text().withProps({
|
this._skuDataCollectionTimerText = _view.modelBuilder.text()
|
||||||
|
.withProps({
|
||||||
value: '',
|
value: '',
|
||||||
CSSStyles: {
|
CSSStyles: {
|
||||||
...styles.LIGHT_LABEL_CSS,
|
...styles.LIGHT_LABEL_CSS,
|
||||||
@@ -895,7 +899,8 @@ export class SKURecommendationPage extends MigrationWizardPage {
|
|||||||
}
|
}
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
this._skuStopDataCollectionButton = this._view.modelBuilder.button().withProps({
|
this._skuStopDataCollectionButton = this._view.modelBuilder.button()
|
||||||
|
.withProps({
|
||||||
iconPath: IconPathHelper.cancel,
|
iconPath: IconPathHelper.cancel,
|
||||||
label: constants.STOP_PERFORMANCE_COLLECTION,
|
label: constants.STOP_PERFORMANCE_COLLECTION,
|
||||||
width: 150,
|
width: 150,
|
||||||
@@ -922,14 +927,14 @@ export class SKURecommendationPage extends MigrationWizardPage {
|
|||||||
'display': this.migrationStateModel.performanceCollectionStopped() ? 'block' : 'none',
|
'display': this.migrationStateModel.performanceCollectionStopped() ? 'block' : 'none',
|
||||||
}
|
}
|
||||||
}).component();
|
}).component();
|
||||||
this._disposables.push(this._skuRestartDataCollectionButton.onDidClick(async (e) => {
|
this._disposables.push(
|
||||||
|
this._skuRestartDataCollectionButton.onDidClick(async (e) => {
|
||||||
await this.migrationStateModel.startPerfDataCollection(
|
await this.migrationStateModel.startPerfDataCollection(
|
||||||
this.migrationStateModel._skuRecommendationPerformanceLocation,
|
this.migrationStateModel._skuRecommendationPerformanceLocation,
|
||||||
this.migrationStateModel._performanceDataQueryIntervalInSeconds,
|
this.migrationStateModel._performanceDataQueryIntervalInSeconds,
|
||||||
this.migrationStateModel._staticDataQueryIntervalInSeconds,
|
this.migrationStateModel._staticDataQueryIntervalInSeconds,
|
||||||
this.migrationStateModel._numberOfPerformanceDataQueryIterations,
|
this.migrationStateModel._numberOfPerformanceDataQueryIterations,
|
||||||
this
|
this);
|
||||||
);
|
|
||||||
await this.refreshSkuRecommendationComponents();
|
await this.refreshSkuRecommendationComponents();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -943,10 +948,12 @@ export class SKURecommendationPage extends MigrationWizardPage {
|
|||||||
'margin': '0 0 0 12px',
|
'margin': '0 0 0 12px',
|
||||||
}
|
}
|
||||||
}).component();
|
}).component();
|
||||||
this._disposables.push(this._refreshAzureRecommendationButton.onDidClick(async (e) => {
|
this._disposables.push(
|
||||||
await this.refreshAzureRecommendation();
|
this._refreshAzureRecommendationButton.onDidClick(
|
||||||
}));
|
async (e) => await this.refreshAzureRecommendation()));
|
||||||
this._skuLastRefreshTimeText = this._view.modelBuilder.text().withProps({
|
|
||||||
|
this._skuLastRefreshTimeText = this._view.modelBuilder.text()
|
||||||
|
.withProps({
|
||||||
value: constants.LAST_REFRESHED_TIME(),
|
value: constants.LAST_REFRESHED_TIME(),
|
||||||
CSSStyles: {
|
CSSStyles: {
|
||||||
...styles.SMALL_NOTE_CSS,
|
...styles.SMALL_NOTE_CSS,
|
||||||
@@ -966,14 +973,13 @@ export class SKURecommendationPage extends MigrationWizardPage {
|
|||||||
this._skuStopDataCollectionButton,
|
this._skuStopDataCollectionButton,
|
||||||
this._skuRestartDataCollectionButton,
|
this._skuRestartDataCollectionButton,
|
||||||
this._refreshAzureRecommendationButton,
|
this._refreshAzureRecommendationButton,
|
||||||
this._skuLastRefreshTimeText,
|
this._skuLastRefreshTimeText]);
|
||||||
]);
|
|
||||||
|
|
||||||
container.addItems([
|
container.addItems([
|
||||||
this._skuControlButtonsContainer,
|
this._skuControlButtonsContainer,
|
||||||
statusIconTextContainer,
|
statusIconTextContainer,
|
||||||
this._skuDataCollectionTimerText,
|
this._skuDataCollectionTimerText]);
|
||||||
]);
|
|
||||||
return container;
|
return container;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -984,7 +990,8 @@ export class SKURecommendationPage extends MigrationWizardPage {
|
|||||||
'display': this.migrationStateModel.performanceCollectionNotStarted() ? 'none' : 'block',
|
'display': this.migrationStateModel.performanceCollectionNotStarted() ? 'none' : 'block',
|
||||||
}
|
}
|
||||||
}).component();
|
}).component();
|
||||||
const recommendationParametersSection = _view.modelBuilder.text().withProps({
|
const recommendationParametersSection = _view.modelBuilder.text()
|
||||||
|
.withProps({
|
||||||
value: constants.RECOMMENDATION_PARAMETERS,
|
value: constants.RECOMMENDATION_PARAMETERS,
|
||||||
CSSStyles: {
|
CSSStyles: {
|
||||||
...styles.BODY_CSS,
|
...styles.BODY_CSS,
|
||||||
@@ -992,7 +999,8 @@ export class SKURecommendationPage extends MigrationWizardPage {
|
|||||||
}
|
}
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
const editParametersButton = this._view.modelBuilder.button().withProps({
|
const editParametersButton = this._view.modelBuilder.button()
|
||||||
|
.withProps({
|
||||||
iconPath: IconPathHelper.edit,
|
iconPath: IconPathHelper.edit,
|
||||||
label: constants.EDIT_PARAMETERS,
|
label: constants.EDIT_PARAMETERS,
|
||||||
width: 130,
|
width: 130,
|
||||||
@@ -1004,15 +1012,16 @@ export class SKURecommendationPage extends MigrationWizardPage {
|
|||||||
}
|
}
|
||||||
}).component();
|
}).component();
|
||||||
let skuEditParametersDialog = new SkuEditParametersDialog(this, this.migrationStateModel);
|
let skuEditParametersDialog = new SkuEditParametersDialog(this, this.migrationStateModel);
|
||||||
this._disposables.push(editParametersButton.onDidClick(async () => {
|
this._disposables.push(
|
||||||
await skuEditParametersDialog.openDialog();
|
editParametersButton.onDidClick(
|
||||||
}));
|
async () => await skuEditParametersDialog.openDialog()));
|
||||||
|
|
||||||
const createParameterGroup = (label: string, value: string): {
|
const createParameterGroup = (label: string, value: string): {
|
||||||
flexContainer: azdata.FlexContainer,
|
flexContainer: azdata.FlexContainer,
|
||||||
text: azdata.TextComponent,
|
text: azdata.TextComponent,
|
||||||
} => {
|
} => {
|
||||||
const parameterGroup = this._view.modelBuilder.flexContainer().withProps({
|
const parameterGroup = this._view.modelBuilder.flexContainer()
|
||||||
|
.withProps({
|
||||||
CSSStyles: {
|
CSSStyles: {
|
||||||
'flex-direction': 'row',
|
'flex-direction': 'row',
|
||||||
'align-content': 'left',
|
'align-content': 'left',
|
||||||
@@ -1020,7 +1029,8 @@ export class SKURecommendationPage extends MigrationWizardPage {
|
|||||||
'margin-right': '24px',
|
'margin-right': '24px',
|
||||||
}
|
}
|
||||||
}).component();
|
}).component();
|
||||||
const labelText = this._view.modelBuilder.text().withProps({
|
const labelText = this._view.modelBuilder.text()
|
||||||
|
.withProps({
|
||||||
value: label + ':',
|
value: label + ':',
|
||||||
CSSStyles: {
|
CSSStyles: {
|
||||||
...styles.LIGHT_LABEL_CSS,
|
...styles.LIGHT_LABEL_CSS,
|
||||||
@@ -1028,7 +1038,8 @@ export class SKURecommendationPage extends MigrationWizardPage {
|
|||||||
'margin-right': '4px',
|
'margin-right': '4px',
|
||||||
}
|
}
|
||||||
}).component();
|
}).component();
|
||||||
const valueText = this._view.modelBuilder.text().withProps({
|
const valueText = this._view.modelBuilder.text()
|
||||||
|
.withProps({
|
||||||
value: value,
|
value: value,
|
||||||
CSSStyles: {
|
CSSStyles: {
|
||||||
...styles.BODY_CSS,
|
...styles.BODY_CSS,
|
||||||
@@ -1037,24 +1048,30 @@ export class SKURecommendationPage extends MigrationWizardPage {
|
|||||||
}).component();
|
}).component();
|
||||||
parameterGroup.addItems([
|
parameterGroup.addItems([
|
||||||
labelText,
|
labelText,
|
||||||
valueText,
|
valueText]);
|
||||||
]);
|
|
||||||
return {
|
return {
|
||||||
flexContainer: parameterGroup,
|
flexContainer: parameterGroup,
|
||||||
text: valueText,
|
text: valueText,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const scaleFactorParameterGroup = createParameterGroup(constants.SCALE_FACTOR, this.migrationStateModel._skuScalingFactor.toString());
|
const scaleFactorParameterGroup = createParameterGroup(
|
||||||
|
constants.SCALE_FACTOR,
|
||||||
|
this.migrationStateModel._skuScalingFactor.toString());
|
||||||
this._skuScaleFactorText = scaleFactorParameterGroup.text;
|
this._skuScaleFactorText = scaleFactorParameterGroup.text;
|
||||||
|
|
||||||
const skuTargetPercentileParameterGroup = createParameterGroup(constants.PERCENTAGE_UTILIZATION, constants.PERCENTAGE(this.migrationStateModel._skuTargetPercentile));
|
const skuTargetPercentileParameterGroup = createParameterGroup(
|
||||||
|
constants.PERCENTAGE_UTILIZATION,
|
||||||
|
constants.PERCENTAGE(this.migrationStateModel._skuTargetPercentile));
|
||||||
this._skuTargetPercentileText = skuTargetPercentileParameterGroup.text;
|
this._skuTargetPercentileText = skuTargetPercentileParameterGroup.text;
|
||||||
|
|
||||||
const skuEnablePreviewParameterGroup = createParameterGroup(constants.ENABLE_PREVIEW_SKU, this.migrationStateModel._skuEnablePreview ? constants.YES : constants.NO);
|
const skuEnablePreviewParameterGroup = createParameterGroup(
|
||||||
|
constants.ENABLE_PREVIEW_SKU,
|
||||||
|
this.migrationStateModel._skuEnablePreview ? constants.YES : constants.NO);
|
||||||
this._skuEnablePreviewSkuText = skuEnablePreviewParameterGroup.text;
|
this._skuEnablePreviewSkuText = skuEnablePreviewParameterGroup.text;
|
||||||
|
|
||||||
const parametersContainer = _view.modelBuilder.flexContainer().withProps({
|
const parametersContainer = _view.modelBuilder.flexContainer()
|
||||||
|
.withProps({
|
||||||
CSSStyles: {
|
CSSStyles: {
|
||||||
'margin': '8px 0',
|
'margin': '8px 0',
|
||||||
'flex-direction': 'row',
|
'flex-direction': 'row',
|
||||||
@@ -1172,7 +1189,10 @@ export class SKURecommendationPage extends MigrationWizardPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private hasRecommendations(): boolean {
|
private hasRecommendations(): boolean {
|
||||||
return this.migrationStateModel._skuRecommendationResults?.recommendations && !this.migrationStateModel._skuRecommendationResults?.recommendationError ? true : false;
|
return this.migrationStateModel._skuRecommendationResults?.recommendations
|
||||||
|
&& !this.migrationStateModel._skuRecommendationResults?.recommendationError
|
||||||
|
? true
|
||||||
|
: false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { MigrationWizardPage } from '../models/migrationWizardPage';
|
|||||||
import { MigrationSourceAuthenticationType, MigrationStateModel, StateChangeEvent } from '../models/stateMachine';
|
import { MigrationSourceAuthenticationType, MigrationStateModel, StateChangeEvent } from '../models/stateMachine';
|
||||||
import * as constants from '../constants/strings';
|
import * as constants from '../constants/strings';
|
||||||
import { createLabelTextComponent, createHeadingTextComponent, WIZARD_INPUT_COMPONENT_WIDTH } from './wizardController';
|
import { createLabelTextComponent, createHeadingTextComponent, WIZARD_INPUT_COMPONENT_WIDTH } from './wizardController';
|
||||||
|
import { AuthenticationType } from '../api/sqlUtils';
|
||||||
|
|
||||||
export class SqlSourceConfigurationPage extends MigrationWizardPage {
|
export class SqlSourceConfigurationPage extends MigrationWizardPage {
|
||||||
private _view!: azdata.ModelView;
|
private _view!: azdata.ModelView;
|
||||||
@@ -59,10 +60,13 @@ export class SqlSourceConfigurationPage extends MigrationWizardPage {
|
|||||||
const query = 'select SUSER_NAME()';
|
const query = 'select SUSER_NAME()';
|
||||||
const results = await queryProvider.runQueryAndReturn(await (azdata.connection.getUriForConnection(this.migrationStateModel.sourceConnectionId)), query);
|
const results = await queryProvider.runQueryAndReturn(await (azdata.connection.getUriForConnection(this.migrationStateModel.sourceConnectionId)), query);
|
||||||
const username = results.rows[0][0].displayValue;
|
const username = results.rows[0][0].displayValue;
|
||||||
this.migrationStateModel._authenticationType = connectionProfile.authenticationType === 'SqlLogin' ? MigrationSourceAuthenticationType.Sql : connectionProfile.authenticationType === 'Integrated' ? MigrationSourceAuthenticationType.Integrated : undefined!;
|
this.migrationStateModel._authenticationType = connectionProfile.authenticationType === AuthenticationType.SqlLogin
|
||||||
|
? MigrationSourceAuthenticationType.Sql
|
||||||
|
: connectionProfile.authenticationType === AuthenticationType.Integrated
|
||||||
|
? MigrationSourceAuthenticationType.Integrated
|
||||||
|
: undefined!;
|
||||||
|
|
||||||
const sourceCredText = await createHeadingTextComponent(this._view, constants.SOURCE_CREDENTIALS);
|
const sourceCredText = await createHeadingTextComponent(this._view, constants.SOURCE_CREDENTIALS);
|
||||||
|
|
||||||
const enterYourCredText = createLabelTextComponent(
|
const enterYourCredText = createLabelTextComponent(
|
||||||
this._view,
|
this._view,
|
||||||
constants.ENTER_YOUR_SQL_CREDS,
|
constants.ENTER_YOUR_SQL_CREDS,
|
||||||
|
|||||||
@@ -24,134 +24,204 @@ export class SummaryPage extends MigrationWizardPage {
|
|||||||
|
|
||||||
protected async registerContent(view: azdata.ModelView): Promise<void> {
|
protected async registerContent(view: azdata.ModelView): Promise<void> {
|
||||||
this._view = view;
|
this._view = view;
|
||||||
this._flexContainer = view.modelBuilder.flexContainer().withLayout({
|
this._flexContainer = view.modelBuilder.flexContainer()
|
||||||
flexFlow: 'column'
|
.withLayout({ flexFlow: 'column' })
|
||||||
}).component();
|
.component();
|
||||||
const form = view.modelBuilder.formContainer()
|
const form = view.modelBuilder.formContainer()
|
||||||
.withFormItems(
|
.withFormItems([{ component: this._flexContainer }])
|
||||||
[
|
.component();
|
||||||
{
|
|
||||||
component: this._flexContainer
|
|
||||||
}
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
this._disposables.push(this._view.onClosed(e => {
|
this._disposables.push(
|
||||||
|
this._view.onClosed(e =>
|
||||||
this._disposables.forEach(
|
this._disposables.forEach(
|
||||||
d => { try { d.dispose(); } catch { } });
|
d => { try { d.dispose(); } catch { } })));
|
||||||
}));
|
|
||||||
|
|
||||||
await view.initializeModel(form.component());
|
await view.initializeModel(form);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async onPageEnter(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise<void> {
|
public async onPageEnter(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise<void> {
|
||||||
const targetDatabaseSummary = new TargetDatabaseSummaryDialog(this.migrationStateModel);
|
const targetDatabaseSummary = new TargetDatabaseSummaryDialog(this.migrationStateModel);
|
||||||
const targetDatabaseHyperlink = this._view.modelBuilder.hyperlink().withProps({
|
const isSqlVmTarget = this.migrationStateModel._targetType === MigrationTargetType.SQLVM;
|
||||||
|
const isSqlMiTarget = this.migrationStateModel._targetType === MigrationTargetType.SQLMI;
|
||||||
|
const isSqlDbTarget = this.migrationStateModel._targetType === MigrationTargetType.SQLDB;
|
||||||
|
const isNetworkShare = this.migrationStateModel._databaseBackup.networkContainerType === NetworkContainerType.NETWORK_SHARE;
|
||||||
|
|
||||||
|
const targetDatabaseHyperlink = this._view.modelBuilder.hyperlink()
|
||||||
|
.withProps({
|
||||||
url: '',
|
url: '',
|
||||||
label: this.migrationStateModel._databasesForMigration?.length.toString(),
|
label: (this.migrationStateModel._databasesForMigration?.length ?? 0).toString(),
|
||||||
CSSStyles: {
|
CSSStyles: { ...styles.BODY_CSS, 'margin': '0px', 'width': '300px', }
|
||||||
...styles.BODY_CSS,
|
|
||||||
'margin': '0px',
|
|
||||||
'width': '300px',
|
|
||||||
}
|
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
this._disposables.push(targetDatabaseHyperlink.onDidClick(async e => {
|
this._disposables.push(
|
||||||
await targetDatabaseSummary.initialize();
|
targetDatabaseHyperlink.onDidClick(
|
||||||
}));
|
async e => await targetDatabaseSummary.initialize()));
|
||||||
|
|
||||||
const targetDatabaseRow = this._view.modelBuilder.flexContainer()
|
const targetDatabaseRow = this._view.modelBuilder.flexContainer()
|
||||||
.withLayout(
|
.withLayout({ flexFlow: 'row', alignItems: 'center', })
|
||||||
{
|
.withItems([
|
||||||
flexFlow: 'row',
|
createLabelTextComponent(
|
||||||
alignItems: 'center',
|
this._view,
|
||||||
})
|
constants.SUMMARY_DATABASE_COUNT_LABEL,
|
||||||
.withItems(
|
{ ...styles.BODY_CSS, 'width': '300px' }),
|
||||||
[
|
targetDatabaseHyperlink],
|
||||||
createLabelTextComponent(this._view, constants.SUMMARY_DATABASE_COUNT_LABEL,
|
{ CSSStyles: { 'margin-right': '5px' } })
|
||||||
{
|
|
||||||
...styles.BODY_CSS,
|
|
||||||
'width': '300px',
|
|
||||||
}
|
|
||||||
),
|
|
||||||
targetDatabaseHyperlink
|
|
||||||
],
|
|
||||||
{
|
|
||||||
CSSStyles: {
|
|
||||||
'margin-right': '5px'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.component();
|
.component();
|
||||||
|
|
||||||
this._flexContainer.addItems(
|
this._flexContainer
|
||||||
[
|
.addItems([
|
||||||
await createHeadingTextComponent(this._view, constants.ACCOUNTS_SELECTION_PAGE_TITLE, true),
|
await createHeadingTextComponent(
|
||||||
createInformationRow(this._view, constants.ACCOUNTS_SELECTION_PAGE_TITLE, this.migrationStateModel._azureAccount.displayInfo.displayName),
|
this._view,
|
||||||
|
constants.SOURCE_DATABASES),
|
||||||
await createHeadingTextComponent(this._view, constants.SOURCE_DATABASES),
|
|
||||||
targetDatabaseRow,
|
targetDatabaseRow,
|
||||||
|
|
||||||
await createHeadingTextComponent(this._view, constants.AZURE_SQL_TARGET_PAGE_TITLE),
|
await createHeadingTextComponent(
|
||||||
createInformationRow(this._view, constants.AZURE_SQL_TARGET_PAGE_TITLE, (this.migrationStateModel._targetType === MigrationTargetType.SQLVM) ? constants.SUMMARY_VM_TYPE : constants.SUMMARY_MI_TYPE),
|
this._view,
|
||||||
createInformationRow(this._view, constants.SUBSCRIPTION, this.migrationStateModel._targetSubscription.name),
|
constants.AZURE_SQL_TARGET_PAGE_TITLE),
|
||||||
createInformationRow(this._view, constants.LOCATION, await this.migrationStateModel.getLocationDisplayName(this.migrationStateModel._targetServerInstance.location)),
|
createInformationRow(
|
||||||
createInformationRow(this._view, constants.RESOURCE_GROUP, getResourceGroupFromId(this.migrationStateModel._targetServerInstance.id)),
|
this._view,
|
||||||
createInformationRow(this._view, (this.migrationStateModel._targetType === MigrationTargetType.SQLVM) ? constants.SUMMARY_VM_TYPE : constants.SUMMARY_MI_TYPE, await this.migrationStateModel.getLocationDisplayName(this.migrationStateModel._targetServerInstance.name!)),
|
constants.ACCOUNTS_SELECTION_PAGE_TITLE,
|
||||||
|
this.migrationStateModel._azureAccount.displayInfo.displayName),
|
||||||
|
createInformationRow(
|
||||||
|
this._view,
|
||||||
|
constants.AZURE_SQL_TARGET_PAGE_TITLE,
|
||||||
|
isSqlVmTarget
|
||||||
|
? constants.SUMMARY_VM_TYPE
|
||||||
|
: isSqlMiTarget
|
||||||
|
? constants.SUMMARY_MI_TYPE
|
||||||
|
: constants.SUMMARY_SQLDB_TYPE),
|
||||||
|
createInformationRow(
|
||||||
|
this._view,
|
||||||
|
constants.SUBSCRIPTION,
|
||||||
|
this.migrationStateModel._targetSubscription.name),
|
||||||
|
createInformationRow(
|
||||||
|
this._view,
|
||||||
|
constants.LOCATION,
|
||||||
|
await this.migrationStateModel.getLocationDisplayName(
|
||||||
|
this.migrationStateModel._targetServerInstance.location)),
|
||||||
|
createInformationRow(
|
||||||
|
this._view,
|
||||||
|
constants.RESOURCE_GROUP,
|
||||||
|
getResourceGroupFromId(
|
||||||
|
this.migrationStateModel._targetServerInstance.id)),
|
||||||
|
createInformationRow(
|
||||||
|
this._view,
|
||||||
|
(isSqlVmTarget)
|
||||||
|
? constants.SUMMARY_VM_TYPE
|
||||||
|
: (isSqlMiTarget)
|
||||||
|
? constants.SUMMARY_MI_TYPE
|
||||||
|
: constants.SUMMARY_SQLDB_TYPE,
|
||||||
|
await this.migrationStateModel.getLocationDisplayName(
|
||||||
|
this.migrationStateModel._targetServerInstance.name!)),
|
||||||
|
await createHeadingTextComponent(
|
||||||
|
this._view,
|
||||||
|
constants.DATABASE_BACKUP_MIGRATION_MODE_LABEL),
|
||||||
|
createInformationRow(
|
||||||
|
this._view,
|
||||||
|
constants.MODE,
|
||||||
|
this.migrationStateModel._databaseBackup.migrationMode === MigrationMode.ONLINE
|
||||||
|
? constants.DATABASE_BACKUP_MIGRATION_MODE_ONLINE_LABEL
|
||||||
|
: constants.DATABASE_BACKUP_MIGRATION_MODE_OFFLINE_LABEL),
|
||||||
|
]);
|
||||||
|
|
||||||
await createHeadingTextComponent(this._view, constants.DATABASE_BACKUP_MIGRATION_MODE_LABEL),
|
if (this.migrationStateModel._targetType !== MigrationTargetType.SQLDB) {
|
||||||
createInformationRow(this._view, constants.MODE, this.migrationStateModel._databaseBackup.migrationMode === MigrationMode.ONLINE ? constants.DATABASE_BACKUP_MIGRATION_MODE_ONLINE_LABEL : constants.DATABASE_BACKUP_MIGRATION_MODE_OFFLINE_LABEL),
|
this._flexContainer.addItems([
|
||||||
|
await createHeadingTextComponent(
|
||||||
|
this._view,
|
||||||
|
constants.DATABASE_BACKUP_PAGE_TITLE),
|
||||||
|
await this.createNetworkContainerRows()]);
|
||||||
|
}
|
||||||
|
|
||||||
await createHeadingTextComponent(this._view, constants.DATABASE_BACKUP_PAGE_TITLE),
|
this._flexContainer.addItems([
|
||||||
await this.createNetworkContainerRows(),
|
|
||||||
|
|
||||||
await createHeadingTextComponent(this._view, constants.IR_PAGE_TITLE),
|
|
||||||
createInformationRow(this._view, constants.SUBSCRIPTION, this.migrationStateModel._targetSubscription.name),
|
|
||||||
createInformationRow(this._view, constants.LOCATION, await this.migrationStateModel.getLocationDisplayName(this.migrationStateModel._sqlMigrationService?.location!)),
|
|
||||||
createInformationRow(this._view, constants.RESOURCE_GROUP, this.migrationStateModel._sqlMigrationService?.properties?.resourceGroup!),
|
|
||||||
createInformationRow(this._view, constants.IR_PAGE_TITLE, this.migrationStateModel._sqlMigrationService?.name!)
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (this.migrationStateModel._databaseBackup.networkContainerType === NetworkContainerType.NETWORK_SHARE && this.migrationStateModel._nodeNames?.length > 0) {
|
await createHeadingTextComponent(
|
||||||
this._flexContainer.addItem(createInformationRow(this._view, constants.SHIR, this.migrationStateModel._nodeNames.join(', ')));
|
this._view,
|
||||||
|
constants.IR_PAGE_TITLE),
|
||||||
|
createInformationRow(
|
||||||
|
this._view, constants.SUBSCRIPTION,
|
||||||
|
this.migrationStateModel._targetSubscription.name),
|
||||||
|
createInformationRow(
|
||||||
|
this._view,
|
||||||
|
constants.LOCATION,
|
||||||
|
await this.migrationStateModel.getLocationDisplayName(
|
||||||
|
this.migrationStateModel._sqlMigrationService?.location!)),
|
||||||
|
createInformationRow(
|
||||||
|
this._view,
|
||||||
|
constants.RESOURCE_GROUP,
|
||||||
|
this.migrationStateModel._sqlMigrationService?.properties?.resourceGroup!),
|
||||||
|
createInformationRow(
|
||||||
|
this._view,
|
||||||
|
constants.IR_PAGE_TITLE,
|
||||||
|
this.migrationStateModel._sqlMigrationService?.name!)]);
|
||||||
|
|
||||||
|
if (isSqlDbTarget ||
|
||||||
|
(isNetworkShare && this.migrationStateModel._nodeNames?.length > 0)) {
|
||||||
|
|
||||||
|
this._flexContainer.addItem(
|
||||||
|
createInformationRow(
|
||||||
|
this._view,
|
||||||
|
constants.SHIR,
|
||||||
|
this.migrationStateModel._nodeNames.join(', ')));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async onPageLeave(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise<void> {
|
public async onPageLeave(pageChangeInfo: azdata.window.WizardPageChangeInfo): Promise<void> {
|
||||||
this._flexContainer.clearItems();
|
this._flexContainer.clearItems();
|
||||||
this.wizard.registerNavigationValidator(async (pageChangeInfo) => {
|
this.wizard.registerNavigationValidator(async (pageChangeInfo) => true);
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async handleStateChange(e: StateChangeEvent): Promise<void> {
|
protected async handleStateChange(e: StateChangeEvent): Promise<void> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async createNetworkContainerRows(): Promise<azdata.FlexContainer> {
|
private async createNetworkContainerRows(): Promise<azdata.FlexContainer> {
|
||||||
const flexContainer = this._view.modelBuilder.flexContainer().withLayout({
|
const flexContainer = this._view.modelBuilder.flexContainer()
|
||||||
flexFlow: 'column'
|
.withLayout({ flexFlow: 'column' })
|
||||||
}).component();
|
.component();
|
||||||
|
|
||||||
|
const networkShare = this.migrationStateModel._databaseBackup.networkShares[0];
|
||||||
switch (this.migrationStateModel._databaseBackup.networkContainerType) {
|
switch (this.migrationStateModel._databaseBackup.networkContainerType) {
|
||||||
case NetworkContainerType.NETWORK_SHARE:
|
case NetworkContainerType.NETWORK_SHARE:
|
||||||
flexContainer.addItems(
|
flexContainer.addItems([
|
||||||
[
|
createInformationRow(
|
||||||
createInformationRow(this._view, constants.BACKUP_LOCATION, constants.NETWORK_SHARE),
|
this._view,
|
||||||
createInformationRow(this._view, constants.USER_ACCOUNT, this.migrationStateModel._databaseBackup.networkShares[0].windowsUser),
|
constants.BACKUP_LOCATION,
|
||||||
await createHeadingTextComponent(this._view, constants.AZURE_STORAGE_ACCOUNT_TO_UPLOAD_BACKUPS),
|
constants.NETWORK_SHARE),
|
||||||
createInformationRow(this._view, constants.SUBSCRIPTION, this.migrationStateModel._databaseBackup.subscription.name),
|
createInformationRow(
|
||||||
createInformationRow(this._view, constants.LOCATION, this.migrationStateModel._databaseBackup.networkShares[0].storageAccount?.location),
|
this._view,
|
||||||
createInformationRow(this._view, constants.RESOURCE_GROUP, this.migrationStateModel._databaseBackup.networkShares[0].storageAccount?.resourceGroup!),
|
constants.USER_ACCOUNT,
|
||||||
createInformationRow(this._view, constants.STORAGE_ACCOUNT, this.migrationStateModel._databaseBackup.networkShares[0].storageAccount?.name!),
|
networkShare.windowsUser),
|
||||||
]
|
await createHeadingTextComponent(
|
||||||
);
|
this._view,
|
||||||
|
constants.AZURE_STORAGE_ACCOUNT_TO_UPLOAD_BACKUPS),
|
||||||
|
createInformationRow(
|
||||||
|
this._view,
|
||||||
|
constants.SUBSCRIPTION,
|
||||||
|
this.migrationStateModel._databaseBackup.subscription.name),
|
||||||
|
createInformationRow(
|
||||||
|
this._view,
|
||||||
|
constants.LOCATION,
|
||||||
|
networkShare.storageAccount?.location),
|
||||||
|
createInformationRow(
|
||||||
|
this._view,
|
||||||
|
constants.RESOURCE_GROUP,
|
||||||
|
networkShare.storageAccount?.resourceGroup!),
|
||||||
|
createInformationRow(
|
||||||
|
this._view,
|
||||||
|
constants.STORAGE_ACCOUNT,
|
||||||
|
networkShare.storageAccount?.name!),
|
||||||
|
]);
|
||||||
break;
|
break;
|
||||||
case NetworkContainerType.BLOB_CONTAINER:
|
case NetworkContainerType.BLOB_CONTAINER:
|
||||||
flexContainer.addItems(
|
flexContainer.addItems([
|
||||||
[
|
createInformationRow(
|
||||||
createInformationRow(this._view, constants.TYPE, constants.BLOB_CONTAINER),
|
this._view,
|
||||||
createInformationRow(this._view, constants.SUMMARY_AZURE_STORAGE_SUBSCRIPTION, this.migrationStateModel._databaseBackup.subscription.name)
|
constants.TYPE,
|
||||||
]
|
constants.BLOB_CONTAINER),
|
||||||
);
|
createInformationRow(
|
||||||
|
this._view,
|
||||||
|
constants.SUMMARY_AZURE_STORAGE_SUBSCRIPTION,
|
||||||
|
this.migrationStateModel._databaseBackup.subscription.name)]);
|
||||||
}
|
}
|
||||||
return flexContainer;
|
return flexContainer;
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -19,6 +19,7 @@ import { sendSqlMigrationActionEvent, TelemetryAction, TelemetryViews, logError
|
|||||||
import * as styles from '../constants/styles';
|
import * as styles from '../constants/styles';
|
||||||
import { MigrationLocalStorage, MigrationServiceContext } from '../models/migrationLocalStorage';
|
import { MigrationLocalStorage, MigrationServiceContext } from '../models/migrationLocalStorage';
|
||||||
import { azureResource } from 'azurecore';
|
import { azureResource } from 'azurecore';
|
||||||
|
import { ServiceContextChangeEvent } from '../dashboard/tabBase';
|
||||||
|
|
||||||
export const WIZARD_INPUT_COMPONENT_WIDTH = '600px';
|
export const WIZARD_INPUT_COMPONENT_WIDTH = '600px';
|
||||||
export class WizardController {
|
export class WizardController {
|
||||||
@@ -27,7 +28,7 @@ export class WizardController {
|
|||||||
constructor(
|
constructor(
|
||||||
private readonly extensionContext: vscode.ExtensionContext,
|
private readonly extensionContext: vscode.ExtensionContext,
|
||||||
private readonly _model: MigrationStateModel,
|
private readonly _model: MigrationStateModel,
|
||||||
private readonly _onClosedCallback: () => Promise<void>) {
|
private readonly _serviceContextChangedEvent: vscode.EventEmitter<ServiceContextChangeEvent>) {
|
||||||
}
|
}
|
||||||
|
|
||||||
public async openWizard(connectionId: string): Promise<void> {
|
public async openWizard(connectionId: string): Promise<void> {
|
||||||
@@ -40,7 +41,11 @@ export class WizardController {
|
|||||||
|
|
||||||
private async createWizard(stateModel: MigrationStateModel): Promise<void> {
|
private async createWizard(stateModel: MigrationStateModel): Promise<void> {
|
||||||
const serverName = (await stateModel.getSourceConnectionProfile()).serverName;
|
const serverName = (await stateModel.getSourceConnectionProfile()).serverName;
|
||||||
this._wizardObject = azdata.window.createWizard(loc.WIZARD_TITLE(serverName), 'MigrationWizard', 'wide');
|
this._wizardObject = azdata.window.createWizard(
|
||||||
|
loc.WIZARD_TITLE(serverName),
|
||||||
|
'MigrationWizard',
|
||||||
|
'wide');
|
||||||
|
|
||||||
this._wizardObject.generateScriptButton.enabled = false;
|
this._wizardObject.generateScriptButton.enabled = false;
|
||||||
this._wizardObject.generateScriptButton.hidden = true;
|
this._wizardObject.generateScriptButton.hidden = true;
|
||||||
const saveAndCloseButton = azdata.window.createButton(loc.SAVE_AND_CLOSE);
|
const saveAndCloseButton = azdata.window.createButton(loc.SAVE_AND_CLOSE);
|
||||||
@@ -60,8 +65,7 @@ export class WizardController {
|
|||||||
migrationModePage,
|
migrationModePage,
|
||||||
databaseBackupPage,
|
databaseBackupPage,
|
||||||
integrationRuntimePage,
|
integrationRuntimePage,
|
||||||
summaryPage
|
summaryPage];
|
||||||
];
|
|
||||||
|
|
||||||
this._wizardObject.pages = pages.map(p => p.getwizardPage());
|
this._wizardObject.pages = pages.map(p => p.getwizardPage());
|
||||||
|
|
||||||
@@ -82,17 +86,23 @@ export class WizardController {
|
|||||||
|
|
||||||
// if the user selected network share and selected save & close afterwards, it should always return to the database backup page so that
|
// if the user selected network share and selected save & close afterwards, it should always return to the database backup page so that
|
||||||
// the user can input their password again
|
// the user can input their password again
|
||||||
if (this._model.savedInfo.closedPage >= Page.DatabaseBackup && this._model.savedInfo.networkContainerType === NetworkContainerType.NETWORK_SHARE) {
|
if (this._model.savedInfo.closedPage >= Page.DatabaseBackup &&
|
||||||
|
this._model.savedInfo.networkContainerType === NetworkContainerType.NETWORK_SHARE) {
|
||||||
wizardSetupPromises.push(this._wizardObject.setCurrentPage(Page.DatabaseBackup));
|
wizardSetupPromises.push(this._wizardObject.setCurrentPage(Page.DatabaseBackup));
|
||||||
} else {
|
} else {
|
||||||
wizardSetupPromises.push(this._wizardObject.setCurrentPage(this._model.savedInfo.closedPage));
|
wizardSetupPromises.push(this._wizardObject.setCurrentPage(this._model.savedInfo.closedPage));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this._model.extensionContext.subscriptions.push(this._wizardObject.onPageChanged(async (pageChangeInfo: azdata.window.WizardPageChangeInfo) => {
|
this._model.extensionContext.subscriptions.push(
|
||||||
|
this._wizardObject.onPageChanged(
|
||||||
|
async (pageChangeInfo: azdata.window.WizardPageChangeInfo) => {
|
||||||
const newPage = pageChangeInfo.newPage;
|
const newPage = pageChangeInfo.newPage;
|
||||||
const lastPage = pageChangeInfo.lastPage;
|
const lastPage = pageChangeInfo.lastPage;
|
||||||
this.sendPageButtonClickEvent(pageChangeInfo).catch(e => logError(TelemetryViews.MigrationWizardController, 'ErrorSendingPageButtonClick', e));
|
this.sendPageButtonClickEvent(pageChangeInfo)
|
||||||
|
.catch(e => logError(
|
||||||
|
TelemetryViews.MigrationWizardController,
|
||||||
|
'ErrorSendingPageButtonClick', e));
|
||||||
await pages[lastPage]?.onPageLeave(pageChangeInfo);
|
await pages[lastPage]?.onPageLeave(pageChangeInfo);
|
||||||
await pages[newPage]?.onPageEnter(pageChangeInfo);
|
await pages[newPage]?.onPageEnter(pageChangeInfo);
|
||||||
}));
|
}));
|
||||||
@@ -110,11 +120,11 @@ export class WizardController {
|
|||||||
await Promise.all(wizardSetupPromises);
|
await Promise.all(wizardSetupPromises);
|
||||||
this._model.extensionContext.subscriptions.push(
|
this._model.extensionContext.subscriptions.push(
|
||||||
this._wizardObject.onPageChanged(
|
this._wizardObject.onPageChanged(
|
||||||
async (pageChangeInfo: azdata.window.WizardPageChangeInfo) => {
|
async (pageChangeInfo: azdata.window.WizardPageChangeInfo) =>
|
||||||
await pages[0].onPageEnter(pageChangeInfo);
|
await pages[0].onPageEnter(pageChangeInfo)));
|
||||||
}));
|
|
||||||
|
|
||||||
this._disposables.push(saveAndCloseButton.onClick(async () => {
|
this._disposables.push(
|
||||||
|
saveAndCloseButton.onClick(async () => {
|
||||||
await stateModel.saveInfo(serverName, this._wizardObject.currentPage);
|
await stateModel.saveInfo(serverName, this._wizardObject.currentPage);
|
||||||
await this._wizardObject.close();
|
await this._wizardObject.close();
|
||||||
|
|
||||||
@@ -123,7 +133,8 @@ export class WizardController {
|
|||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this._disposables.push(this._wizardObject.cancelButton.onClick(e => {
|
this._disposables.push(
|
||||||
|
this._wizardObject.cancelButton.onClick(e => {
|
||||||
sendSqlMigrationActionEvent(
|
sendSqlMigrationActionEvent(
|
||||||
TelemetryViews.SqlMigrationWizard,
|
TelemetryViews.SqlMigrationWizard,
|
||||||
TelemetryAction.PageButtonClick,
|
TelemetryAction.PageButtonClick,
|
||||||
@@ -131,17 +142,20 @@ export class WizardController {
|
|||||||
...this.getTelemetryProps(),
|
...this.getTelemetryProps(),
|
||||||
'buttonPressed': TelemetryAction.Cancel,
|
'buttonPressed': TelemetryAction.Cancel,
|
||||||
'pageTitle': this._wizardObject.pages[this._wizardObject.currentPage].title
|
'pageTitle': this._wizardObject.pages[this._wizardObject.currentPage].title
|
||||||
}, {});
|
},
|
||||||
|
{});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this._wizardObject.doneButton.label = loc.START_MIGRATION_TEXT;
|
this._wizardObject.doneButton.label = loc.START_MIGRATION_TEXT;
|
||||||
|
|
||||||
this._disposables.push(
|
this._disposables.push(
|
||||||
this._wizardObject.doneButton.onClick(async (e) => {
|
this._wizardObject.doneButton.onClick(async (e) => {
|
||||||
|
try {
|
||||||
await stateModel.startMigration();
|
await stateModel.startMigration();
|
||||||
await this.updateServiceContext(stateModel);
|
await this.updateServiceContext(stateModel, this._serviceContextChangedEvent);
|
||||||
await this._onClosedCallback();
|
} catch (e) {
|
||||||
|
logError(TelemetryViews.MigrationWizardController, 'StartMigrationFailed', e);
|
||||||
|
} finally {
|
||||||
sendSqlMigrationActionEvent(
|
sendSqlMigrationActionEvent(
|
||||||
TelemetryViews.SqlMigrationWizard,
|
TelemetryViews.SqlMigrationWizard,
|
||||||
TelemetryAction.PageButtonClick,
|
TelemetryAction.PageButtonClick,
|
||||||
@@ -149,11 +163,16 @@ export class WizardController {
|
|||||||
...this.getTelemetryProps(),
|
...this.getTelemetryProps(),
|
||||||
'buttonPressed': TelemetryAction.Done,
|
'buttonPressed': TelemetryAction.Done,
|
||||||
'pageTitle': this._wizardObject.pages[this._wizardObject.currentPage].title
|
'pageTitle': this._wizardObject.pages[this._wizardObject.currentPage].title
|
||||||
}, {});
|
},
|
||||||
|
{});
|
||||||
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
private async updateServiceContext(stateModel: MigrationStateModel): Promise<void> {
|
private async updateServiceContext(
|
||||||
|
stateModel: MigrationStateModel,
|
||||||
|
serviceContextChangedEvent: vscode.EventEmitter<ServiceContextChangeEvent>): Promise<void> {
|
||||||
|
|
||||||
const resourceGroup = this._getResourceGroupByName(
|
const resourceGroup = this._getResourceGroupByName(
|
||||||
stateModel._resourceGroups,
|
stateModel._resourceGroups,
|
||||||
stateModel._sqlMigrationService?.properties.resourceGroup);
|
stateModel._sqlMigrationService?.properties.resourceGroup);
|
||||||
@@ -174,18 +193,28 @@ export class WizardController {
|
|||||||
location: location,
|
location: location,
|
||||||
resourceGroup: resourceGroup,
|
resourceGroup: resourceGroup,
|
||||||
migrationService: stateModel._sqlMigrationService,
|
migrationService: stateModel._sqlMigrationService,
|
||||||
});
|
},
|
||||||
|
serviceContextChangedEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getResourceGroupByName(resourceGroups: azureResource.AzureResourceResourceGroup[], displayName?: string): azureResource.AzureResourceResourceGroup | undefined {
|
private _getResourceGroupByName(
|
||||||
|
resourceGroups: azureResource.AzureResourceResourceGroup[],
|
||||||
|
displayName?: string): azureResource.AzureResourceResourceGroup | undefined {
|
||||||
|
|
||||||
return resourceGroups.find(rg => rg.name === displayName);
|
return resourceGroups.find(rg => rg.name === displayName);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getLocationByValue(locations: azureResource.AzureLocation[], name?: string): azureResource.AzureLocation | undefined {
|
private _getLocationByValue(
|
||||||
|
locations: azureResource.AzureLocation[],
|
||||||
|
name?: string): azureResource.AzureLocation | undefined {
|
||||||
|
|
||||||
return locations.find(loc => loc.name === name);
|
return locations.find(loc => loc.name === name);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getSubscriptionFromResourceId(subscriptions: azureResource.AzureResourceSubscription[], resourceId?: string): azureResource.AzureResourceSubscription | undefined {
|
private _getSubscriptionFromResourceId(
|
||||||
|
subscriptions: azureResource.AzureResourceSubscription[],
|
||||||
|
resourceId?: string): azureResource.AzureResourceSubscription | undefined {
|
||||||
|
|
||||||
let parts = resourceId?.split('/subscriptions/');
|
let parts = resourceId?.split('/subscriptions/');
|
||||||
if (parts?.length && parts?.length > 1) {
|
if (parts?.length && parts?.length > 1) {
|
||||||
parts = parts[1]?.split('/resourcegroups/');
|
parts = parts[1]?.split('/resourcegroups/');
|
||||||
@@ -198,7 +227,9 @@ export class WizardController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async sendPageButtonClickEvent(pageChangeInfo: azdata.window.WizardPageChangeInfo) {
|
private async sendPageButtonClickEvent(pageChangeInfo: azdata.window.WizardPageChangeInfo) {
|
||||||
const buttonPressed = pageChangeInfo.newPage > pageChangeInfo.lastPage ? TelemetryAction.Next : TelemetryAction.Prev;
|
const buttonPressed = pageChangeInfo.newPage > pageChangeInfo.lastPage
|
||||||
|
? TelemetryAction.Next
|
||||||
|
: TelemetryAction.Prev;
|
||||||
const pageTitle = this._wizardObject.pages[pageChangeInfo.lastPage]?.title;
|
const pageTitle = this._wizardObject.pages[pageChangeInfo.lastPage]?.title;
|
||||||
sendSqlMigrationActionEvent(
|
sendSqlMigrationActionEvent(
|
||||||
TelemetryViews.SqlMigrationWizard,
|
TelemetryViews.SqlMigrationWizard,
|
||||||
@@ -207,7 +238,8 @@ export class WizardController {
|
|||||||
...this.getTelemetryProps(),
|
...this.getTelemetryProps(),
|
||||||
'buttonPressed': buttonPressed,
|
'buttonPressed': buttonPressed,
|
||||||
'pageTitle': pageTitle
|
'pageTitle': pageTitle
|
||||||
}, {});
|
},
|
||||||
|
{});
|
||||||
}
|
}
|
||||||
|
|
||||||
private getTelemetryProps() {
|
private getTelemetryProps() {
|
||||||
@@ -221,33 +253,38 @@ export class WizardController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createInformationRow(view: azdata.ModelView, label: string, value: string): azdata.FlexContainer {
|
export function createInformationRow(
|
||||||
|
view: azdata.ModelView,
|
||||||
|
label: string,
|
||||||
|
value: string): azdata.FlexContainer {
|
||||||
|
|
||||||
return view.modelBuilder.flexContainer()
|
return view.modelBuilder.flexContainer()
|
||||||
.withLayout(
|
.withLayout({ flexFlow: 'row', alignItems: 'center', })
|
||||||
{
|
.withItems([
|
||||||
flexFlow: 'row',
|
createLabelTextComponent(
|
||||||
alignItems: 'center',
|
view,
|
||||||
})
|
label,
|
||||||
.withItems(
|
|
||||||
[
|
|
||||||
createLabelTextComponent(view, label,
|
|
||||||
{
|
{
|
||||||
...styles.BODY_CSS,
|
...styles.BODY_CSS,
|
||||||
'margin': '4px 0px',
|
'margin': '4px 0px',
|
||||||
'width': '300px',
|
'width': '300px',
|
||||||
}
|
}),
|
||||||
),
|
createTextComponent(
|
||||||
createTextComponent(view, value,
|
view,
|
||||||
|
value,
|
||||||
{
|
{
|
||||||
...styles.BODY_CSS,
|
...styles.BODY_CSS,
|
||||||
'margin': '4px 0px',
|
'margin': '4px 0px',
|
||||||
'width': '300px',
|
'width': '300px',
|
||||||
}
|
})])
|
||||||
)
|
.component();
|
||||||
]).component();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createHeadingTextComponent(view: azdata.ModelView, value: string, firstElement: boolean = false): Promise<azdata.TextComponent> {
|
export async function createHeadingTextComponent(
|
||||||
|
view: azdata.ModelView,
|
||||||
|
value: string,
|
||||||
|
firstElement: boolean = false): Promise<azdata.TextComponent> {
|
||||||
|
|
||||||
const component = createTextComponent(view, value);
|
const component = createTextComponent(view, value);
|
||||||
await component.updateCssStyles({
|
await component.updateCssStyles({
|
||||||
...styles.LABEL_CSS,
|
...styles.LABEL_CSS,
|
||||||
@@ -256,14 +293,20 @@ export async function createHeadingTextComponent(view: azdata.ModelView, value:
|
|||||||
return component;
|
return component;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createLabelTextComponent(view: azdata.ModelView, value: string, styles: { [key: string]: string; } = { 'width': '300px' }): azdata.TextComponent {
|
export function createLabelTextComponent(
|
||||||
const component = createTextComponent(view, value, styles);
|
view: azdata.ModelView,
|
||||||
return component;
|
value: string,
|
||||||
|
styles: { [key: string]: string; } = { 'width': '300px' }): azdata.TextComponent {
|
||||||
|
|
||||||
|
return createTextComponent(view, value, styles);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createTextComponent(view: azdata.ModelView, value: string, styles: { [key: string]: string; } = { 'width': '300px' }): azdata.TextComponent {
|
export function createTextComponent(
|
||||||
return view.modelBuilder.text().withProps({
|
view: azdata.ModelView,
|
||||||
value: value,
|
value: string,
|
||||||
CSSStyles: styles
|
styles: { [key: string]: string; } = { 'width': '300px' }): azdata.TextComponent {
|
||||||
}).component();
|
|
||||||
|
return view.modelBuilder.text()
|
||||||
|
.withProps({ value: value, CSSStyles: styles })
|
||||||
|
.component();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user