diff --git a/extensions/sql-migration/images/background.svg b/extensions/sql-migration/images/background.svg
new file mode 100644
index 0000000000..9bc057df35
--- /dev/null
+++ b/extensions/sql-migration/images/background.svg
@@ -0,0 +1,38 @@
+
diff --git a/extensions/sql-migration/images/backgroundLogo.svg b/extensions/sql-migration/images/backgroundLogo.svg
new file mode 100644
index 0000000000..024ff98147
--- /dev/null
+++ b/extensions/sql-migration/images/backgroundLogo.svg
@@ -0,0 +1,32 @@
+
diff --git a/extensions/sql-migration/images/createNotebook.svg b/extensions/sql-migration/images/createNotebook.svg
new file mode 100644
index 0000000000..c8d1afea88
--- /dev/null
+++ b/extensions/sql-migration/images/createNotebook.svg
@@ -0,0 +1 @@
+
diff --git a/extensions/sql-migration/images/migration.svg b/extensions/sql-migration/images/migration.svg
new file mode 100644
index 0000000000..35d958684b
--- /dev/null
+++ b/extensions/sql-migration/images/migration.svg
@@ -0,0 +1,32 @@
+
diff --git a/extensions/sql-migration/images/sqlMiImportHelpThumbnail.svg b/extensions/sql-migration/images/sqlMiImportHelpThumbnail.svg
new file mode 100644
index 0000000000..94fbb8932b
--- /dev/null
+++ b/extensions/sql-migration/images/sqlMiImportHelpThumbnail.svg
@@ -0,0 +1,9 @@
+
diff --git a/extensions/sql-migration/images/sqlVmImportHelpThumbnail.svg b/extensions/sql-migration/images/sqlVmImportHelpThumbnail.svg
new file mode 100644
index 0000000000..1f1f586e01
--- /dev/null
+++ b/extensions/sql-migration/images/sqlVmImportHelpThumbnail.svg
@@ -0,0 +1,9 @@
+
diff --git a/extensions/sql-migration/package.json b/extensions/sql-migration/package.json
index a0dad23c8b..7d9654830d 100644
--- a/extensions/sql-migration/package.json
+++ b/extensions/sql-migration/package.json
@@ -12,6 +12,7 @@
"azdata": ">=1.19.0"
},
"activationEvents": [
+ "onDashboardOpen",
"onCommand:sqlmigration.start",
"onCommand:sqlmigration.testDialog",
"onCommand:sqlmigration.openNotebooks"
@@ -41,6 +42,35 @@
"title": "%migration-notebook-command-title%",
"category": "SQL Migration"
}
+ ],
+ "dashboard.tabs": [
+ {
+ "id": "migration-dashboard",
+ "description": "%migration-dashboard-title%",
+ "provider": "MSSQL",
+ "title": "%migration-dashboard-title%",
+ "icon": {
+ "light": "./images/migration.svg",
+ "dark": "./images/migration.svg"
+ },
+ "when": "connectionProvider == 'MSSQL' && !mssql:iscloud",
+ "container": {
+ "grid-container": [
+ {
+ "name": "",
+ "row": 0,
+ "col": 1,
+ "rowspan": 5,
+ "colspan": 5,
+ "widget": {
+ "modelview": {
+ "id": "migration.dashboard"
+ }
+ }
+ }
+ ]
+ }
+ }
]
},
"dependencies": {
diff --git a/extensions/sql-migration/package.nls.json b/extensions/sql-migration/package.nls.json
index 86980920da..f64b7e61c2 100644
--- a/extensions/sql-migration/package.nls.json
+++ b/extensions/sql-migration/package.nls.json
@@ -1,5 +1,7 @@
{
"displayName": "SQL Migration",
"description": "SQL migration description",
- "migration-notebook-command-title": "Open SQL migration notebooks"
+ "migration-notebook-command-title": "Open SQL migration notebooks",
+ "migration-dashboard-title": "SQL Migration",
+ "migration-dashboard-tasks": "Migration Tasks"
}
diff --git a/extensions/sql-migration/src/api/azure.ts b/extensions/sql-migration/src/api/azure.ts
index 0687d962c6..d249f3d72b 100644
--- a/extensions/sql-migration/src/api/azure.ts
+++ b/extensions/sql-migration/src/api/azure.ts
@@ -127,8 +127,8 @@ export async function getMigrationControllerAuthKeys(account: azdata.Account, su
throw new Error(response.errors.toString());
}
return {
- keyName1: response?.response?.data?.keyName1 ?? '',
- keyName2: response?.response?.data?.keyName2 ?? ''
+ authKey1: response?.response?.data?.authKey1 ?? '',
+ authKey2: response?.response?.data?.authKey2 ?? ''
};
}
@@ -157,7 +157,7 @@ export async function getMigrationControllerMonitoringData(account: azdata.Accou
return response.response.data;
}
-export async function startDatabaseMigration(account: azdata.Account, subscription: Subscription, resourceGroupName: string, regionName: string, managedInstance: string, migrationControllerName: string, requestBody: StartDatabaseMigrationRequest): Promise {
+export async function startDatabaseMigration(account: azdata.Account, subscription: Subscription, resourceGroupName: string, regionName: string, managedInstance: string, migrationControllerName: string, requestBody: StartDatabaseMigrationRequest): Promise {
const api = await getAzureCoreAPI();
const host = `https://${regionName}.management.azure.com`;
const path = `/subscriptions/${subscription.id}/resourceGroups/${resourceGroupName}/providers/Microsoft.Sql/managedInstances/${managedInstance}/providers/Microsoft.DataMigration/databaseMigrations/${migrationControllerName}?api-version=2020-09-01-preview`;
@@ -166,12 +166,23 @@ export async function startDatabaseMigration(account: azdata.Account, subscripti
throw new Error(response.errors.toString());
}
return {
- errors: response.errors,
status: response.response.status,
databaseMigration: response.response.data
};
}
+export async function getMigrationStatus(account: azdata.Account, subscription: Subscription, migration: DatabaseMigration): Promise {
+ const api = await getAzureCoreAPI();
+ const host = `https://eastus2euap.management.azure.com`;
+ const path = `${migration.id}?$expand=MigrationStatusDetails&api-version=2020-09-01-preview`;
+ 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());
+ }
+ return {
+ result: response.response.data
+ };
+}
/**
* For now only east us euap is supported. Actual API calls will be added in the public release.
@@ -223,8 +234,8 @@ export interface MigrationController {
}
export interface GetMigrationControllerAuthKeysResult {
- keyName1: string,
- keyName2: string
+ authKey1: string,
+ authKey2: string
}
export interface GetStorageAccountAccessKeysResult {
@@ -285,3 +296,8 @@ export interface DatabaseMigration {
name: string,
type: string
}
+
+export interface StartDatabaseMigrationResponse {
+ status: number,
+ databaseMigration: DatabaseMigration
+}
diff --git a/extensions/sql-migration/src/constants/iconPathHelper.ts b/extensions/sql-migration/src/constants/iconPathHelper.ts
index 2495353572..64c3eec683 100644
--- a/extensions/sql-migration/src/constants/iconPathHelper.ts
+++ b/extensions/sql-migration/src/constants/iconPathHelper.ts
@@ -11,21 +11,37 @@ export interface IconPath {
}
export class IconPathHelper {
- private static context: vscode.ExtensionContext;
-
public static copy: IconPath;
public static refresh: IconPath;
+ public static sqlMiImportHelpThumbnail: IconPath;
+ public static sqlVmImportHelpThumbnail: IconPath;
+ public static migrationDashboardHeaderBackground: IconPath;
+ public static sqlMigrationLogo: IconPath;
public static setExtensionContext(context: vscode.ExtensionContext) {
- IconPathHelper.context = context;
IconPathHelper.copy = {
- light: IconPathHelper.context.asAbsolutePath('images/copy.svg'),
- dark: IconPathHelper.context.asAbsolutePath('images/copy.svg')
+ light: context.asAbsolutePath('images/copy.svg'),
+ dark: context.asAbsolutePath('images/copy.svg')
};
IconPathHelper.refresh = {
light: context.asAbsolutePath('images/refresh.svg'),
dark: context.asAbsolutePath('images/refresh.svg')
};
-
+ IconPathHelper.sqlMiImportHelpThumbnail = {
+ light: context.asAbsolutePath('images/sqlMiImportHelpThumbnail.svg'),
+ dark: context.asAbsolutePath('images/sqlMiImportHelpThumbnail.svg')
+ };
+ IconPathHelper.sqlVmImportHelpThumbnail = {
+ light: context.asAbsolutePath('images/sqlVmImportHelpThumbnail.svg'),
+ dark: context.asAbsolutePath('images/sqlVmImportHelpThumbnail.svg')
+ };
+ IconPathHelper.migrationDashboardHeaderBackground = {
+ light: context.asAbsolutePath('images/background.svg'),
+ dark: context.asAbsolutePath('images/background.svg')
+ };
+ IconPathHelper.sqlMigrationLogo = {
+ light: context.asAbsolutePath('images/migration.svg'),
+ dark: context.asAbsolutePath('images/migration.svg')
+ };
}
}
diff --git a/extensions/sql-migration/src/dashboard/sqlServerDashboard.ts b/extensions/sql-migration/src/dashboard/sqlServerDashboard.ts
new file mode 100644
index 0000000000..7018658f46
--- /dev/null
+++ b/extensions/sql-migration/src/dashboard/sqlServerDashboard.ts
@@ -0,0 +1,499 @@
+/*---------------------------------------------------------------------------------------------
+ * 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 { MigrationLocalStorage } from '../models/migrationLocalStorage';
+import * as loc from '../models/strings';
+import { IconPathHelper } from '../constants/iconPathHelper';
+
+interface IActionMetadata {
+ title?: string,
+ description?: string,
+ link?: string,
+ iconPath?: { light: string | vscode.Uri; dark: string | vscode.Uri },
+ command?: string;
+}
+
+const maxWidth = 800;
+
+export class DashboardWidget {
+
+ private _migrationStatusCardsContainer!: azdata.FlexContainer;
+ private _view!: azdata.ModelView;
+
+ constructor() {
+ }
+
+ public register(): void {
+ azdata.ui.registerModelViewProvider('migration.dashboard', async (view) => {
+ this._view = view;
+ const container = view.modelBuilder.flexContainer().withLayout({
+ flexFlow: 'column',
+ width: '100%',
+ height: '100%'
+ }).component();
+ const header = this.createHeader(view);
+
+ const tasksContainer = await this.createTasks(view);
+
+ container.addItem(header, {
+ CSSStyles: {
+ 'background-image': `url(${vscode.Uri.file(IconPathHelper.migrationDashboardHeaderBackground.light)})`,
+ 'width': '1100px',
+ 'height': '300px',
+ 'background-size': '100%',
+ }
+ });
+ header.addItem(tasksContainer, {
+ CSSStyles: {
+ 'width': `${maxWidth}px`,
+ 'height': '150px',
+ }
+ });
+
+ header.addItem(await this.createFooter(view), {
+ CSSStyles: {
+ 'margin-top': '20px'
+ }
+ });
+
+ const mainContainer = view.modelBuilder.flexContainer()
+ .withLayout({
+ flexFlow: 'column',
+ width: '100%',
+ height: '100%',
+ position: 'absolute'
+ }).component();
+ mainContainer.addItem(container, {
+ CSSStyles: { 'padding-top': '25px', 'padding-left': '5px' }
+ });
+ await view.initializeModel(mainContainer);
+
+ this.refreshMigrations();
+ });
+ }
+
+ private createHeader(view: azdata.ModelView): azdata.FlexContainer {
+ const header = view.modelBuilder.flexContainer().withLayout({
+ flexFlow: 'column',
+ width: maxWidth,
+ }).component();
+ const titleComponent = view.modelBuilder.text().withProps({
+ value: loc.DASHBOARD_TITLE,
+ CSSStyles: {
+ 'font-size': '36px',
+ }
+ }).component();
+ const descComponent = view.modelBuilder.text().withProps({
+ value: loc.DASHBOARD_DESCRIPTION,
+ CSSStyles: {
+ 'font-size': '12px',
+ }
+ }).component();
+ header.addItems([titleComponent, descComponent], {
+ CSSStyles: {
+ 'width': `${maxWidth}px`,
+ 'padding-left': '20px'
+ }
+ });
+
+ return header;
+ }
+
+ private async createTasks(view: azdata.ModelView): Promise {
+ const tasksContainer = view.modelBuilder.flexContainer().withLayout({
+ flexFlow: 'row',
+ width: '100%',
+ height: '50px',
+ }).component();
+
+ const migrateButtonMetadata: IActionMetadata = {
+ title: loc.DASHBOARD_MIGRATE_TASK_BUTTON_TITLE,
+ description: loc.DASHBOARD_MIGRATE_TASK_BUTTON_DESCRIPTION,
+ iconPath: IconPathHelper.sqlMigrationLogo,
+ command: 'sqlmigration.start'
+ };
+
+ const preRequisiteListTitle = view.modelBuilder.text().withProps({
+ value: loc.PRE_REQ_TITLE,
+ CSSStyles: {
+ 'font-size': '14px',
+ 'padding-left': '15px',
+ 'margin-bottom': '-5px'
+ }
+ }).component();
+
+ const migrateButton = this.createTaskButton(view, migrateButtonMetadata);
+
+ const points = `• ${loc.PRE_REQ_1}
+• ${loc.PRE_REQ_2}
+• ${loc.PRE_REQ_3}`;
+
+ const preRequisiteListElement = view.modelBuilder.text().withProps({
+ value: points,
+ CSSStyles: {
+ 'padding-left': '15px'
+ }
+ }).component();
+
+ const preRequisiteLearnMoreLink = view.modelBuilder.hyperlink().withProps({
+ label: loc.LEARN_MORE,
+ url: '', //TODO: add link for the pre req document.
+ CSSStyles: {
+ 'padding-left': '10px'
+ }
+ }).component();
+
+ const preReqContainer = view.modelBuilder.flexContainer().withItems([
+ preRequisiteListTitle,
+ preRequisiteListElement
+ ]).withLayout({
+ flexFlow: 'column'
+ }).component();
+
+ preReqContainer.addItem(preRequisiteLearnMoreLink, {
+ CSSStyles: {
+ 'padding-left': '10px'
+ }
+ });
+
+
+ tasksContainer.addItem(migrateButton, {
+ CSSStyles: {
+ 'margin-top': '20px',
+ 'padding': '10px'
+ }
+ });
+ tasksContainer.addItems([preReqContainer], {
+ CSSStyles: {
+ 'padding': '10px'
+ }
+ });
+
+ return tasksContainer;
+ }
+
+ private createTaskButton(view: azdata.ModelView, taskMetaData: IActionMetadata): azdata.Component {
+ const maxHeight: number = 84;
+ const maxWidth: number = 236;
+ const buttonContainer = view.modelBuilder.button().withProps({
+ buttonType: azdata.ButtonType.Informational,
+ description: taskMetaData.description,
+ height: maxHeight,
+ iconHeight: 32,
+ iconPath: taskMetaData.iconPath,
+ iconWidth: 32,
+ label: taskMetaData.title,
+ title: taskMetaData.title,
+ width: maxWidth,
+ }).component();
+ buttonContainer.onDidClick(async () => {
+ if (taskMetaData.command) {
+ await vscode.commands.executeCommand(taskMetaData.command);
+ }
+ });
+ return view.modelBuilder.divContainer().withItems([buttonContainer]).component();
+ }
+
+ private async refreshMigrations(): Promise {
+ this._migrationStatusCardsContainer.clearItems();
+ const currentConnection = (await azdata.connection.getCurrentConnection());
+ const getMigrations = MigrationLocalStorage.getMigrations(currentConnection);
+ getMigrations.forEach((migration) => {
+ const button = this._view.modelBuilder.button().withProps({
+ label: `Migration to ${migration.targetManagedInstance.name} using controller ${migration.migrationContext.name}`
+ }).component();
+ button.onDidClick(async (e) => {
+ });
+ this._migrationStatusCardsContainer.addItem(
+ button
+ );
+ });
+ }
+
+ private async createFooter(view: azdata.ModelView): Promise {
+ const footerContainer = view.modelBuilder.flexContainer().withLayout({
+ flexFlow: 'row',
+ width: maxWidth,
+ height: '500px',
+ justifyContent: 'flex-start'
+ }).component();
+ const statusContainer = await this.createMigrationStatusContainer(view);
+ const videoLinksContainer = this.createVideoLinks(view);
+ footerContainer.addItem(statusContainer);
+ footerContainer.addItem(videoLinksContainer, {
+ CSSStyles: {
+ 'padding-left': '10px',
+ }
+ });
+
+ return footerContainer;
+ }
+
+ private async createMigrationStatusContainer(view: azdata.ModelView): Promise {
+ const statusContainer = view.modelBuilder.flexContainer().withLayout({
+ flexFlow: 'column',
+ width: '400px',
+ height: '280px',
+ justifyContent: 'flex-start',
+ }).withProps({
+ CSSStyles: {
+ 'border': '1px solid rgba(0, 0, 0, 0.1)',
+ 'padding': '15px'
+ }
+ }).component();
+
+ const statusContainerTitle = view.modelBuilder.text().withProps({
+ value: loc.DATABASE_MIGRATION_STATUS,
+ CSSStyles: {
+ 'font-size': '18px',
+ 'font-weight': 'bold',
+ 'margin': '0px',
+ 'width': '290px'
+ }
+ }).component();
+
+ const viewAllButton = view.modelBuilder.hyperlink().withProps({
+ label: loc.VIEW_ALL,
+ url: ''
+ }).component();
+
+ const refreshButton = view.modelBuilder.hyperlink().withProps({
+ label: loc.REFRESH,
+ url: '',
+ CSSStyles: {
+ 'text-align': 'right'
+ }
+ }).component();
+
+ refreshButton.onDidClick((e) => {
+ this.refreshMigrations();
+ });
+
+ const buttonContainer = view.modelBuilder.flexContainer().withLayout({
+ justifyContent: 'flex-end',
+ }).component();
+
+ buttonContainer.addItem(viewAllButton, {
+ flex: 'auto',
+ CSSStyles: {
+ 'border-right': '1px solid rgba(0, 0, 0, 0.7)',
+ 'width': '40px',
+ }
+ });
+
+ buttonContainer.addItem(refreshButton, {
+ flex: 'auto',
+ CSSStyles: {
+ 'margin-left': '5px',
+ 'width': '25px'
+ }
+ });
+
+ const header = view.modelBuilder.flexContainer().withItems(
+ [
+ statusContainerTitle,
+ buttonContainer
+ ]
+ ).withLayout({
+ flexFlow: 'row'
+ }).component();
+
+
+
+ this._migrationStatusCardsContainer = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column' }).component();
+
+ statusContainer.addItem(
+ header, {
+ CSSStyles: {
+ 'padding': '0px',
+ 'padding-right': '5px',
+ 'padding-top': '10px',
+ 'height': '10px',
+ 'margin': '0px'
+ }
+ }
+ );
+
+ statusContainer.addItem(this._migrationStatusCardsContainer, {
+ CSSStyles: {
+ 'margin-top': '30px'
+ }
+ });
+
+ return statusContainer;
+ }
+
+ private createVideoLinks(view: azdata.ModelView): azdata.Component {
+ const linksContainer = view.modelBuilder.flexContainer().withLayout({
+ flexFlow: 'column',
+ width: '400px',
+ height: '280px',
+ justifyContent: 'flex-start',
+ }).withProps({
+ CSSStyles: {
+ 'border': '1px solid rgba(0, 0, 0, 0.1)',
+ 'padding': '15px'
+ }
+ }).component();
+ const titleComponent = view.modelBuilder.text().withProps({
+ value: loc.HELP_TITLE,
+ CSSStyles: {
+ 'font-size': '18px',
+ 'font-weight': 'bold',
+ 'margin': '0px'
+ }
+ }).component();
+
+ linksContainer.addItems([titleComponent], {
+ CSSStyles: {
+ 'padding': '0px',
+ 'padding-right': '5px',
+ 'padding-top': '10px',
+ 'height': '10px',
+ 'margin': '0px'
+ }
+ });
+
+ const links = [{
+ title: loc.HELP_LINK1_TITLE,
+ description: loc.HELP_LINK1_DESCRIPTION,
+ link: 'www.microsoft.com' //TODO: add proper link over here.
+ }];
+
+ const styles = {
+ 'margin-top': '10px',
+ 'padding': '10px 10px 10px 0'
+ };
+ linksContainer.addItems(links.map(l => this.createLink(view, l)), {
+ CSSStyles: styles
+ });
+
+ const videosContainer = this.createVideoLinkContainers(view, [
+ {
+ iconPath: IconPathHelper.sqlMiImportHelpThumbnail,
+ description: loc.HELP_VIDEO1_TITLE,
+ link: 'https://www.youtube.com/watch?v=sE99cSoFOHs' //TODO: Fix Video link
+ },
+ {
+ iconPath: IconPathHelper.sqlVmImportHelpThumbnail,
+ description: loc.HELP_VIDEO2_TITLE,
+ link: 'https://www.youtube.com/watch?v=R4GCBoxADyQ' //TODO: Fix video link
+ }
+ ]);
+ const viewPanelStyle = {
+ 'padding': '10px 5px 10px 10px',
+ 'margin-top': '-15px'
+ };
+ linksContainer.addItem(videosContainer, {
+ CSSStyles: viewPanelStyle
+ });
+
+ return linksContainer;
+ }
+
+ private createLink(view: azdata.ModelView, linkMetaData: IActionMetadata): azdata.Component {
+ const maxWidth = 400;
+ const labelsContainer = view.modelBuilder.flexContainer().withLayout({
+ flexFlow: 'column',
+ width: maxWidth,
+ justifyContent: 'flex-start'
+ }).component();
+ const descriptionComponent = view.modelBuilder.text().withProps({
+ value: linkMetaData.description,
+ width: maxWidth,
+ CSSStyles: {
+ 'font-size': '12px',
+ 'line-height': '16px',
+ 'margin': '0px'
+ }
+ }).component();
+ const linkContainer = view.modelBuilder.flexContainer().withLayout({
+ flexFlow: 'row',
+ width: maxWidth + 10,
+ justifyContent: 'flex-start'
+ }).component();
+ const linkComponent = view.modelBuilder.hyperlink().withProps({
+ label: linkMetaData.title!,
+ url: linkMetaData.link!,
+ showLinkIcon: true,
+ CSSStyles: {
+ 'font-size': '14px',
+ 'margin': '0px'
+ }
+ }).component();
+ linkContainer.addItem(linkComponent, {
+ CSSStyles: {
+ 'font-size': '14px',
+ 'line-height': '18px',
+ 'padding': '0 5px 0 0',
+ }
+ });
+ labelsContainer.addItems([linkContainer, descriptionComponent], {
+ CSSStyles: {
+ 'padding': '5px 0 0 0',
+ }
+ });
+
+ return labelsContainer;
+ }
+
+ private createVideoLinkContainers(view: azdata.ModelView, links: IActionMetadata[]): azdata.Component {
+ const maxWidth = 400;
+ const videosContainer = view.modelBuilder.flexContainer().withLayout({
+ flexFlow: 'row',
+ width: maxWidth,
+ }).component();
+
+ links.forEach(link => {
+ const videoContainer = this.createVideoLink(view, link);
+
+ videosContainer.addItem(videoContainer);
+ });
+
+ return videosContainer;
+ }
+
+ private createVideoLink(view: azdata.ModelView, linkMetaData: IActionMetadata): azdata.Component {
+ const maxWidth = 150;
+ const videosContainer = view.modelBuilder.flexContainer().withLayout({
+ flexFlow: 'column',
+ width: maxWidth,
+ justifyContent: 'flex-start'
+ }).component();
+ const video1Container = view.modelBuilder.divContainer().withProps({
+ clickable: true,
+ width: maxWidth,
+ height: '100px'
+ }).component();
+ const descriptionComponent = view.modelBuilder.text().withProps({
+ value: linkMetaData.description,
+ width: maxWidth,
+ height: '50px',
+ CSSStyles: {
+ 'font-size': '13px',
+ 'margin': '0px'
+ }
+ }).component();
+ video1Container.onDidClick(async () => {
+ if (linkMetaData.link) {
+ await vscode.env.openExternal(vscode.Uri.parse(linkMetaData.link));
+ }
+ });
+ videosContainer.addItem(video1Container, {
+ CSSStyles: {
+ 'background-image': `url(${vscode.Uri.file(linkMetaData.iconPath?.light)})`,
+ 'background-repeat': 'no-repeat',
+ 'background-position': 'top',
+ 'width': `${maxWidth}px`,
+ 'height': '104px',
+ 'background-size': `${maxWidth}px 120px`
+ }
+ });
+ videosContainer.addItem(descriptionComponent);
+ return videosContainer;
+ }
+}
diff --git a/extensions/sql-migration/src/wizard/createMigrationControllerDialog.ts b/extensions/sql-migration/src/dialog/createMigrationDialog/createMigrationControllerDialog.ts
similarity index 97%
rename from extensions/sql-migration/src/wizard/createMigrationControllerDialog.ts
rename to extensions/sql-migration/src/dialog/createMigrationDialog/createMigrationControllerDialog.ts
index 07012cd9f7..708bc37b10 100644
--- a/extensions/sql-migration/src/wizard/createMigrationControllerDialog.ts
+++ b/extensions/sql-migration/src/dialog/createMigrationDialog/createMigrationControllerDialog.ts
@@ -5,13 +5,13 @@
import * as azdata from 'azdata';
import * as vscode from 'vscode';
-import { createMigrationController, getMigrationControllerRegions, getMigrationController, getResourceGroups, getMigrationControllerAuthKeys, getMigrationControllerMonitoringData } from '../api/azure';
-import { MigrationStateModel } from '../models/stateMachine';
-import * as constants from '../models/strings';
+import { createMigrationController, getMigrationControllerRegions, getMigrationController, getResourceGroups, getMigrationControllerAuthKeys, getMigrationControllerMonitoringData } from '../../api/azure';
+import { MigrationStateModel } from '../../models/stateMachine';
+import * as constants from '../../models/strings';
import * as os from 'os';
import { azureResource } from 'azureResource';
-import { IntergrationRuntimePage } from './integrationRuntimePage';
-import { IconPathHelper } from '../constants/iconPathHelper';
+import { IntergrationRuntimePage } from '../../wizard/integrationRuntimePage';
+import { IconPathHelper } from '../../constants/iconPathHelper';
export class CreateMigrationControllerDialog {
@@ -464,7 +464,7 @@ export class CreateMigrationControllerDialog {
value: constants.CONTROLLER_KEY1_LABEL
},
{
- value: keys.keyName1
+ value: keys.authKey1
},
{
value: this._copyKey1Button
@@ -478,7 +478,7 @@ export class CreateMigrationControllerDialog {
value: constants.CONTROLLER_KEY2_LABEL
},
{
- value: keys.keyName2
+ value: keys.authKey2
},
{
value: this._copyKey2Button
diff --git a/extensions/sql-migration/src/main.ts b/extensions/sql-migration/src/main.ts
index 9a34d42995..be6a5e4a9e 100644
--- a/extensions/sql-migration/src/main.ts
+++ b/extensions/sql-migration/src/main.ts
@@ -11,12 +11,15 @@ import { promises as fs } from 'fs';
import * as loc from './models/strings';
import { MigrationNotebookInfo, NotebookPathHelper } from './constants/notebookPathHelper';
import { IconPathHelper } from './constants/iconPathHelper';
+import { DashboardWidget } from './dashboard/sqlServerDashboard';
+import { MigrationLocalStorage } from './models/migrationLocalStorage';
class SQLMigration {
constructor(private readonly context: vscode.ExtensionContext) {
NotebookPathHelper.setExtensionContext(context);
IconPathHelper.setExtensionContext(context);
+ MigrationLocalStorage.setExtensionContext(context);
}
async start(): Promise {
@@ -83,6 +86,8 @@ let sqlMigration: SQLMigration;
export async function activate(context: vscode.ExtensionContext) {
sqlMigration = new SQLMigration(context);
await sqlMigration.registerCommands();
+ let widget = new DashboardWidget();
+ widget.register();
}
export function deactivate(): void {
diff --git a/extensions/sql-migration/src/models/migrationLocalStorage.ts b/extensions/sql-migration/src/models/migrationLocalStorage.ts
index fd04fd6773..19db37a846 100644
--- a/extensions/sql-migration/src/models/migrationLocalStorage.ts
+++ b/extensions/sql-migration/src/models/migrationLocalStorage.ts
@@ -23,7 +23,7 @@ export class MigrationLocalStorage {
const migrationMementos: MigrationContext[] = this.context.globalState.get(this.mementoToken) || [];
dataBaseMigrations = migrationMementos.filter((memento) => {
- return memento.connection.serverName === connectionProfile.serverName;
+ return memento.sourceConnectionProfile.serverName === connectionProfile.serverName;
}).map((memento) => {
return memento;
});
@@ -35,13 +35,13 @@ export class MigrationLocalStorage {
return dataBaseMigrations;
}
- public static saveMigration(connection: azdata.connection.ConnectionProfile, migration: DatabaseMigration, targetMI: SqlManagedInstance, azureAccount: azdata.Account, subscription: azureResource.AzureResourceSubscription): void {
+ public static saveMigration(connectionProfile: azdata.connection.ConnectionProfile, migrationContext: DatabaseMigration, targetMI: SqlManagedInstance, azureAccount: azdata.Account, subscription: azureResource.AzureResourceSubscription): void {
try {
const migrationMementos: MigrationContext[] = this.context.globalState.get(this.mementoToken) || [];
migrationMementos.push({
- connection: connection,
- migration: migration,
- targetMI: targetMI,
+ sourceConnectionProfile: connectionProfile,
+ migrationContext: migrationContext,
+ targetManagedInstance: targetMI,
subscription: subscription,
azureAccount: azureAccount
});
@@ -57,9 +57,9 @@ export class MigrationLocalStorage {
}
export interface MigrationContext {
- connection: azdata.connection.ConnectionProfile,
- migration: DatabaseMigration,
- targetMI: SqlManagedInstance,
+ sourceConnectionProfile: azdata.connection.ConnectionProfile,
+ migrationContext: DatabaseMigration,
+ targetManagedInstance: SqlManagedInstance,
azureAccount: azdata.Account,
subscription: azureResource.AzureResourceSubscription
}
diff --git a/extensions/sql-migration/src/models/stateMachine.ts b/extensions/sql-migration/src/models/stateMachine.ts
index e8d03a4c3b..353d000b10 100644
--- a/extensions/sql-migration/src/models/stateMachine.ts
+++ b/extensions/sql-migration/src/models/stateMachine.ts
@@ -453,7 +453,7 @@ export class MigrationStateModel implements Model, vscode.Disposable {
Scope: this._targetManagedInstance.id
}
};
- console.log(requestBody);
+
const response = await startDatabaseMigration(
this.azureAccount,
this._targetSubscription,
@@ -464,9 +464,8 @@ export class MigrationStateModel implements Model, vscode.Disposable {
requestBody
);
- console.log(response);
- if (!response.error) {
- MigrationLocalStorage.saveMigration(currentConnection!, response, this._targetManagedInstance, this.azureAccount, this._targetSubscription);
+ if (response.status === 201) {
+ MigrationLocalStorage.saveMigration(currentConnection!, response.databaseMigration, this._targetManagedInstance, this.azureAccount, this._targetSubscription);
}
vscode.window.showInformationMessage(constants.MIGRATION_STARTED);
diff --git a/extensions/sql-migration/src/models/strings.ts b/extensions/sql-migration/src/models/strings.ts
index 5b4728115f..cfc007976a 100644
--- a/extensions/sql-migration/src/models/strings.ts
+++ b/extensions/sql-migration/src/models/strings.ts
@@ -149,7 +149,7 @@ export const CANCEL = localize('sql.migration.cancel', "Cancel");
export const TYPE = localize('sql.migration.type', "Type");
export const PATH = localize('sql.migration.path', "Path");
export const USER_ACCOUNT = localize('sql.migration.path.user.account', "User Account");
-
+export const VIEW_ALL = localize('sql.migration.view.all', "View All");
//Summary Page
export const SUMMARY_PAGE_TITLE = localize('sql.migration.summary.page.title', "Summary");
@@ -171,3 +171,20 @@ export const NOTEBOOK_QUICK_PICK_PLACEHOLDER = localize('sql.migration.quick.pic
export const NOTEBOOK_INLINE_MIGRATION_TITLE = localize('sql.migration.inline.migration.notebook.title', "Inline migration");
export const NOTEBOOK_SQL_MIGRATION_ASSESSMENT_TITLE = localize('sql.migration.sql.assessment.notebook.title', "SQL migration assessment");
export const NOTEBOOK_OPEN_ERROR = localize('sql.migration.notebook.open.error', "Error opening migration notebook");
+
+// Dashboard
+export const DASHBOARD_TITLE = localize('sql.migration.dashboard.title', "Azure SQL Migration");
+export const DASHBOARD_DESCRIPTION = localize('sql.migration.dashboard.description', "Determine the migration readiness of your SQL Server instances, identify a recommended Azure SQL target, and complete the migration of your SQL Server instance to Azure SQL Managed Instance or SQL Server on Azure Virtual Machines.");
+export const DASHBOARD_MIGRATE_TASK_BUTTON_TITLE = localize('sql.migration.dashboard.migrate.task.button', "Migrate to Azure SQL");
+export const DASHBOARD_MIGRATE_TASK_BUTTON_DESCRIPTION = localize('sql.migration.dashboard.migrate.task.button.description', "Migrate SQL Server instance to Azure SQL.");
+export const DATABASE_MIGRATION_STATUS = localize('sql.migration.database.migration.status', "Database Migration Status");
+export const HELP_VIDEO1_TITLE = localize('sql.migration.dashboard.video1.title', "Migrate to SQL Server to SQL Managed Instance");
+export const HELP_VIDEO2_TITLE = localize('sql.migration.dashboard.video2.title', "Migrate to SQL Server to SQL Virtual Machine");
+export const HELP_LINK1_TITLE = localize('sql.migration.dashboard.link1.title', "Migrating your SQL Server to cloud");
+export const HELP_LINK1_DESCRIPTION = localize('sql.migration.dashboard.link1.description', "Lorem ipsum dolor sit amet, consectetur adipi. Lorem ipsum dolor sit amet, consectetur adipi. Lorem ipsum.");
+export const HELP_TITLE = localize('sql.migration.dashboard.help.title', "Help Articles and Video Links");
+export const PRE_REQ_TITLE = localize('sql.migration.pre.req.title', "Things you need before starting migration:");
+export const PRE_REQ_1 = localize('sql.migration.pre.req.1', "Azure account details");
+export const PRE_REQ_2 = localize('sql.migration.pre.req.2', "Azure SQL Managed Instance or SQL Server on Azure Virtual Machine");
+export const PRE_REQ_3 = localize('sql.migration.pre.req.3', "Backup location details");
+
diff --git a/extensions/sql-migration/src/wizard/integrationRuntimePage.ts b/extensions/sql-migration/src/wizard/integrationRuntimePage.ts
index 00d72d1620..ee5e718730 100644
--- a/extensions/sql-migration/src/wizard/integrationRuntimePage.ts
+++ b/extensions/sql-migration/src/wizard/integrationRuntimePage.ts
@@ -6,7 +6,7 @@
import * as azdata from 'azdata';
import { MigrationWizardPage } from '../models/migrationWizardPage';
import { MigrationStateModel, StateChangeEvent } from '../models/stateMachine';
-import { CreateMigrationControllerDialog } from './createMigrationControllerDialog';
+import { CreateMigrationControllerDialog } from '../dialog/createMigrationDialog/createMigrationControllerDialog';
import * as constants from '../models/strings';
import * as os from 'os';
import { WIZARD_INPUT_COMPONENT_WIDTH } from './wizardController';