Compare commits

..

21 Commits

Author SHA1 Message Date
Karl Burtram
4d4917d328 Bump version for 1.25.2 release (#14007) 2021-01-20 14:05:08 -08:00
Hale Rankin
edea311757 Revised section scrolling logic to fix broken user experience. (#13926) (#14005) 2021-01-20 13:55:16 -08:00
Karl Burtram
e7eacc32c0 Bump ADS version for Hotfix (#13761) 2020-12-10 13:19:22 -08:00
Charles Gagnon
12f50cca8d Update STS to revert SqlClient update (#13758) (#13760)
(cherry picked from commit 94feb1a80d)
2020-12-10 13:08:03 -08:00
Charles Gagnon
88a4dba695 [Port] Fix env var names for Arc deployment (#13735)
* Fix environment variables for controller create (#13732)


(cherry picked from commit aee8bc2759)

* vBump and update engine version
2020-12-09 10:50:16 -08:00
Charles Gagnon
634ea0ab6a Use console.log for retry logging (#13722) (#13723)
(cherry picked from commit a74119038f)
2020-12-08 10:28:02 -08:00
Charles Gagnon
cd0b5cbc7a Retry getConfig (#13712) (#13713)
* Retry getConfig

* Add logging

(cherry picked from commit d6e1e8eb52)
2020-12-07 15:14:38 -08:00
Charles Gagnon
0b7de6608a Retry publish and always try adding asset (#13700) (#13704)
* Retry publish and always try adding asset

* Undo asset upload change

* Add logging

(cherry picked from commit 6c89c61b0d)
2020-12-07 12:36:03 -08:00
Charles Gagnon
8c6bd8c857 [Port] Add descriptions/validations for Arc connected mode deployment (#13689)
* Add descriptions and validation to connected mode (#13676)


(cherry picked from commit 757ac1d4aa)

* bump version
2020-12-04 16:52:39 -08:00
Monica Gupta
def5775e00 Fix issue with pasting results in Teams (#13673) (#13687)
* Fix issue with pasting results in Teams

* Addressed comment to change header tag to th

Co-authored-by: Monica Gupta <mogupt@microsoft.com>

Co-authored-by: Monica Gupta <mogupt@microsoft.com>
2020-12-04 16:31:08 -08:00
Chris LaFreniere
91da9aea98 Prevent Table from Disappearing due to exception when looking for tHead (#13680) (#13685)
* Prevent exception when tHead doesn't exist at node

* Add test for no thead
2020-12-04 15:14:32 -08:00
Vasu Bhog
1c898402f8 Fix notebook unordered grid values after papermill execution (#13614) (#13665)
* Fix unordered table

* check entire first row schema:

* SQL Notebooks should not get affected

* delete unused variable and edit comments

* refactor for efficient table ordering

* nit naming
2020-12-03 18:10:24 -08:00
Monica Gupta
54210cf479 Fix empty column issue (#13641) (#13653)
Co-authored-by: Monica Gupta <mogupt@microsoft.com>

Co-authored-by: Monica Gupta <mogupt@microsoft.com>
2020-12-03 14:57:16 -08:00
Barbara Valdez
cbcea87a82 add right padding to notebook toolbar action item (#13640) (#13650)
* add right padding to action item

* remove extra line and add space
2020-12-03 11:30:10 -08:00
Barbara Valdez
2d50d2c5d1 add await to thenable method (#13635) (#13638) 2020-12-02 18:12:48 -08:00
Chris LaFreniere
7448c6c32c WYSIWYG Improvements to highlight (#13032) (#13636)
* Improvements to highlight

* wip

* Tests pass

* Leverage escaping mechanism

* Tweak highlight logic

* PR comments
2020-12-02 16:26:18 -08:00
Karl Burtram
3196cf5be0 Bump distro to pickup new icons (#13598) (#13625) 2020-12-02 11:24:23 -08:00
Kim Santiago
666726a5fa Update open existing dialog icons (#13571) (#13593)
* update open existing dialog icons

* undo removing folder.svg

* remove max width and max height
2020-12-01 14:38:06 -08:00
Benjin Dubishar
818a3d204e Update tools service to .61 (#13591) (#13595) 2020-12-01 14:36:01 -08:00
Lucy Zhang
d45758b4f4 dont add column header in continue request (#13568) (#13577) 2020-11-30 15:12:06 -08:00
Benjin Dubishar
1eda5eb33a Adding additional parameter to data workspace provider API (#13570) (#13574) 2020-11-30 13:50:31 -08:00
139 changed files with 974 additions and 2797 deletions

View File

@@ -12,10 +12,6 @@
{
"file": "build\\actions\\AutoMerge\\dist\\index.js",
"_justification": "False positive from webpacked code"
},
{
"file": ".devcontainer\\devcontainer.json",
"_justification": "Local development environment - not used in production"
}
]
}

View File

@@ -1,2 +0,0 @@
Needs Logs:
comment: "We need more info to debug your particular issue. If you could attach your logs to the issue (ensure no private data is in them), it would help us fix the issue much faster.\n\nTo find your logs:\n\n- Open command palette (Click **View** -> **Command Palette**)\n- Run the command: **`Developer: Open Logs Folder`**\n\nThis will open the log file locally. Please zip up this folder and attach it to the issue."

View File

@@ -1,15 +0,0 @@
name: On Label
on:
issues:
types: [labeled]
jobs:
processLabelAction:
name: Process Label Action
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Process Label Action
uses: hramos/label-actions@v1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -1,19 +1,5 @@
# Change Log
## Version 1.25.1
* Release date: December 10, 2020
* Release status: General Availability
* Fixes https://github.com/microsoft/azuredatastudio/issues/13751
## Version 1.25.0
* Release date: December 8, 2020
* Release status: General Availability
* Kusto extension improvements
* SQL Project extension improvements
* Notebook improvements
* Azure Browse Connections Preview performance improvements
* Bug Fixes
## Version 1.24.0
* Release date: November 12, 2020
* Release status: General Availability

View File

@@ -131,10 +131,10 @@ Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the [Source EULA](LICENSE.txt).
[win-user]: https://go.microsoft.com/fwlink/?linkid=2150927
[win-system]: https://go.microsoft.com/fwlink/?linkid=2150928
[win-zip]: https://go.microsoft.com/fwlink/?linkid=2151312
[osx-zip]: https://go.microsoft.com/fwlink/?linkid=2151311
[linux-zip]: https://go.microsoft.com/fwlink/?linkid=2151508
[linux-rpm]: https://go.microsoft.com/fwlink/?linkid=2151407
[linux-deb]: https://go.microsoft.com/fwlink/?linkid=2151506
[win-user]: https://go.microsoft.com/fwlink/?linkid=2148607
[win-system]: https://go.microsoft.com/fwlink/?linkid=2148907
[win-zip]: https://go.microsoft.com/fwlink/?linkid=2148908
[osx-zip]: https://go.microsoft.com/fwlink/?linkid=2148710
[linux-zip]: https://go.microsoft.com/fwlink/?linkid=2148708
[linux-rpm]: https://go.microsoft.com/fwlink/?linkid=2148709
[linux-deb]: https://go.microsoft.com/fwlink/?linkid=2148806

View File

@@ -114,8 +114,6 @@
"# Login to the data controller.\n",
"#\n",
"os.environ[\"AZDATA_PASSWORD\"] = os.environ[\"AZDATA_NB_VAR_CONTROLLER_PASSWORD\"]\n",
"os.environ[\"KUBECONFIG\"] = controller_kubeconfig\n",
"os.environ[\"KUBECTL_CONTEXT\"] = controller_kubectl_context\n",
"cmd = f'azdata login -e {controller_endpoint} -u {controller_username}'\n",
"out=run_command()"
],

View File

@@ -114,8 +114,6 @@
"# Login to the data controller.\n",
"#\n",
"os.environ[\"AZDATA_PASSWORD\"] = os.environ[\"AZDATA_NB_VAR_CONTROLLER_PASSWORD\"]\n",
"os.environ[\"KUBECONFIG\"] = controller_kubeconfig\n",
"os.environ[\"KUBECTL_CONTEXT\"] = controller_kubectl_context\n",
"cmd = f'azdata login -e {controller_endpoint} -u {controller_username}'\n",
"out=run_command()"
],

View File

@@ -2,7 +2,7 @@
"name": "arc",
"displayName": "%arc.displayName%",
"description": "%arc.description%",
"version": "0.7.1",
"version": "0.6.5",
"publisher": "Microsoft",
"preview": true,
"license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/main/LICENSE.txt",
@@ -189,9 +189,7 @@
"editable": false,
"options": {
"source": {
"providerId": "arc.controller.config.profiles",
"loadingText": "%arc.data.controller.cluster.config.profile.loading%",
"loadingCompletedText": "%arc.data.controller.cluster.config.profile.loadingcompleted%"
"providerId": "arc.controller.config.profiles"
},
"defaultValue": "azure-arc-aks-default-storage",
"optionsType": "radio"
@@ -317,10 +315,10 @@
"type": "text",
"required": true,
"defaultValue": "",
"enabled": false,
"valueProvider": {
"providerId": "subscription-id-to-tenant-id",
"triggerField": "AZDATA_NB_VAR_ARC_SUBSCRIPTION"
"placeHolder": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"enabled": {
"target": "AZDATA_NB_VAR_ARC_DATA_CONTROLLER_CONNECTIVITY_MODE",
"value": "direct"
},
"validations" : [{
"type": "regex_match",
@@ -556,6 +554,18 @@
{
"title": "%arc.data.controller.summary.azure%",
"fields": [
{
"label": "%arc.data.controller.summary.data.controller.namespace%",
"type": "readonly_text",
"isEvaluated": true,
"defaultValue": "$(AZDATA_NB_VAR_ARC_DATA_CONTROLLER_NAMESPACE)"
},
{
"label": "%arc.data.controller.summary.data.controller.name%",
"type": "readonly_text",
"isEvaluated": true,
"defaultValue": "$(AZDATA_NB_VAR_ARC_DATA_CONTROLLER_NAME)"
},
{
"label": "%arc.data.controller.summary.subscription%",
"type": "readonly_text",
@@ -580,18 +590,6 @@
{
"title": "%arc.data.controller.summary.controller%",
"fields": [
{
"label": "%arc.data.controller.summary.data.controller.namespace%",
"type": "readonly_text",
"isEvaluated": true,
"defaultValue": "$(AZDATA_NB_VAR_ARC_DATA_CONTROLLER_NAMESPACE)"
},
{
"label": "%arc.data.controller.summary.data.controller.name%",
"type": "readonly_text",
"isEvaluated": true,
"defaultValue": "$(AZDATA_NB_VAR_ARC_DATA_CONTROLLER_NAME)"
},
{
"label": "%arc.data.controller.connectivitymode%",
"type": "readonly_text",
@@ -610,7 +608,7 @@
},
{
"name": "azdata",
"version": "20.2.5"
"version": "20.2.0"
}
],
"when": true
@@ -789,7 +787,7 @@
},
{
"name": "azdata",
"version": "20.2.5"
"version": "20.2.0"
}
],
"when": "true"
@@ -1037,7 +1035,7 @@
},
{
"name": "azdata",
"version": "20.2.5"
"version": "20.2.0"
}
],
"when": "true"

View File

@@ -20,8 +20,6 @@
"arc.data.controller.kube.cluster.context": "Cluster context",
"arc.data.controller.cluster.config.profile.title": "Choose the config profile",
"arc.data.controller.cluster.config.profile": "Config profile",
"arc.data.controller.cluster.config.profile.loading": "Loading config profiles",
"arc.data.controller.cluster.config.profile.loadingcompleted": "Loading config profiles complete",
"arc.data.controller.create.azureconfig.title": "Azure and Connectivity Configuration",
"arc.data.controller.connectivitymode.description": "Select the connectivity mode for the controller.",
"arc.data.controller.create.controllerconfig.title": "Controller Configuration",

View File

@@ -4,17 +4,11 @@
*--------------------------------------------------------------------------------------------*/
import * as arc from 'arc';
import * as rd from 'resource-deployment';
import * as loc from '../localizedConstants';
import { PasswordToControllerDialog } from '../ui/dialogs/connectControllerDialog';
import { AzureArcTreeDataProvider } from '../ui/tree/azureArcTreeDataProvider';
import { ControllerTreeNode } from '../ui/tree/controllerTreeNode';
import { UserCancelledError } from './utils';
export class UserCancelledError extends Error implements rd.ErrorWithType {
public get type(): rd.ErrorType {
return rd.ErrorType.userCancelled;
}
}
export function arcApi(treeDataProvider: AzureArcTreeDataProvider): arc.IExtension {
return {
getRegisteredDataControllers: () => getRegisteredDataControllers(treeDataProvider),
@@ -22,13 +16,12 @@ export function arcApi(treeDataProvider: AzureArcTreeDataProvider): arc.IExtensi
reacquireControllerPassword: (controllerInfo: arc.ControllerInfo) => reacquireControllerPassword(treeDataProvider, controllerInfo)
};
}
export async function reacquireControllerPassword(treeDataProvider: AzureArcTreeDataProvider, controllerInfo: arc.ControllerInfo): Promise<string> {
const dialog = new PasswordToControllerDialog(treeDataProvider);
dialog.showDialog(controllerInfo);
const model = await dialog.waitForClose();
if (!model) {
throw new UserCancelledError(loc.userCancelledError);
throw new UserCancelledError();
}
return model.password;
}

View File

@@ -13,11 +13,6 @@ export interface KubeClusterContext {
isCurrentContext: boolean;
}
/**
* returns the cluster context defined in the {@see configFile}
*
* @param configFile
*/
export function getKubeConfigClusterContexts(configFile: string): Promise<KubeClusterContext[]> {
const config: any = yamljs.load(configFile);
const rawContexts = <any[]>config['contexts'];
@@ -38,38 +33,6 @@ export function getKubeConfigClusterContexts(configFile: string): Promise<KubeCl
return Promise.resolve(contexts);
}
/**
* searches for {@see previousClusterContext} in the array of {@see clusterContexts}.
* if {@see previousClusterContext} was truthy and it was found in {@see clusterContexts}
* then it returns {@see previousClusterContext}
* else it returns the current cluster context from {@see clusterContexts} unless throwIfNotFound was set on input in which case an error is thrown instead.
* else it returns the current cluster context from {@see clusterContexts}
*
*
* @param clusterContexts
* @param previousClusterContext
* @param throwIfNotFound
*/
export function getCurrentClusterContext(clusterContexts: KubeClusterContext[], previousClusterContext?: string, throwIfNotFound: boolean = false): string {
if (previousClusterContext) {
if (clusterContexts.find(c => c.name === previousClusterContext)) { // if previous cluster context value is found in clusters then return that value
return previousClusterContext;
} else {
if (throwIfNotFound) {
throw new Error(loc.clusterContextNotFound(previousClusterContext));
}
}
}
// if not previousClusterContext or throwIfNotFound was false when previousCLusterContext was not found in the clusterContexts
const currentClusterContext = clusterContexts.find(c => c.isCurrentContext)?.name;
throwUnless(currentClusterContext !== undefined, loc.noCurrentClusterContext);
return currentClusterContext;
}
/**
* returns the default kube config file path
*/
export function getDefaultKubeConfigPath(): string {
return path.join(os.homedir(), '.kube', 'config');
}

View File

@@ -9,6 +9,8 @@ import * as vscode from 'vscode';
import { ConnectionMode, IconPath, IconPathHelper } from '../constants';
import * as loc from '../localizedConstants';
export class UserCancelledError extends Error { }
/**
* Converts the resource type name into the localized Display Name for that type.
* @param resourceType The resource type name to convert

View File

@@ -67,7 +67,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<arc.IE
// register option sources
const rdApi = <rd.IExtension>vscode.extensions.getExtension(rd.extension.name)?.exports;
context.subscriptions.push(rdApi.registerOptionsSourceProvider(new ArcControllersOptionsSourceProvider(treeDataProvider)));
rdApi.registerOptionsSourceProvider(new ArcControllersOptionsSourceProvider(treeDataProvider));
return arcApi(treeDataProvider);
}

View File

@@ -61,7 +61,7 @@ export const yes = localize('arc.yes', "Yes");
export const no = localize('arc.no', "No");
export const feedback = localize('arc.feedback', "Feedback");
export const selectConnectionString = localize('arc.selectConnectionString', "Select from available client connection strings below.");
export const addingWorkerNodes = localize('arc.addingWorkerNodes', "adding worker nodes");
export const addingWokerNodes = localize('arc.addingWokerNodes', "adding worker nodes");
export const workerNodesDescription = localize('arc.workerNodesDescription', "Expand your server group and scale your database by adding worker nodes.");
export const postgresConfigurationInformation = localize('arc.postgres.configurationInformation', "You can configure the number of CPU cores and storage size that will apply to both worker nodes and coordinator node. Each worker node will have the same configuration. Adjust the number of CPU cores and memory settings for your server group.");
export const workerNodesInformation = localize('arc.workerNodeInformation', "In preview it is not possible to reduce the number of worker nodes. Please refer to documentation linked above for more information.");
@@ -85,8 +85,6 @@ export const passwordToController = localize('arc.passwordToController', "Provid
export const controllerUrl = localize('arc.controllerUrl', "Controller URL");
export const serverEndpoint = localize('arc.serverEndpoint', "Server Endpoint");
export const controllerName = localize('arc.controllerName', "Name");
export const controllerKubeConfig = localize('arc.controllerKubeConfig', "Kube Config File Path");
export const controllerClusterContext = localize('arc.controllerClusterContext', "Cluster Context");
export const defaultControllerName = localize('arc.defaultControllerName', "arc-dc");
export const username = localize('arc.username', "Username");
export const password = localize('arc.password', "Password");
@@ -141,6 +139,7 @@ export const coresRequest = localize('arc.coresRequest', "CPU request:");
export const memoryLimit = localize('arc.memoryLimit', "Memory limit (in GB):");
export const memoryRequest = localize('arc.memoryRequest', "Memory request (in GB):");
export const workerValidationErrorMessage = localize('arc.workerValidationErrorMessage', "The number of workers cannot be decreased.");
export const coresValidationErrorMessage = localize('arc.coresValidationErrorMessage', "Valid CPU resource quantities are strictly positive.");
export const memoryRequestValidationErrorMessage = localize('arc.memoryRequestValidationErrorMessage', "Memory request must be at least 0.25Gib");
export const memoryLimitValidationErrorMessage = localize('arc.memoryLimitValidationErrorMessage', "Memory limit must be at least 0.25Gib");
export const arcResources = localize('arc.arcResources', "Azure Arc Resources");
@@ -173,7 +172,6 @@ export function numVCores(vCores: string | undefined): string {
}
}
export function updated(when: string): string { return localize('arc.updated', "Updated {0}", when); }
export function validationMin(min: number): string { return localize('arc.validationMin', "Value must be greater than or equal to {0}.", min); }
// Errors
export const connectionRequired = localize('arc.connectionRequired', "A connection is required to show all properties. Click refresh to re-enter connection information");
@@ -204,11 +202,6 @@ export const variableValueFetchForUnsupportedVariable = (variableName: string) =
export const isPasswordFetchForUnsupportedVariable = (variableName: string) => localize('getIsPassword.unknownVariableName', "Attempt to get isPassword for unknown variable:{0}", variableName);
export const noControllerInfoFound = (name: string) => localize('noControllerInfoFound', "Controller Info could not be found with name: {0}", name);
export const noPasswordFound = (controllerName: string) => localize('noPasswordFound', "Password could not be retrieved for controller: {0} and user did not provide a password. Please retry later.", controllerName);
export const clusterContextNotFound = (clusterContext: string) => localize('clusterContextNotFound', "Cluster Context with name: {0} not found in the Kube config file", clusterContext);
export const noCurrentClusterContext = localize('noCurrentClusterContext', "No current cluster context was found in the kube config file");
export const browse = localize('filePicker.browse', "Browse");
export const select = localize('button.label', "Select");
export const noContextFound = (configFile: string) => localize('noContextFound', "No 'contexts' found in the config file: {0}", configFile);
export const noCurrentContextFound = (configFile: string) => localize('noCurrentContextFound', "No context is marked as 'current-context' in the config file: {0}", configFile);
export const noNameInContext = (configFile: string) => localize('noNameInContext', "No name field was found in a cluster context in the config file: {0}", configFile);
export const userCancelledError = localize('userCancelledError', "User cancelled the dialog");

View File

@@ -6,7 +6,7 @@
import { ControllerInfo, ResourceType } from 'arc';
import * as azdataExt from 'azdata-ext';
import * as vscode from 'vscode';
import { UserCancelledError } from '../common/api';
import { UserCancelledError } from '../common/utils';
import * as loc from '../localizedConstants';
import { ConnectToControllerDialog } from '../ui/dialogs/connectControllerDialog';
import { AzureArcTreeDataProvider } from '../ui/tree/azureArcTreeDataProvider';
@@ -71,7 +71,7 @@ export class ControllerModel {
await this.treeDataProvider.addOrUpdateController(model.controllerModel, model.password, false);
this._password = model.password;
} else {
throw new UserCancelledError(loc.userCancelledError);
throw new UserCancelledError();
}
}
}

View File

@@ -7,9 +7,8 @@ import { MiaaResourceInfo } from 'arc';
import * as azdata from 'azdata';
import * as azdataExt from 'azdata-ext';
import * as vscode from 'vscode';
import { UserCancelledError } from '../common/api';
import { Deferred } from '../common/promise';
import { createCredentialId, parseIpAndPort } from '../common/utils';
import { createCredentialId, parseIpAndPort, UserCancelledError } from '../common/utils';
import { credentialNamespace } from '../constants';
import * as loc from '../localizedConstants';
import { ConnectToSqlDialog } from '../ui/dialogs/connectSqlDialog';

View File

@@ -17,7 +17,7 @@ import { AzureArcTreeDataProvider } from '../ui/tree/azureArcTreeDataProvider';
*/
export class ArcControllersOptionsSourceProvider implements rd.IOptionsSourceProvider {
private _cacheManager = new CacheManager<string, string>();
readonly id = 'arc.controllers';
readonly optionsSourceId = 'arc.controllers';
constructor(private _treeProvider: AzureArcTreeDataProvider) { }
async getOptions(): Promise<string[] | azdata.CategoryValue[]> {

View File

@@ -55,7 +55,7 @@ describe('KubeUtils', function (): void {
});
it('throws error when unable to load config file', async () => {
const error = new Error('unknown error accessing file');
sinon.stub(yamljs, 'load').throws(error); // simulate an error thrown from config file load
sinon.stub(yamljs, 'load').throws(error); //erroring config file load
((await tryExecuteAction(() => getKubeConfigClusterContexts(configFile))).error).should.equal(error, `test: getKubeConfigClusterContexts failed`);
});
});

View File

@@ -49,7 +49,6 @@ export class FakeAzdataApi implements azdataExt.IAzdataApi {
replaceEngineSettings?: boolean,
workers?: number
},
_engineVersion?: string,
_additionalEnvVars?: { [key: string]: string }): Promise<azdataExt.AzdataOutput<void>> { throw new Error('Method not implemented.'); }
}
},

View File

@@ -11,7 +11,7 @@ import { AzureArcTreeDataProvider } from '../../ui/tree/azureArcTreeDataProvider
export class FakeControllerModel extends ControllerModel {
constructor(treeDataProvider?: AzureArcTreeDataProvider, info?: Partial<ControllerInfo>, password?: string) {
const _info: ControllerInfo = Object.assign({ id: uuid(), url: '', kubeConfigFilePath: '', kubeClusterContext: '', name: '', username: '', rememberPassword: false, resources: [] }, info);
const _info: ControllerInfo = Object.assign({ id: uuid(), url: '', name: '', username: '', rememberPassword: false, resources: [] }, info);
super(treeDataProvider!, _info, password);
}

View File

@@ -11,8 +11,7 @@ import * as sinon from 'sinon';
import * as TypeMoq from 'typemoq';
import { v4 as uuid } from 'uuid';
import * as vscode from 'vscode';
import * as loc from '../../localizedConstants';
import { UserCancelledError } from '../../common/api';
import { UserCancelledError } from '../../common/utils';
import { ControllerModel } from '../../models/controllerModel';
import { ConnectToControllerDialog } from '../../ui/dialogs/connectControllerDialog';
import { AzureArcTreeDataProvider } from '../../ui/tree/azureArcTreeDataProvider';
@@ -39,8 +38,8 @@ describe('ControllerModel', function (): void {
it('Rejected with expected error when user cancels', async function (): Promise<void> {
// Returning an undefined model here indicates that the dialog closed without clicking "Ok" - usually through the user clicking "Cancel"
sinon.stub(ConnectToControllerDialog.prototype, 'waitForClose').returns(Promise.resolve(undefined));
const model = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), { id: uuid(), url: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', username: 'admin', name: 'arc', rememberPassword: true, resources: [] });
await should(model.azdataLogin()).be.rejectedWith(new UserCancelledError(loc.userCancelledError));
const model = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), { id: uuid(), url: '127.0.0.1', username: 'admin', name: 'arc', rememberPassword: true, resources: [] });
await should(model.azdataLogin()).be.rejectedWith(new UserCancelledError());
});
it('Reads password from cred store', async function (): Promise<void> {
@@ -58,14 +57,14 @@ describe('ControllerModel', function (): void {
azdataMock.setup(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => <any>Promise.resolve(undefined));
azdataExtApiMock.setup(x => x.azdata).returns(() => azdataMock.object);
sinon.stub(vscode.extensions, 'getExtension').returns(<any>{ exports: azdataExtApiMock.object });
const model = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), { id: uuid(), url: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', username: 'admin', name: 'arc', rememberPassword: true, resources: [] });
const model = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), { id: uuid(), url: '127.0.0.1', username: 'admin', name: 'arc', rememberPassword: true, resources: [] });
await model.azdataLogin();
azdataMock.verify(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), password), TypeMoq.Times.once());
});
it('Prompt for password when not in cred store', async function (): Promise<void> {
const password = 'password123'; // [SuppressMessage("Microsoft.Security", "CS001:SecretInline", Justification="Stub value for testing")]
const password = 'password123';
// Set up cred store to return empty password
const credProviderMock = TypeMoq.Mock.ofType<azdata.CredentialProvider>();
@@ -81,17 +80,17 @@ describe('ControllerModel', function (): void {
sinon.stub(vscode.extensions, 'getExtension').returns(<any>{ exports: azdataExtApiMock.object });
// Set up dialog to return new model with our password
const newModel = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), { id: uuid(), url: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', username: 'admin', name: 'arc', rememberPassword: true, resources: [] }, password);
const newModel = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), { id: uuid(), url: '127.0.0.1', username: 'admin', name: 'arc', rememberPassword: true, resources: [] }, password);
sinon.stub(ConnectToControllerDialog.prototype, 'waitForClose').returns(Promise.resolve({ controllerModel: newModel, password: password }));
const model = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), { id: uuid(), url: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', username: 'admin', name: 'arc', rememberPassword: true, resources: [] });
const model = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), { id: uuid(), url: '127.0.0.1', username: 'admin', name: 'arc', rememberPassword: true, resources: [] });
await model.azdataLogin();
azdataMock.verify(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), password), TypeMoq.Times.once());
});
it('Prompt for password when rememberPassword is true but prompt reconnect is true', async function (): Promise<void> {
const password = 'password123'; // [SuppressMessage("Microsoft.Security", "CS001:SecretInline", Justification="Stub value for testing")]
const password = 'password123';
// Set up cred store to return a password to start with
const credProviderMock = TypeMoq.Mock.ofType<azdata.CredentialProvider>();
credProviderMock.setup(x => x.readCredential(TypeMoq.It.isAny())).returns(() => Promise.resolve({ credentialId: 'id', password: 'originalPassword' }));
@@ -106,10 +105,10 @@ describe('ControllerModel', function (): void {
sinon.stub(vscode.extensions, 'getExtension').returns(<any>{ exports: azdataExtApiMock.object });
// Set up dialog to return new model with our new password from the reprompt
const newModel = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), { id: uuid(), url: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', username: 'admin', name: 'arc', rememberPassword: true, resources: [] }, password);
const newModel = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), { id: uuid(), url: '127.0.0.1', username: 'admin', name: 'arc', rememberPassword: true, resources: [] }, password);
const waitForCloseStub = sinon.stub(ConnectToControllerDialog.prototype, 'waitForClose').returns(Promise.resolve({ controllerModel: newModel, password: password }));
const model = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), { id: uuid(), url: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', username: 'admin', name: 'arc', rememberPassword: true, resources: [] });
const model = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), { id: uuid(), url: '127.0.0.1', username: 'admin', name: 'arc', rememberPassword: true, resources: [] });
await model.azdataLogin(true);
should(waitForCloseStub.called).be.true('waitForClose should have been called');
@@ -117,7 +116,7 @@ describe('ControllerModel', function (): void {
});
it('Prompt for password when we already have a password but prompt reconnect is true', async function (): Promise<void> {
const password = 'password123'; // [SuppressMessage("Microsoft.Security", "CS001:SecretInline", Justification="Stub value for testing")]
const password = 'password123';
// Set up cred store to return a password to start with
const credProviderMock = TypeMoq.Mock.ofType<azdata.CredentialProvider>();
credProviderMock.setup(x => x.readCredential(TypeMoq.It.isAny())).returns(() => Promise.resolve({ credentialId: 'id', password: 'originalPassword' }));
@@ -132,11 +131,11 @@ describe('ControllerModel', function (): void {
sinon.stub(vscode.extensions, 'getExtension').returns(<any>{ exports: azdataExtApiMock.object });
// Set up dialog to return new model with our new password from the reprompt
const newModel = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), { id: uuid(), url: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', username: 'admin', name: 'arc', rememberPassword: true, resources: [] }, password);
const newModel = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), { id: uuid(), url: '127.0.0.1', username: 'admin', name: 'arc', rememberPassword: true, resources: [] }, password);
const waitForCloseStub = sinon.stub(ConnectToControllerDialog.prototype, 'waitForClose').returns(Promise.resolve({ controllerModel: newModel, password: password }));
// Set up original model with a password
const model = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), { id: uuid(), url: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', username: 'admin', name: 'arc', rememberPassword: true, resources: [] }, 'originalPassword');
const model = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), { id: uuid(), url: '127.0.0.1', username: 'admin', name: 'arc', rememberPassword: true, resources: [] }, 'originalPassword');
await model.azdataLogin(true);
should(waitForCloseStub.called).be.true('waitForClose should have been called');
@@ -166,8 +165,6 @@ describe('ControllerModel', function (): void {
{
id: uuid(),
url: '127.0.0.1',
kubeConfigFilePath: '/path/to/.kube/config',
kubeClusterContext: 'currentCluster',
username: 'admin',
name: 'arc',
rememberPassword: false,
@@ -180,8 +177,6 @@ describe('ControllerModel', function (): void {
const newInfo: ControllerInfo = {
id: model.info.id, // The ID stays the same since we're just re-entering information for the same model
url: 'newUrl',
kubeConfigFilePath: '/path/to/.kube/config',
kubeClusterContext: 'currentCluster',
username: 'newUser',
name: 'newName',
rememberPassword: true,

View File

@@ -7,44 +7,21 @@ import * as azdata from 'azdata';
import * as TypeMoq from 'typemoq';
import * as vscode from 'vscode';
interface ModelViewMocks {
mockModelView: TypeMoq.IMock<azdata.ModelView>,
mockModelBuilder: TypeMoq.IMock<azdata.ModelBuilder>,
mockTextBuilder: TypeMoq.IMock<azdata.ComponentBuilder<azdata.TextComponent, azdata.TextComponentProperties>>,
mockInputBoxBuilder: TypeMoq.IMock<azdata.ComponentBuilder<azdata.InputBoxComponent, azdata.InputBoxProperties>>,
mockButtonBuilder: TypeMoq.IMock<azdata.ComponentBuilder<azdata.ButtonComponent, azdata.ButtonProperties>>,
mockRadioButtonBuilder: TypeMoq.IMock<azdata.ComponentBuilder<azdata.RadioButtonComponent, azdata.RadioButtonProperties>>,
mockDivBuilder: TypeMoq.IMock<azdata.DivBuilder>,
mockFlexBuilder: TypeMoq.IMock<azdata.FlexBuilder>,
mockLoadingBuilder: TypeMoq.IMock<azdata.LoadingComponentBuilder>
}
export function createModelViewMock(buttonClickEmitter?: vscode.EventEmitter<any>): ModelViewMocks {
export function createModelViewMock() {
const mockModelBuilder = TypeMoq.Mock.ofType<azdata.ModelBuilder>();
const mockTextBuilder = setupMockComponentBuilder<azdata.TextComponent, azdata.TextComponentProperties>();
const mockInputBoxBuilder = setupMockComponentBuilder<azdata.InputBoxComponent, azdata.InputBoxProperties>();
buttonClickEmitter = buttonClickEmitter ?? new vscode.EventEmitter<any>();
const mockButtonBuilder = setupMockButtonBuilderWithClickEmitter(buttonClickEmitter);
const mockRadioButtonBuilder = setupMockComponentBuilder<azdata.RadioButtonComponent, azdata.RadioButtonProperties>();
const mockDivBuilder = setupMockContainerBuilder<azdata.DivContainer, azdata.DivContainerProperties, azdata.DivBuilder>();
const mockFlexBuilder = setupMockContainerBuilder<azdata.FlexContainer, azdata.ComponentProperties, azdata.FlexBuilder>();
const mockLoadingBuilder = setupMockLoadingBuilder();
mockModelBuilder.setup(b => b.loadingComponent()).returns(() => mockLoadingBuilder.object);
mockModelBuilder.setup(b => b.text()).returns(() => mockTextBuilder.object);
mockModelBuilder.setup(b => b.inputBox()).returns(() => mockInputBoxBuilder.object);
mockModelBuilder.setup(b => b.button()).returns(() => mockButtonBuilder.object);
mockModelBuilder.setup(b => b.radioButton()).returns(() => mockRadioButtonBuilder.object);
mockModelBuilder.setup(b => b.divContainer()).returns(() => mockDivBuilder.object);
mockModelBuilder.setup(b => b.flexContainer()).returns(() => mockFlexBuilder.object);
const mockModelView = TypeMoq.Mock.ofType<azdata.ModelView>();
mockModelView.setup(mv => mv.modelBuilder).returns(() => mockModelBuilder.object);
return { mockModelView, mockModelBuilder, mockTextBuilder, mockInputBoxBuilder, mockButtonBuilder, mockRadioButtonBuilder, mockDivBuilder, mockFlexBuilder, mockLoadingBuilder };
}
function setupMockButtonBuilderWithClickEmitter(buttonClickEmitter: vscode.EventEmitter<any>): TypeMoq.IMock<azdata.ComponentBuilder<azdata.ButtonComponent, azdata.ButtonProperties>> {
const { mockComponentBuilder: mockButtonBuilder, mockComponent: mockButtonComponent } = setupMockComponentBuilderAndComponent<azdata.ButtonComponent, azdata.ButtonProperties>();
mockButtonComponent.setup(b => b.onDidClick(TypeMoq.It.isAny())).returns(buttonClickEmitter.event);
return mockButtonBuilder;
return { mockModelView, mockModelBuilder, mockTextBuilder, mockInputBoxBuilder, mockRadioButtonBuilder, mockDivBuilder };
}
function setupMockLoadingBuilder(
@@ -62,44 +39,26 @@ export function setupMockComponentBuilder<T extends azdata.Component, P extends
mockComponentBuilder?: TypeMoq.IMock<B>,
): TypeMoq.IMock<B> {
mockComponentBuilder = mockComponentBuilder ?? TypeMoq.Mock.ofType<B>();
setupMockComponentBuilderAndComponent<T, P, B>(mockComponentBuilder, componentGetter);
return mockComponentBuilder;
}
function setupMockComponentBuilderAndComponent<T extends azdata.Component, P extends azdata.ComponentProperties, B extends azdata.ComponentBuilder<T, P> = azdata.ComponentBuilder<T, P>>(
mockComponentBuilder?: TypeMoq.IMock<B>,
componentGetter?: ((props: P) => T)
): { mockComponentBuilder: TypeMoq.IMock<B>, mockComponent: TypeMoq.IMock<T> } {
mockComponentBuilder = mockComponentBuilder ?? TypeMoq.Mock.ofType<B>();
const mockComponent = createComponentMock<T>();
const returnComponent = TypeMoq.Mock.ofType<T>();
// Need to setup 'then' for when a mocked object is resolved otherwise the test will hang : https://github.com/florinn/typemoq/issues/66
returnComponent.setup((x: any) => x.then).returns(() => { });
let compProps: P;
mockComponentBuilder.setup(b => b.withProperties(TypeMoq.It.isAny())).callback((props: P) => compProps = props).returns(() => mockComponentBuilder!.object);
mockComponentBuilder.setup(b => b.component()).returns(() => {
return componentGetter ? componentGetter(compProps) : Object.assign<T, P>(Object.assign({}, mockComponent.object), compProps);
return componentGetter ? componentGetter(compProps) : Object.assign<T, P>(Object.assign({}, returnComponent.object), compProps);
});
// For now just have these be passthrough - can hook up additional functionality later if needed
mockComponentBuilder.setup(b => b.withValidation(TypeMoq.It.isAny())).returns(() => mockComponentBuilder!.object);
return { mockComponentBuilder, mockComponent };
}
function createComponentMock<T extends azdata.Component>(): TypeMoq.IMock<T> {
const mockComponent = TypeMoq.Mock.ofType<T>();
// Need to setup 'then' for when a mocked object is resolved otherwise the test will hang : https://github.com/florinn/typemoq/issues/66
mockComponent.setup((x: any) => x.then).returns(() => { });
return mockComponent;
return mockComponentBuilder;
}
export function setupMockContainerBuilder<T extends azdata.Container<any, any>, P extends azdata.ComponentProperties, B extends azdata.ContainerBuilder<T, any, any, any> = azdata.ContainerBuilder<T, any, any, any>>(
mockContainerBuilder?: TypeMoq.IMock<B>
): TypeMoq.IMock<B> {
const items: azdata.Component[] = [];
const mockContainer = createComponentMock<T>(); // T is azdata.Container type so this creates a azdata.Container mock
mockContainer.setup(c => c.items).returns(() => items);
mockContainerBuilder = mockContainerBuilder ?? setupMockComponentBuilder<T, P, B>((_props) => mockContainer.object);
mockContainerBuilder.setup(b => b.withItems(TypeMoq.It.isAny(), TypeMoq.It.isAny())).callback((_items, _itemsStyle) => items.push(..._items)).returns(() => mockContainerBuilder!.object);
mockContainerBuilder = mockContainerBuilder ?? setupMockComponentBuilder<T, P, B>();
// For now just have these be passthrough - can hook up additional functionality later if needed
mockContainerBuilder.setup(b => b.withItems(TypeMoq.It.isAny(), undefined)).returns(() => mockContainerBuilder!.object);
mockContainerBuilder.setup(b => b.withLayout(TypeMoq.It.isAny())).returns(() => mockContainerBuilder!.object);
return mockContainerBuilder;
}

View File

@@ -1,66 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import * as should from 'should';
import * as sinon from 'sinon';
import * as vscode from 'vscode';
import { Deferred } from '../../../common/promise';
import { FilePicker } from '../../../ui/components/filePicker';
import { createModelViewMock } from '../../stubs';
let filePicker: FilePicker;
const initialPath = '/path/to/.kube/config';
const newFilePath = '/path/to/new/.kube/config';
let filePathInputBox: azdata.InputBoxComponent;
let browseButton: azdata.ButtonComponent;
let flexContainer: azdata.FlexContainer;
const browseButtonEmitter = new vscode.EventEmitter<undefined>();
describe('filePicker', function (): void {
beforeEach(async () => {
const { mockModelBuilder, mockInputBoxBuilder, mockButtonBuilder, mockFlexBuilder } = createModelViewMock(browseButtonEmitter);
filePicker = new FilePicker(mockModelBuilder.object, initialPath, (_disposable) => { });
filePathInputBox = mockInputBoxBuilder.object.component();
browseButton = mockButtonBuilder.object.component();
flexContainer = mockFlexBuilder.object.component();
});
afterEach(() => {
sinon.restore();
});
it('browse Button chooses new FilePath', async () => {
should(filePathInputBox.value).should.not.be.undefined();
filePicker.value!.should.equal(initialPath);
flexContainer.items.should.deepEqual([filePathInputBox, browseButton]);
const deferred = new Deferred();
sinon.stub(vscode.window, 'showOpenDialog').callsFake(async (_options) => {
deferred.resolve();
return [vscode.Uri.file(newFilePath)];
});
browseButtonEmitter.fire(undefined); //simulate the click of the browseButton
await deferred;
filePicker.value!.should.equal(newFilePath);
});
describe('getters and setters', async () => {
it('component getter', () => {
should(filePicker.component()).equal(flexContainer);
});
[true, false].forEach(testValue => {
it(`Test readOnly with testValue: ${testValue}`, () => {
filePicker.readOnly = testValue;
filePicker.readOnly!.should.equal(testValue);
});
it(`Test enabled with testValue: ${testValue}`, () => {
filePicker.enabled = testValue;
filePicker.enabled!.should.equal(testValue);
});
});
});
});

View File

@@ -21,11 +21,11 @@ const radioOptionsInfo = <RadioOptionsInfo>{
};
const divItems: azdata.Component[] = [];
let radioOptionsGroup: RadioOptionsGroup;
let loadingComponent: azdata.LoadingComponent;
describe('radioOptionsGroup', function (): void {
beforeEach(async () => {
const { mockModelBuilder, mockRadioButtonBuilder, mockDivBuilder, mockLoadingBuilder } = createModelViewMock();
const { mockModelView, mockRadioButtonBuilder, mockDivBuilder } = createModelViewMock();
mockRadioButtonBuilder.reset(); // reset any previous mock so that we can set our own.
setupMockComponentBuilder<azdata.RadioButtonComponent, azdata.RadioButtonProperties>(
(props) => new FakeRadioButton(props),
@@ -41,9 +41,8 @@ describe('radioOptionsGroup', function (): void {
},
mockDivBuilder
);
radioOptionsGroup = new RadioOptionsGroup(mockModelBuilder.object, (_disposable) => { });
radioOptionsGroup = new RadioOptionsGroup(mockModelView.object, (_disposable) => { });
await radioOptionsGroup.load(async () => radioOptionsInfo);
loadingComponent = mockLoadingBuilder.object.component();
});
it('verify construction and load', async () => {
@@ -73,23 +72,6 @@ describe('radioOptionsGroup', function (): void {
should(label.CSSStyles!.color).not.be.undefined();
label.CSSStyles!.color.should.equal('Red');
});
describe('getters and setters', async () => {
it(`component getter`, () => {
radioOptionsGroup.component().should.deepEqual(loadingComponent);
});
[true, false].forEach(testValue => {
it(`Test readOnly with testValue: ${testValue}`, () => {
radioOptionsGroup.readOnly = testValue;
radioOptionsGroup.readOnly!.should.equal(testValue);
});
it(`Test enabled with testValue: ${testValue}`, () => {
radioOptionsGroup.enabled = testValue;
radioOptionsGroup.enabled!.should.equal(testValue);
});
});
});
});
function verifyRadioGroup() {

View File

@@ -32,7 +32,7 @@ describe('ConnectControllerDialog', function (): void {
it('validate returns false if controller refresh fails', async function (): Promise<void> {
sinon.stub(ControllerModel.prototype, 'refresh').returns(Promise.reject('Controller refresh failed'));
const connectControllerDialog = new ConnectToControllerDialog(undefined!);
const info = { id: uuid(), url: 'https://127.0.0.1:30080', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] };
const info = { id: uuid(), url: 'https://127.0.0.1:30080', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] };
connectControllerDialog.showDialog(info, 'pwd');
await connectControllerDialog.isInitialized;
const validateResult = await connectControllerDialog.validate();
@@ -41,36 +41,36 @@ describe('ConnectControllerDialog', function (): void {
it('validate replaces http with https', async function (): Promise<void> {
await validateConnectControllerDialog(
{ id: uuid(), url: 'http://127.0.0.1:30081', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] },
{ id: uuid(), url: 'http://127.0.0.1:30081', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] },
'https://127.0.0.1:30081');
});
it('validate appends https if missing', async function (): Promise<void> {
await validateConnectControllerDialog({ id: uuid(), url: '127.0.0.1:30080', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] },
await validateConnectControllerDialog({ id: uuid(), url: '127.0.0.1:30080', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] },
'https://127.0.0.1:30080');
});
it('validate appends default port if missing', async function (): Promise<void> {
await validateConnectControllerDialog({ id: uuid(), url: 'https://127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] },
await validateConnectControllerDialog({ id: uuid(), url: 'https://127.0.0.1', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] },
'https://127.0.0.1:30080');
});
it('validate appends both port and https if missing', async function (): Promise<void> {
await validateConnectControllerDialog({ id: uuid(), url: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] },
await validateConnectControllerDialog({ id: uuid(), url: '127.0.0.1', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] },
'https://127.0.0.1:30080');
});
for (const name of ['', undefined]) {
it.skip(`validate display name gets set to arc instance name for user chosen name of:${name}`, async function (): Promise<void> {
await validateConnectControllerDialog(
{ id: uuid(), url: 'http://127.0.0.1:30081', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: name!, username: 'sa', rememberPassword: true, resources: [] },
{ id: uuid(), url: 'http://127.0.0.1:30081', name: name!, username: 'sa', rememberPassword: true, resources: [] },
'https://127.0.0.1:30081');
});
}
it.skip(`validate display name gets set to default data controller name for user chosen name of:'' and instanceName in explicably returned as undefined from the controller endpoint`, async function (): Promise<void> {
await validateConnectControllerDialog(
{ id: uuid(), url: 'http://127.0.0.1:30081', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: '', username: 'sa', rememberPassword: true, resources: [] },
{ id: uuid(), url: 'http://127.0.0.1:30081', name: '', username: 'sa', rememberPassword: true, resources: [] },
'https://127.0.0.1:30081',
undefined);
});

View File

@@ -53,7 +53,7 @@ describe('AzureArcTreeDataProvider tests', function (): void {
treeDataProvider['_loading'] = false;
let children = await treeDataProvider.getChildren();
should(children.length).equal(0, 'There initially shouldn\'t be any children');
const controllerModel = new ControllerModel(treeDataProvider, { id: uuid(), url: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] });
const controllerModel = new ControllerModel(treeDataProvider, { id: uuid(), url: '127.0.0.1', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] });
await treeDataProvider.addOrUpdateController(controllerModel, '');
should(children.length).equal(1, 'Controller node should be added correctly');
await treeDataProvider.addOrUpdateController(controllerModel, '');
@@ -64,12 +64,12 @@ describe('AzureArcTreeDataProvider tests', function (): void {
treeDataProvider['_loading'] = false;
let children = await treeDataProvider.getChildren();
should(children.length).equal(0, 'There initially shouldn\'t be any children');
const originalInfo: ControllerInfo = { id: uuid(), url: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] };
const originalInfo: ControllerInfo = { id: uuid(), url: '127.0.0.1', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] };
const controllerModel = new ControllerModel(treeDataProvider, originalInfo);
await treeDataProvider.addOrUpdateController(controllerModel, '');
should(children.length).equal(1, 'Controller node should be added correctly');
should((<ControllerTreeNode>children[0]).model.info).deepEqual(originalInfo);
const newInfo = { id: originalInfo.id, url: '1.1.1.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'new-name', username: 'admin', rememberPassword: false, resources: [] };
const newInfo = { id: originalInfo.id, url: '1.1.1.1', name: 'new-name', username: 'admin', rememberPassword: false, resources: [] };
const controllerModel2 = new ControllerModel(treeDataProvider, newInfo);
await treeDataProvider.addOrUpdateController(controllerModel2, '');
should(children.length).equal(1, 'Shouldn\'t add duplicate controller node');
@@ -102,7 +102,7 @@ describe('AzureArcTreeDataProvider tests', function (): void {
mockArcApi.setup(x => x.azdata).returns(() => fakeAzdataApi);
sinon.stub(vscode.extensions, 'getExtension').returns(mockArcExtension.object);
const controllerModel = new ControllerModel(treeDataProvider, { id: uuid(), url: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] }, 'mypassword');
const controllerModel = new ControllerModel(treeDataProvider, { id: uuid(), url: '127.0.0.1', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] }, 'mypassword');
await treeDataProvider.addOrUpdateController(controllerModel, '');
const controllerNode = treeDataProvider.getControllerNode(controllerModel);
const children = await treeDataProvider.getChildren(controllerNode);
@@ -115,8 +115,8 @@ describe('AzureArcTreeDataProvider tests', function (): void {
describe('removeController', function (): void {
it('removing a controller should work as expected', async function (): Promise<void> {
treeDataProvider['_loading'] = false;
const controllerModel = new ControllerModel(treeDataProvider, { id: uuid(), url: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] });
const controllerModel2 = new ControllerModel(treeDataProvider, { id: uuid(), url: '127.0.0.2', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', username: 'cloudsa', rememberPassword: true, resources: [] });
const controllerModel = new ControllerModel(treeDataProvider, { id: uuid(), url: '127.0.0.1', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] });
const controllerModel2 = new ControllerModel(treeDataProvider, { id: uuid(), url: '127.0.0.2', name: 'my-arc', username: 'cloudsa', rememberPassword: true, resources: [] });
await treeDataProvider.addOrUpdateController(controllerModel, '');
await treeDataProvider.addOrUpdateController(controllerModel2, '');
const children = <ControllerTreeNode[]>(await treeDataProvider.getChildren());
@@ -133,20 +133,20 @@ describe('AzureArcTreeDataProvider tests', function (): void {
describe('openResourceDashboard', function (): void {
it('Opening dashboard for nonexistent controller node throws', async function (): Promise<void> {
const controllerModel = new ControllerModel(treeDataProvider, { id: uuid(), url: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] });
const controllerModel = new ControllerModel(treeDataProvider, { id: uuid(), url: '127.0.0.1', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] });
const openDashboardPromise = treeDataProvider.openResourceDashboard(controllerModel, ResourceType.sqlManagedInstances, '');
await should(openDashboardPromise).be.rejected();
});
it('Opening dashboard for nonexistent resource throws', async function (): Promise<void> {
const controllerModel = new ControllerModel(treeDataProvider, { id: uuid(), url: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] });
const controllerModel = new ControllerModel(treeDataProvider, { id: uuid(), url: '127.0.0.1', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] });
await treeDataProvider.addOrUpdateController(controllerModel, '');
const openDashboardPromise = treeDataProvider.openResourceDashboard(controllerModel, ResourceType.sqlManagedInstances, '');
await should(openDashboardPromise).be.rejected();
});
it('Opening dashboard for existing resource node succeeds', async function (): Promise<void> {
const controllerModel = new ControllerModel(treeDataProvider, { id: uuid(), url: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] });
const controllerModel = new ControllerModel(treeDataProvider, { id: uuid(), url: '127.0.0.1', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] });
const miaaModel = new MiaaModel(controllerModel, { name: 'miaa-1', resourceType: ResourceType.sqlManagedInstances }, undefined!, treeDataProvider);
await treeDataProvider.addOrUpdateController(controllerModel, '');
const controllerNode = treeDataProvider.getControllerNode(controllerModel)!;

View File

@@ -31,8 +31,6 @@ declare module 'arc' {
export type ControllerInfo = {
id: string,
kubeConfigFilePath: string,
kubeClusterContext: string
url: string,
name: string,
username: string,

View File

@@ -1,91 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import * as path from 'path';
import * as vscode from 'vscode';
import * as loc from '../../localizedConstants';
import { IReadOnly } from '../dialogs/connectControllerDialog';
export interface RadioOptionsInfo {
values?: string[],
defaultValue: string
}
export class FilePicker implements IReadOnly {
private _flexContainer: azdata.FlexContainer;
private _filePathInputBox: azdata.InputBoxComponent;
private _filePickerButton: azdata.ButtonComponent;
constructor(
modelBuilder: azdata.ModelBuilder,
initialPath: string, onNewDisposableCreated: (disposable: vscode.Disposable) => void
) {
const buttonWidth = 80;
this._filePathInputBox = modelBuilder.inputBox()
.withProperties<azdata.InputBoxProperties>({
value: initialPath,
width: 350
}).component();
this._filePickerButton = modelBuilder.button()
.withProperties<azdata.ButtonProperties>({
label: loc.browse,
width: buttonWidth
}).component();
onNewDisposableCreated(this._filePickerButton.onDidClick(async () => {
const fileUris = await vscode.window.showOpenDialog({
canSelectFiles: true,
canSelectFolders: false,
canSelectMany: false,
defaultUri: this._filePathInputBox.value ? vscode.Uri.file(path.dirname(this._filePathInputBox.value)) : undefined,
openLabel: loc.select,
filters: undefined /* file type filters */
});
if (!fileUris || fileUris.length === 0) {
return; // This can happen when a user cancels out. we don't throw and the user just won't be able to move on until they select something.
}
const fileUri = fileUris[0]; //we allow the user to select only one file in the dialog
this._filePathInputBox.value = fileUri.fsPath;
}));
this._flexContainer = createFlexContainer(modelBuilder, [this._filePathInputBox, this._filePickerButton]);
}
component(): azdata.Component {
return this._flexContainer;
}
get onTextChanged() {
return this._filePathInputBox.onTextChanged;
}
get value(): string | undefined {
return this._filePathInputBox?.value;
}
get readOnly(): boolean {
return this.enabled;
}
set readOnly(value: boolean) {
this.enabled = value;
}
get enabled(): boolean {
return !!this._flexContainer.enabled && this._flexContainer.items.every(r => r.enabled);
}
set enabled(value: boolean) {
this._flexContainer.items.forEach(r => r.enabled = value);
this._flexContainer.enabled = value;
}
}
function createFlexContainer(modelBuilder: azdata.ModelBuilder, items: azdata.Component[], rowLayout: boolean = true, width?: string | number, height?: string | number, alignItems?: azdata.AlignItemsType, cssStyles?: { [key: string]: string }): azdata.FlexContainer {
const flexFlow = rowLayout ? 'row' : 'column';
alignItems = alignItems || (rowLayout ? 'center' : undefined);
const itemsStyle = rowLayout ? { CSSStyles: { 'margin-right': '5px', } } : {};
const flexLayout: azdata.FlexLayout = { flexFlow: flexFlow, height: height, width: width, alignItems: alignItems };
return modelBuilder.flexContainer().withItems(items, itemsStyle).withLayout(flexLayout).withProperties<azdata.ComponentProperties>({ CSSStyles: cssStyles || {} }).component();
}

View File

@@ -5,22 +5,24 @@
import * as azdata from 'azdata';
import * as vscode from 'vscode';
import { getErrorMessage } from '../../common/utils';
import { IReadOnly } from '../dialogs/connectControllerDialog';
export interface RadioOptionsInfo {
values?: string[],
defaultValue: string
}
export class RadioOptionsGroup implements IReadOnly {
export class RadioOptionsGroup {
static id: number = 1;
private _divContainer!: azdata.DivContainer;
private _loadingBuilder: azdata.LoadingComponentBuilder;
private _currentRadioOption!: azdata.RadioButtonComponent;
constructor(private _modelBuilder: azdata.ModelBuilder, private _onNewDisposableCreated: (disposable: vscode.Disposable) => void, private _groupName: string = `RadioOptionsGroup${RadioOptionsGroup.id++}`) {
this._divContainer = this._modelBuilder.divContainer().withProperties<azdata.DivContainerProperties>({ clickable: false }).component();
this._loadingBuilder = this._modelBuilder.loadingComponent().withItem(this._divContainer);
constructor(private _view: azdata.ModelView, private _onNewDisposableCreated: (disposable: vscode.Disposable) => void, private _groupName: string = `RadioOptionsGroup${RadioOptionsGroup.id++}`) {
const divBuilder = this._view.modelBuilder.divContainer();
const divBuilderWithProperties = divBuilder.withProperties<azdata.DivContainerProperties>({ clickable: false });
this._divContainer = divBuilderWithProperties.component();
const loadingComponentBuilder = this._view.modelBuilder.loadingComponent();
this._loadingBuilder = loadingComponentBuilder.withItem(this._divContainer);
}
public component(): azdata.LoadingComponent {
@@ -35,7 +37,7 @@ export class RadioOptionsGroup implements IReadOnly {
const options = optionsInfo.values!;
let defaultValue: string = optionsInfo.defaultValue!;
options.forEach((option: string) => {
const radioOption = this._modelBuilder.radioButton().withProperties<azdata.RadioButtonProperties>({
const radioOption = this._view!.modelBuilder.radioButton().withProperties<azdata.RadioButtonProperties>({
label: option,
checked: option === defaultValue,
name: this._groupName,
@@ -58,7 +60,7 @@ export class RadioOptionsGroup implements IReadOnly {
});
}
catch (e) {
const errorLabel = this._modelBuilder.text().withProperties({ value: getErrorMessage(e), CSSStyles: { 'color': 'Red' } }).component();
const errorLabel = this._view!.modelBuilder.text().withProperties({ value: getErrorMessage(e), CSSStyles: { 'color': 'Red' } }).component();
this._divContainer.addItem(errorLabel);
}
this.component().loading = false;
@@ -67,21 +69,4 @@ export class RadioOptionsGroup implements IReadOnly {
get value(): string | undefined {
return this._currentRadioOption?.value;
}
get readOnly(): boolean {
return this.enabled;
}
set readOnly(value: boolean) {
this.enabled = value;
}
get enabled(): boolean {
return !!this._divContainer.enabled && this._divContainer.items.every(r => r.enabled);
}
set enabled(value: boolean) {
this._divContainer.items.forEach(r => r.enabled = value);
this._divContainer.enabled = value;
}
}

View File

@@ -179,6 +179,7 @@ export class MiaaComputeAndStoragePage extends DashboardPage {
this.coresLimitBox = this.modelView.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({
readOnly: false,
min: 1,
validationErrorMessage: loc.coresValidationErrorMessage,
inputType: 'number',
placeHolder: loc.loading
}).component();
@@ -196,6 +197,7 @@ export class MiaaComputeAndStoragePage extends DashboardPage {
this.coresRequestBox = this.modelView.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({
readOnly: false,
min: 1,
validationErrorMessage: loc.coresValidationErrorMessage,
inputType: 'number',
placeHolder: loc.loading
}).component();
@@ -316,7 +318,6 @@ export class MiaaComputeAndStoragePage extends DashboardPage {
currentCPUSize = '';
}
this.coresRequestBox!.validationErrorMessage = loc.validationMin(this.coresRequestBox!.min!);
this.coresRequestBox!.placeHolder = currentCPUSize;
this.coresRequestBox!.value = '';
this.saveArgs.coresRequest = undefined;
@@ -327,7 +328,6 @@ export class MiaaComputeAndStoragePage extends DashboardPage {
currentCPUSize = '';
}
this.coresLimitBox!.validationErrorMessage = loc.validationMin(this.coresLimitBox!.min!);
this.coresLimitBox!.placeHolder = currentCPUSize;
this.coresLimitBox!.value = '';
this.saveArgs.coresLimit = undefined;

View File

@@ -76,7 +76,7 @@ export class PostgresComputeAndStoragePage extends DashboardPage {
}).component();
const workerNodeslink = this.modelView.modelBuilder.hyperlink().withProperties<azdata.HyperlinkComponentProperties>({
label: loc.addingWorkerNodes,
label: loc.addingWokerNodes,
url: 'https://docs.microsoft.com/azure/azure-arc/data/scale-up-down-postgresql-hyperscale-server-group-using-cli',
CSSStyles: { 'margin-block-start': '0px', 'margin-block-end': '0px' }
}).component();
@@ -157,9 +157,7 @@ export class PostgresComputeAndStoragePage extends DashboardPage {
async (_progress, _token): Promise<void> => {
try {
await this._azdataApi.azdata.arc.postgres.server.edit(
this._postgresModel.info.name,
this.saveArgs,
this._postgresModel.engineVersion);
this._postgresModel.info.name, this.saveArgs);
} catch (err) {
// If an error occurs while editing the instance then re-enable the save button since
// the edit wasn't successfully applied
@@ -227,6 +225,7 @@ export class PostgresComputeAndStoragePage extends DashboardPage {
this.coresLimitBox = this.modelView.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({
readOnly: false,
min: 1,
validationErrorMessage: loc.coresValidationErrorMessage,
inputType: 'number',
placeHolder: loc.loading
}).component();
@@ -244,6 +243,7 @@ export class PostgresComputeAndStoragePage extends DashboardPage {
this.coresRequestBox = this.modelView.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({
readOnly: false,
min: 1,
validationErrorMessage: loc.coresValidationErrorMessage,
inputType: 'number',
placeHolder: loc.loading
}).component();
@@ -448,7 +448,6 @@ export class PostgresComputeAndStoragePage extends DashboardPage {
currentCPUSize = '';
}
this.coresRequestBox!.validationErrorMessage = loc.validationMin(this.coresRequestBox!.min!);
this.coresRequestBox!.placeHolder = currentCPUSize;
this.coresRequestBox!.value = '';
this.saveArgs.coresRequest = undefined;
@@ -459,7 +458,6 @@ export class PostgresComputeAndStoragePage extends DashboardPage {
currentCPUSize = '';
}
this.coresLimitBox!.validationErrorMessage = loc.validationMin(this.coresLimitBox!.min!);
this.coresLimitBox!.placeHolder = currentCPUSize;
this.coresLimitBox!.value = '';
this.saveArgs.coresLimit = undefined;

View File

@@ -157,7 +157,6 @@ export class PostgresOverviewPage extends DashboardPage {
adminPassword: true,
noWait: true
},
this._postgresModel.engineVersion,
{ 'AZDATA_PASSWORD': password });
vscode.window.showInformationMessage(loc.passwordReset);
}

View File

@@ -14,45 +14,23 @@ import { ControllerModel } from '../../models/controllerModel';
import { InitializingComponent } from '../components/initializingComponent';
import { AzureArcTreeDataProvider } from '../tree/azureArcTreeDataProvider';
import { getErrorMessage } from '../../common/utils';
import { RadioOptionsGroup } from '../components/radioOptionsGroup';
import { getCurrentClusterContext, getDefaultKubeConfigPath, getKubeConfigClusterContexts } from '../../common/kubeUtils';
import { FilePicker } from '../components/filePicker';
export type ConnectToControllerDialogModel = { controllerModel: ControllerModel, password: string };
export interface IReadOnly {
readOnly?: boolean
}
abstract class ControllerDialogBase extends InitializingComponent {
protected _toDispose: vscode.Disposable[] = [];
protected modelBuilder!: azdata.ModelBuilder;
protected dialog: azdata.window.Dialog;
protected urlInputBox!: azdata.InputBoxComponent;
protected kubeConfigInputBox!: FilePicker;
protected clusterContextRadioGroup!: RadioOptionsGroup;
protected nameInputBox!: azdata.InputBoxComponent;
protected usernameInputBox!: azdata.InputBoxComponent;
protected passwordInputBox!: azdata.InputBoxComponent;
protected dispose(): void {
this._toDispose.forEach(disposable => disposable.dispose());
this._toDispose.length = 0; // clear the _toDispose array
}
protected getComponents(): (azdata.FormComponent<azdata.Component> & { layout?: azdata.FormItemLayout | undefined; })[] {
return [
{
component: this.urlInputBox,
title: loc.controllerUrl,
required: true
}, {
component: this.kubeConfigInputBox.component(),
title: loc.controllerKubeConfig,
required: true
}, {
component: this.clusterContextRadioGroup.component(),
title: loc.controllerClusterContext,
required: true
}, {
component: this.nameInputBox,
title: loc.controllerName,
@@ -70,7 +48,7 @@ abstract class ControllerDialogBase extends InitializingComponent {
}
protected abstract fieldToFocusOn(): azdata.Component;
protected readonlyFields(): IReadOnly[] { return []; }
protected readonlyFields(): azdata.InputBoxComponent[] { return []; }
protected initializeFields(controllerInfo: ControllerInfo | undefined, password: string | undefined) {
this.urlInputBox = this.modelBuilder.inputBox()
@@ -79,18 +57,6 @@ abstract class ControllerDialogBase extends InitializingComponent {
// If we have a model then we're editing an existing connection so don't let them modify the URL
readOnly: !!controllerInfo
}).component();
this.kubeConfigInputBox = new FilePicker(
this.modelBuilder,
controllerInfo?.kubeConfigFilePath || getDefaultKubeConfigPath(),
(disposable) => this._toDispose.push(disposable)
);
this.modelBuilder.inputBox()
.withProperties<azdata.InputBoxProperties>({
value: controllerInfo?.kubeConfigFilePath || getDefaultKubeConfigPath()
}).component();
this.clusterContextRadioGroup = new RadioOptionsGroup(this.modelBuilder, (disposable) => this._toDispose.push(disposable));
this.loadRadioGroup(controllerInfo?.kubeClusterContext);
this._toDispose.push(this.kubeConfigInputBox.onTextChanged(() => this.loadRadioGroup(controllerInfo?.kubeClusterContext)));
this.nameInputBox = this.modelBuilder.inputBox()
.withProperties<azdata.InputBoxProperties>({
value: controllerInfo?.name
@@ -115,20 +81,10 @@ abstract class ControllerDialogBase extends InitializingComponent {
this.dialog = azdata.window.createModelViewDialog(title);
}
private loadRadioGroup(previousClusterContext?: string): void {
this.clusterContextRadioGroup.load(async () => {
const clusters = await getKubeConfigClusterContexts(this.kubeConfigInputBox.value!);
return {
values: clusters.map(c => c.name),
defaultValue: getCurrentClusterContext(clusters, previousClusterContext, false),
};
});
}
public showDialog(controllerInfo?: ControllerInfo, password: string | undefined = undefined): azdata.window.Dialog {
this.id = controllerInfo?.id ?? uuid();
this.resources = controllerInfo?.resources ?? [];
this._toDispose.push(this.dialog.cancelButton.onClick(() => this.handleCancel()));
this.dialog.cancelButton.onClick(() => this.handleCancel());
this.dialog.registerContent(async (view) => {
this.modelBuilder = view.modelBuilder;
this.initializeFields(controllerInfo, password);
@@ -144,13 +100,7 @@ abstract class ControllerDialogBase extends InitializingComponent {
this.initialized = true;
});
this.dialog.registerCloseValidator(async () => {
const isValidated = await this.validate();
if (isValidated) {
this.dispose();
}
return isValidated;
});
this.dialog.registerCloseValidator(async () => await this.validate());
this.dialog.okButton.label = loc.connect;
this.dialog.cancelButton.label = loc.cancel;
azdata.window.openDialog(this.dialog);
@@ -166,19 +116,6 @@ abstract class ControllerDialogBase extends InitializingComponent {
public waitForClose(): Promise<ConnectToControllerDialogModel | undefined> {
return this.completionPromise.promise;
}
protected getControllerInfo(url: string, rememberPassword: boolean = false): ControllerInfo {
return {
id: this.id,
url: url,
kubeConfigFilePath: this.kubeConfigInputBox.value!,
kubeClusterContext: this.clusterContextRadioGroup.value!,
name: this.nameInputBox.value ?? '',
username: this.usernameInputBox.value!,
rememberPassword: rememberPassword,
resources: this.resources
};
}
}
export class ConnectToControllerDialog extends ControllerDialogBase {
@@ -227,7 +164,14 @@ export class ConnectToControllerDialog extends ControllerDialogBase {
if (!/.*:\d*$/.test(url)) {
url = `${url}:30080`;
}
const controllerInfo: ControllerInfo = this.getControllerInfo(url, !!this.rememberPwCheckBox.checked);
const controllerInfo: ControllerInfo = {
id: this.id,
url: url,
name: this.nameInputBox.value ?? '',
username: this.usernameInputBox.value,
rememberPassword: this.rememberPwCheckBox.checked ?? false,
resources: this.resources
};
const controllerModel = new ControllerModel(this.treeDataProvider, controllerInfo, this.passwordInputBox.value);
try {
// Validate that we can connect to the controller, this also populates the controllerRegistration from the connection response.
@@ -258,8 +202,6 @@ export class PasswordToControllerDialog extends ControllerDialogBase {
protected readonlyFields() {
return [
this.urlInputBox,
this.kubeConfigInputBox,
this.clusterContextRadioGroup,
this.nameInputBox,
this.usernameInputBox
];
@@ -287,7 +229,14 @@ export class PasswordToControllerDialog extends ControllerDialogBase {
return false;
}
}
const controllerInfo: ControllerInfo = this.getControllerInfo(this.urlInputBox.value!, false);
const controllerInfo: ControllerInfo = {
id: this.id,
url: this.urlInputBox.value!,
name: this.nameInputBox.value!,
username: this.usernameInputBox.value!,
rememberPassword: false,
resources: []
};
const controllerModel = new ControllerModel(this.treeDataProvider, controllerInfo, this.passwordInputBox.value);
this.completionPromise.resolve({ controllerModel: controllerModel, password: this.passwordInputBox.value });
return true;

View File

@@ -10,7 +10,7 @@ import { ControllerModel } from '../../models/controllerModel';
import { ControllerTreeNode } from './controllerTreeNode';
import { TreeNode } from './treeNode';
const mementoToken = 'arcDataControllers';
const mementoToken = 'arcControllers';
/**
* The TreeDataProvider for the Azure Arc view, which displays a list of registered

View File

@@ -5,7 +5,7 @@
import { MiaaResourceInfo, ResourceInfo, ResourceType } from 'arc';
import * as vscode from 'vscode';
import { UserCancelledError } from '../../common/api';
import { UserCancelledError } from '../../common/utils';
import * as loc from '../../localizedConstants';
import { ControllerModel, Registration } from '../../models/controllerModel';
import { MiaaModel } from '../../models/miaaModel';

View File

@@ -2,7 +2,7 @@
"name": "asde-deployment",
"displayName": "%extension-displayName%",
"description": "%extension-description%",
"version": "0.4.1",
"version": "0.4.0",
"publisher": "Microsoft",
"preview": true,
"license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/main/LICENSE.txt",

View File

@@ -2,14 +2,14 @@
"name": "azdata",
"displayName": "%azdata.displayName%",
"description": "%azdata.description%",
"version": "0.5.0",
"version": "0.4.1",
"publisher": "Microsoft",
"preview": true,
"license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/main/LICENSE.txt",
"icon": "images/extension.png",
"engines": {
"vscode": "*",
"azdata": ">=1.25.0"
"azdata": ">=1.23.0"
},
"activationEvents": [
"*"

View File

@@ -102,11 +102,10 @@ export function getAzdataApi(localAzdataDiscovered: Promise<IAzdataTool | undefi
replaceEngineSettings?: boolean;
workers?: number;
},
engineVersion?: string,
additionalEnvVars?: { [key: string]: string; }) => {
await localAzdataDiscovered;
throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento));
return azdataToolService.localAzdata.arc.postgres.server.edit(name, args, engineVersion, additionalEnvVars);
return azdataToolService.localAzdata.arc.postgres.server.edit(name, args, additionalEnvVars);
}
}
},

View File

@@ -118,7 +118,6 @@ export class AzdataTool implements azdataExt.IAzdataApi {
replaceEngineSettings?: boolean,
workers?: number
},
engineVersion?: string,
additionalEnvVars?: { [key: string]: string }): Promise<azdataExt.AzdataOutput<void>> => {
const argsArray = ['arc', 'postgres', 'server', 'edit', '-n', name];
if (args.adminPassword) { argsArray.push('--admin-password'); }
@@ -132,7 +131,6 @@ export class AzdataTool implements azdataExt.IAzdataApi {
if (args.port) { argsArray.push('--port', args.port.toString()); }
if (args.replaceEngineSettings) { argsArray.push('--replace-engine-settings'); }
if (args.workers) { argsArray.push('--workers', args.workers.toString()); }
if (engineVersion) { argsArray.push('--engine-version', engineVersion); }
return this.executeCommand<void>(argsArray, additionalEnvVars);
}
}

View File

@@ -65,7 +65,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<azdata
// register option source(s)
const rdApi = <rd.IExtension>vscode.extensions.getExtension(rd.extension.name)?.exports;
context.subscriptions.push(rdApi.registerOptionsSourceProvider(new ArcControllerConfigProfilesOptionsSource(azdataApi)));
rdApi.registerOptionsSourceProvider(new ArcControllerConfigProfilesOptionsSource(azdataApi));
return azdataApi;
}

View File

@@ -10,7 +10,7 @@ import * as azdataExt from 'azdata-ext';
* Class that provides options sources for an Arc Data Controller
*/
export class ArcControllerConfigProfilesOptionsSource implements rd.IOptionsSourceProvider {
readonly id = 'arc.controller.config.profiles';
readonly optionsSourceId = 'arc.controller.config.profiles';
constructor(private _azdataExtApi: azdataExt.IExtension) { }
async getOptions(): Promise<string[]> {
const isEulaAccepted = await this._azdataExtApi.isEulaAccepted();

View File

@@ -262,7 +262,6 @@ declare module 'azdata-ext' {
replaceEngineSettings?: boolean,
workers?: number
},
engineVersion?: string,
additionalEnvVars?: { [key: string]: string }): Promise<AzdataOutput<void>>
}
},

View File

@@ -8,7 +8,6 @@ import * as vscode from 'vscode';
import { promises as fs } from 'fs';
import * as path from 'path';
import * as os from 'os';
import * as resourceDeployment from 'resource-deployment';
import { AppContext } from './appContext';
import { AzureAccountProviderService } from './account-provider/azureAccountProviderService';
@@ -87,8 +86,8 @@ export async function activate(context: vscode.ExtensionContext): Promise<azurec
registerAzureServices(appContext);
const azureResourceTree = new AzureResourceTreeProvider(appContext);
const connectionDialogTree = new ConnectionDialogTreeProvider(appContext);
pushDisposable(vscode.window.registerTreeDataProvider('azureResourceExplorer', azureResourceTree));
pushDisposable(vscode.window.registerTreeDataProvider('connectionDialog/azureResourceExplorer', connectionDialogTree));
pushDisposable(vscode.window.registerTreeDataProvider('azureResourceExplorer', azureResourceTree));
pushDisposable(vscode.workspace.onDidChangeConfiguration(e => onDidChangeConfiguration(e), this));
registerAzureResourceCommands(appContext, azureResourceTree, connectionDialogTree);
azdata.dataprotocol.registerDataGridProvider(new AzureDataGridProvider(appContext));
@@ -106,40 +105,6 @@ export async function activate(context: vscode.ExtensionContext): Promise<azurec
}
});
// Don't block on this since there's a bit of a circular dependency here with the extension activation since resource deployment
// depends on this extension too. It's fine to wait a bit for that to finish before registering the provider
vscode.extensions.getExtension(resourceDeployment.extension.name).activate().then((api: resourceDeployment.IExtension) => {
context.subscriptions.push(api.registerValueProvider({
id: 'subscription-id-to-tenant-id',
getValue: async (triggerValue: string) => {
if (triggerValue === '') {
return '';
}
let accounts: azdata.Account[] = [];
try {
accounts = await azdata.accounts.getAllAccounts();
} catch (err) {
console.warn(`Error fetching accounts for subscription-id-to-tenant-id provider : ${err}`);
return '';
}
for (const account of accounts) {
// Ignore any errors - they'll be logged in the called function and we still want to look
// at any subscriptions that are returned - worst case we'll just return an empty string if we didn't
// find the matching subscription
const subs = await azureResourceUtils.getSubscriptions(appContext, account, true);
const sub = subs.subscriptions.find(sub => sub.id === triggerValue);
if (sub) {
return sub.tenant;
}
}
console.error(`Unable to find subscription with ID ${triggerValue} when mapping subscription ID to tenant ID`);
return '';
}
}));
});
return {
getSubscriptions(account?: azdata.Account, ignoreErrors?: boolean, selectedOnly: boolean = false): Thenable<azurecore.GetSubscriptionsResult> {
return selectedOnly

View File

@@ -7,5 +7,4 @@
/// <reference path='../../../../src/vs/vscode.proposed.d.ts'/>
/// <reference path='../../../../src/sql/azdata.d.ts'/>
/// <reference path='../../../../src/sql/azdata.proposed.d.ts'/>
/// <reference path='../../../resource-deployment/src/typings/resource-deployment.d.ts'/>
/// <reference types='@types/node'/>

View File

@@ -0,0 +1,78 @@
{
"metadata": {
"kernelspec": {
"name": "python3",
"display_name": "Python 3"
},
"language_info": {
"name": "python",
"version": "3.6.6",
"mimetype": "text/x-python",
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"pygments_lexer": "ipython3",
"nbconvert_exporter": "python",
"file_extension": ".py"
}
},
"nbformat_minor": 2,
"nbformat": 4,
"cells": [
{
"cell_type": "code",
"source": [
"import sys,os,getpass,json,html,time\r\n",
"from string import Template"
],
"metadata": {
"azdata_cell_guid": "1887c716-6e0c-41d1-9d67-cfa93884c0d6"
},
"outputs": [],
"execution_count": 1
},
{
"cell_type": "code",
"source": [
"sql_password = \"\"\r\n",
"sql_port = \"\""
],
"metadata": {
"azdata_cell_guid": "f3de6ea8-1ea8-43d6-9277-836b57d85845"
},
"outputs": [],
"execution_count": 2
},
{
"cell_type": "code",
"source": [
"from IPython.display import *\r\n",
"connectionParameter = '{\"serverName\":\"localhost,' + sql_port + '\",\"providerName\":\"MSSQL\",\"authenticationType\":\"SqlLogin\",\"userName\":\"sa\",\"password\":' + json.dumps(sql_password) + '}'\r\n",
"display(HTML('<br/><a href=\"command:azdata.connect?' + html.escape(connectionParameter)+'\"><font size=\"3\">Click here to connect to SQL Server</font></a><br/>'))\r\n",
"display(HTML('<br/><span style=\"color:red\"><font size=\"2\">NOTE: The SQL Server password is included in this link, you may want to clear the results of this code cell before saving the notebook.</font></span>'))"
],
"metadata": {
"azdata_cell_guid": "8e5e0b41-a27d-4a73-9ba6-c0d3bd7a9a2f"
},
"outputs": [
{
"data": {
"text/plain": "<IPython.core.display.HTML object>",
"text/html": "<br/><a href=\"command:azdata.connect?{&quot;serverName&quot;:&quot;localhost,&quot;,&quot;providerName&quot;:&quot;MSSQL&quot;,&quot;authenticationType&quot;:&quot;SqlLogin&quot;,&quot;userName&quot;:&quot;sa&quot;,&quot;password&quot;:&quot;&quot;}\"><font size=\"3\">Click here to connect to SQL Server</font></a><br/>"
},
"metadata": {},
"output_type": "display_data"
}, {
"data": {
"text/plain": "<IPython.core.display.HTML object>",
"text/html": "<br/><span style=\"color:red\"><font size=\"2\">NOTE: The SQL Server password is included in this link, you may want to clear the results of this code cell before saving the notebook.</font></span>"
},
"metadata": {},
"output_type": "display_data"
}
],
"execution_count": 3
}
]
}

View File

@@ -86,7 +86,6 @@
}
},
"dependencies": {
"ads-extension-telemetry": "^1.0.0",
"htmlparser2": "^3.10.1",
"vscode-nls": "^4.0.0"
},

View File

@@ -1,18 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import AdsTelemetryReporter from 'ads-extension-telemetry';
import * as Utils from './utils';
const packageJson = require('../package.json');
let packageInfo = Utils.getPackageInfo(packageJson);
export const TelemetryReporter = new AdsTelemetryReporter(packageInfo.name, packageInfo.version, packageInfo.aiKey);
export enum TelemetryViews {
SelectOperationPage = 'SelectOperationPage'
}

View File

@@ -1,35 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export interface IPackageInfo {
name: string;
version: string;
aiKey: string;
}
export function getPackageInfo(packageJson: any): IPackageInfo | undefined {
if (packageJson) {
return {
name: packageJson.name,
version: packageJson.version,
aiKey: packageJson.aiKey
};
}
return undefined;
}
/**
* Map an error message into a short name for the type of error.
* @param msg The error message to map
*/
export function getTelemetryErrorType(msg: string): string {
if (msg && msg.indexOf('Object reference not set to an instance of an object') !== -1) {
return 'ObjectReferenceNotSet';
}
else {
return 'Other';
}
}

View File

@@ -240,13 +240,6 @@
resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.1.tgz#681df970358c82836b42f989188d133e218c458e"
integrity sha512-yYezQwGWty8ziyYLdZjwxyMb0CZR49h8JALHGrxjQHWlqGgc8kLdHEgWrgL0uZ29DMvEVBDnHU2Wg36zKSIUtA==
ads-extension-telemetry@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/ads-extension-telemetry/-/ads-extension-telemetry-1.0.0.tgz#840b363a6ad958447819b9bc59fdad3e49de31a9"
integrity sha512-ouxZVECe4tsO0ek0dLdnAZEz1Lrytv1uLbbGZhRbZsHITsUYNjnkKnA471uWh0Dj80s+orvv49/j3/tNBDP/SQ==
dependencies:
vscode-extension-telemetry "0.1.2"
ansi-regex@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998"
@@ -266,31 +259,6 @@ append-transform@^2.0.0:
dependencies:
default-require-extensions "^3.0.0"
applicationinsights@1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-1.4.0.tgz#e17e436427b6e273291055181e29832cca978644"
integrity sha512-TV8MYb0Kw9uE2cdu4V/UvTKdOABkX2+Fga9iDz0zqV7FLrNXfmAugWZmmdTx4JoynYkln3d5CUHY3oVSUEbfFw==
dependencies:
cls-hooked "^4.2.2"
continuation-local-storage "^3.2.1"
diagnostic-channel "0.2.0"
diagnostic-channel-publishers "^0.3.2"
async-hook-jl@^1.7.6:
version "1.7.6"
resolved "https://registry.yarnpkg.com/async-hook-jl/-/async-hook-jl-1.7.6.tgz#4fd25c2f864dbaf279c610d73bf97b1b28595e68"
integrity sha512-gFaHkFfSxTjvoxDMYqDuGHlcRyUuamF8s+ZTtJdDzqjws4mCt7v0vuV79/E2Wr2/riMQgtG4/yUtXWs1gZ7JMg==
dependencies:
stack-chain "^1.3.7"
async-listener@^0.6.0:
version "0.6.10"
resolved "https://registry.yarnpkg.com/async-listener/-/async-listener-0.6.10.tgz#a7c97abe570ba602d782273c0de60a51e3e17cbc"
integrity sha512-gpuo6xOyF4D5DE5WvyqZdPA3NGhiT6Qf07l7DCB0wwDEsLvDIbCr6j9S5aj5Ch96dLace5tXVzWBZkxU/c5ohw==
dependencies:
semver "^5.3.0"
shimmer "^1.1.0"
balanced-match@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
@@ -333,15 +301,6 @@ circular-json@^0.3.1:
resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.3.tgz#815c99ea84f6809529d2f45791bdf82711352d66"
integrity sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==
cls-hooked@^4.2.2:
version "4.2.2"
resolved "https://registry.yarnpkg.com/cls-hooked/-/cls-hooked-4.2.2.tgz#ad2e9a4092680cdaffeb2d3551da0e225eae1908"
integrity sha512-J4Xj5f5wq/4jAvcdgoGsL3G103BtWpZrMo8NEinRltN+xpTZdI+M38pyQqhuFU/P792xkMFvnKSf+Lm81U1bxw==
dependencies:
async-hook-jl "^1.7.6"
emitter-listener "^1.0.1"
semver "^5.4.1"
color-convert@^1.9.0:
version "1.9.3"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
@@ -364,14 +323,6 @@ concat-map@0.0.1:
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
continuation-local-storage@^3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/continuation-local-storage/-/continuation-local-storage-3.2.1.tgz#11f613f74e914fe9b34c92ad2d28fe6ae1db7ffb"
integrity sha512-jx44cconVqkCEEyLSKWwkvUXwO561jXMa3LPjTPsm5QR22PA0/mhe33FT4Xb5y74JDvt/Cq+5lm8S8rskLv9ZA==
dependencies:
async-listener "^0.6.0"
emitter-listener "^1.1.1"
convert-source-map@^1.7.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442"
@@ -426,18 +377,6 @@ default-require-extensions@^3.0.0:
dependencies:
strip-bom "^4.0.0"
diagnostic-channel-publishers@^0.3.2:
version "0.3.5"
resolved "https://registry.yarnpkg.com/diagnostic-channel-publishers/-/diagnostic-channel-publishers-0.3.5.tgz#a84a05fd6cc1d7619fdd17791c17e540119a7536"
integrity sha512-AOIjw4T7Nxl0G2BoBPhkQ6i7T4bUd9+xvdYizwvG7vVAM1dvr+SDrcUudlmzwH0kbEwdR2V1EcnKT0wAeYLQNQ==
diagnostic-channel@0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/diagnostic-channel/-/diagnostic-channel-0.2.0.tgz#cc99af9612c23fb1fff13612c72f2cbfaa8d5a17"
integrity sha1-zJmvlhLCP7H/8TYSxy8sv6qNWhc=
dependencies:
semver "^5.3.0"
diff@3.5.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
@@ -476,13 +415,6 @@ domutils@^1.5.1:
dom-serializer "0"
domelementtype "1"
emitter-listener@^1.0.1, emitter-listener@^1.1.1:
version "1.1.2"
resolved "https://registry.yarnpkg.com/emitter-listener/-/emitter-listener-1.1.2.tgz#56b140e8f6992375b3d7cb2cab1cc7432d9632e8"
integrity sha512-Bt1sBAGFHY9DKY+4/2cV6izcKJUf5T7/gkdmkxzX/qv9CcGH8xSwVRW5mtX03SWJtRTWSOpzCuWN9rBFYZepZQ==
dependencies:
shimmer "^1.2.0"
entities@^1.1.1:
version "1.1.2"
resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56"
@@ -861,7 +793,7 @@ safe-buffer@~5.1.0, safe-buffer@~5.1.1:
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
semver@^5.3.0, semver@^5.4.1, semver@^5.6.0:
semver@^5.4.1, semver@^5.6.0:
version "5.7.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
@@ -871,11 +803,6 @@ semver@^6.0.0, semver@^6.3.0:
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
shimmer@^1.1.0, shimmer@^1.2.0:
version "1.2.1"
resolved "https://registry.yarnpkg.com/shimmer/-/shimmer-1.2.1.tgz#610859f7de327b587efebf501fb43117f9aff337"
integrity sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==
should-equal@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/should-equal/-/should-equal-2.0.0.tgz#6072cf83047360867e68e98b09d71143d04ee0c3"
@@ -943,11 +870,6 @@ source-map@^0.6.1:
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
stack-chain@^1.3.7:
version "1.3.7"
resolved "https://registry.yarnpkg.com/stack-chain/-/stack-chain-1.3.7.tgz#d192c9ff4ea6a22c94c4dd459171e3f00cea1285"
integrity sha1-0ZLJ/06moiyUxN1FkXHj8AzqEoU=
string_decoder@^1.1.1:
version "1.2.0"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.2.0.tgz#fe86e738b19544afe70469243b2a1ee9240eae8d"
@@ -1012,13 +934,6 @@ util-deprecate@^1.0.1:
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
vscode-extension-telemetry@0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.1.2.tgz#049207f5453930888ff68ca925b07bab08f2c955"
integrity sha512-FSbaZKlIH3VKvBJsKw7v5bESWHXzltji2rtjaJeJglpQH4tfClzwHMzlMXUZGiblV++djEzb1gW8mb5E+wxFsg==
dependencies:
applicationinsights "1.4.0"
vscode-nls@^4.0.0:
version "4.1.1"
resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.1.1.tgz#f9916b64e4947b20322defb1e676a495861f133c"

View File

@@ -138,7 +138,6 @@
]
},
"dependencies": {
"ads-extension-telemetry": "^1.0.0",
"vscode-nls": "^4.0.0"
},
"devDependencies": {
@@ -147,9 +146,9 @@
"mocha": "^5.2.0",
"mocha-junit-reporter": "^1.17.0",
"mocha-multi-reporters": "^1.1.7",
"should": "^13.2.3",
"sinon": "^9.0.2",
"typemoq": "^2.1.0",
"vscodetestcover": "^1.1.0"
"vscodetestcover": "^1.1.0",
"should": "^13.2.3",
"sinon": "^9.0.2"
}
}

View File

@@ -27,9 +27,4 @@ export class DataWorkspaceExtension implements IExtension {
get defaultProjectSaveLocation(): vscode.Uri | undefined {
return defaultProjectSaveLocation();
}
validateWorkspace(): Promise<boolean> {
return this.workspaceService.validateWorkspace();
}
}

View File

@@ -1,17 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import AdsTelemetryReporter from 'ads-extension-telemetry';
import * as Utils from './utils';
const packageJson = require('../package.json');
let packageInfo = Utils.getPackageInfo(packageJson)!;
export const TelemetryReporter = new AdsTelemetryReporter(packageInfo.name, packageInfo.version, packageInfo.aiKey);
export enum TelemetryViews {
}

View File

@@ -29,21 +29,3 @@ async function getFileStatus(path: string): Promise<fs.Stats | undefined> {
}
}
}
export interface IPackageInfo {
name: string;
version: string;
aiKey: string;
}
export function getPackageInfo(packageJson: any): IPackageInfo | undefined {
if (packageJson) {
return {
name: packageJson.name,
version: packageJson.version,
aiKey: packageJson.aiKey
};
}
return undefined;
}

View File

@@ -22,7 +22,7 @@ declare module 'dataworkspace' {
* Add projects to the workspace
* @param projectFiles Uris of project files to add
*/
addProjectsToWorkspace(projectFiles: vscode.Uri[]): Promise<void>;
addProjectsToWorkspace(projectFiles: vscode.Uri[]): Promise<void>
/**
* Change focus to Projects view
@@ -33,11 +33,6 @@ declare module 'dataworkspace' {
* Returns the default location to save projects
*/
defaultProjectSaveLocation: vscode.Uri | undefined;
/**
* Verifies that a workspace is open or if it should be automatically created
*/
validateWorkspace(): Promise<boolean>;
}
/**

View File

@@ -235,13 +235,6 @@
resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.1.tgz#681df970358c82836b42f989188d133e218c458e"
integrity sha512-yYezQwGWty8ziyYLdZjwxyMb0CZR49h8JALHGrxjQHWlqGgc8kLdHEgWrgL0uZ29DMvEVBDnHU2Wg36zKSIUtA==
ads-extension-telemetry@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/ads-extension-telemetry/-/ads-extension-telemetry-1.0.0.tgz#840b363a6ad958447819b9bc59fdad3e49de31a9"
integrity sha512-ouxZVECe4tsO0ek0dLdnAZEz1Lrytv1uLbbGZhRbZsHITsUYNjnkKnA471uWh0Dj80s+orvv49/j3/tNBDP/SQ==
dependencies:
vscode-extension-telemetry "0.1.2"
ansi-regex@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998"
@@ -261,31 +254,6 @@ append-transform@^2.0.0:
dependencies:
default-require-extensions "^3.0.0"
applicationinsights@1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-1.4.0.tgz#e17e436427b6e273291055181e29832cca978644"
integrity sha512-TV8MYb0Kw9uE2cdu4V/UvTKdOABkX2+Fga9iDz0zqV7FLrNXfmAugWZmmdTx4JoynYkln3d5CUHY3oVSUEbfFw==
dependencies:
cls-hooked "^4.2.2"
continuation-local-storage "^3.2.1"
diagnostic-channel "0.2.0"
diagnostic-channel-publishers "^0.3.2"
async-hook-jl@^1.7.6:
version "1.7.6"
resolved "https://registry.yarnpkg.com/async-hook-jl/-/async-hook-jl-1.7.6.tgz#4fd25c2f864dbaf279c610d73bf97b1b28595e68"
integrity sha512-gFaHkFfSxTjvoxDMYqDuGHlcRyUuamF8s+ZTtJdDzqjws4mCt7v0vuV79/E2Wr2/riMQgtG4/yUtXWs1gZ7JMg==
dependencies:
stack-chain "^1.3.7"
async-listener@^0.6.0:
version "0.6.10"
resolved "https://registry.yarnpkg.com/async-listener/-/async-listener-0.6.10.tgz#a7c97abe570ba602d782273c0de60a51e3e17cbc"
integrity sha512-gpuo6xOyF4D5DE5WvyqZdPA3NGhiT6Qf07l7DCB0wwDEsLvDIbCr6j9S5aj5Ch96dLace5tXVzWBZkxU/c5ohw==
dependencies:
semver "^5.3.0"
shimmer "^1.1.0"
balanced-match@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
@@ -328,15 +296,6 @@ circular-json@^0.3.1:
resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.3.tgz#815c99ea84f6809529d2f45791bdf82711352d66"
integrity sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==
cls-hooked@^4.2.2:
version "4.2.2"
resolved "https://registry.yarnpkg.com/cls-hooked/-/cls-hooked-4.2.2.tgz#ad2e9a4092680cdaffeb2d3551da0e225eae1908"
integrity sha512-J4Xj5f5wq/4jAvcdgoGsL3G103BtWpZrMo8NEinRltN+xpTZdI+M38pyQqhuFU/P792xkMFvnKSf+Lm81U1bxw==
dependencies:
async-hook-jl "^1.7.6"
emitter-listener "^1.0.1"
semver "^5.4.1"
color-convert@^1.9.0:
version "1.9.3"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
@@ -359,14 +318,6 @@ concat-map@0.0.1:
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
continuation-local-storage@^3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/continuation-local-storage/-/continuation-local-storage-3.2.1.tgz#11f613f74e914fe9b34c92ad2d28fe6ae1db7ffb"
integrity sha512-jx44cconVqkCEEyLSKWwkvUXwO561jXMa3LPjTPsm5QR22PA0/mhe33FT4Xb5y74JDvt/Cq+5lm8S8rskLv9ZA==
dependencies:
async-listener "^0.6.0"
emitter-listener "^1.1.1"
convert-source-map@^1.7.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442"
@@ -421,18 +372,6 @@ default-require-extensions@^3.0.0:
dependencies:
strip-bom "^4.0.0"
diagnostic-channel-publishers@^0.3.2:
version "0.3.5"
resolved "https://registry.yarnpkg.com/diagnostic-channel-publishers/-/diagnostic-channel-publishers-0.3.5.tgz#a84a05fd6cc1d7619fdd17791c17e540119a7536"
integrity sha512-AOIjw4T7Nxl0G2BoBPhkQ6i7T4bUd9+xvdYizwvG7vVAM1dvr+SDrcUudlmzwH0kbEwdR2V1EcnKT0wAeYLQNQ==
diagnostic-channel@0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/diagnostic-channel/-/diagnostic-channel-0.2.0.tgz#cc99af9612c23fb1fff13612c72f2cbfaa8d5a17"
integrity sha1-zJmvlhLCP7H/8TYSxy8sv6qNWhc=
dependencies:
semver "^5.3.0"
diff@3.5.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
@@ -443,13 +382,6 @@ diff@^4.0.2:
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
emitter-listener@^1.0.1, emitter-listener@^1.1.1:
version "1.1.2"
resolved "https://registry.yarnpkg.com/emitter-listener/-/emitter-listener-1.1.2.tgz#56b140e8f6992375b3d7cb2cab1cc7432d9632e8"
integrity sha512-Bt1sBAGFHY9DKY+4/2cV6izcKJUf5T7/gkdmkxzX/qv9CcGH8xSwVRW5mtX03SWJtRTWSOpzCuWN9rBFYZepZQ==
dependencies:
shimmer "^1.2.0"
escape-string-regexp@1.0.5, escape-string-regexp@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
@@ -802,7 +734,7 @@ safe-buffer@~5.1.1:
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
semver@^5.3.0, semver@^5.4.1, semver@^5.6.0:
semver@^5.4.1, semver@^5.6.0:
version "5.7.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
@@ -812,11 +744,6 @@ semver@^6.0.0, semver@^6.3.0:
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
shimmer@^1.1.0, shimmer@^1.2.0:
version "1.2.1"
resolved "https://registry.yarnpkg.com/shimmer/-/shimmer-1.2.1.tgz#610859f7de327b587efebf501fb43117f9aff337"
integrity sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==
should-equal@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/should-equal/-/should-equal-2.0.0.tgz#6072cf83047360867e68e98b09d71143d04ee0c3"
@@ -884,11 +811,6 @@ source-map@^0.6.1:
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
stack-chain@^1.3.7:
version "1.3.7"
resolved "https://registry.yarnpkg.com/stack-chain/-/stack-chain-1.3.7.tgz#d192c9ff4ea6a22c94c4dd459171e3f00cea1285"
integrity sha1-0ZLJ/06moiyUxN1FkXHj8AzqEoU=
strip-ansi@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f"
@@ -941,13 +863,6 @@ typemoq@^2.1.0:
lodash "^4.17.4"
postinstall-build "^5.0.1"
vscode-extension-telemetry@0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.1.2.tgz#049207f5453930888ff68ca925b07bab08f2c955"
integrity sha512-FSbaZKlIH3VKvBJsKw7v5bESWHXzltji2rtjaJeJglpQH4tfClzwHMzlMXUZGiblV++djEzb1gW8mb5E+wxFsg==
dependencies:
applicationinsights "1.4.0"
vscode-nls@^4.0.0:
version "4.1.1"
resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.1.1.tgz#f9916b64e4947b20322defb1e676a495861f133c"

View File

@@ -16,7 +16,6 @@ export const developers: string[] = [
'ktech99',
'kburtram',
'lucyzhang929',
'saiavishkarsreerama',
'smartguest',
'udeeshagautam',
'VasuBhog'

View File

@@ -17,21 +17,19 @@ import { promisify } from 'util';
const retryCount = 24; // 2 minutes
const dacpac1: string = path.join(__dirname, '../../testData/Database1.dacpac');
suite('Dacpac integration test suite @DacFx@', () => {
suite('Dacpac integration test suite', () => {
suiteSetup(async function () {
await utils.sleep(5000); // To ensure the providers are registered.
console.log(`Start dacpac tests`);
});
test('Deploy and extract dacpac @UNSTABLE@', async function () {
this.timeout(5 * 60 * 1000);
const server = await getStandaloneServer();
const connectionId = await utils.connectToServer(server);
assert(connectionId, `Failed to connect to "${server.serverName}"`);
await utils.connectToServer(server);
const ownerUri = await azdata.connection.getUriForConnection(connectionId);
const nodes = <azdata.objectexplorer.ObjectExplorerNode[]>await azdata.objectexplorer.getActiveConnectionNodes();
const index = nodes.findIndex(node => node.nodePath.includes(server.serverName));
const ownerUri = await azdata.connection.getUriForConnection(nodes[index].connectionId);
const now = new Date();
const databaseName = 'ADS_deployDacpac_' + now.getTime().toString();
@@ -72,13 +70,12 @@ suite('Dacpac integration test suite @DacFx@', () => {
const bacpac1: string = path.join(__dirname, '..', '..', 'testData', 'Database1.bacpac');
test('Import and export bacpac @UNSTABLE@', async function () {
this.timeout(5 * 60 * 1000);
const server = await getStandaloneServer();
await utils.connectToServer(server);
const connectionId = await utils.connectToServer(server);
assert(connectionId, `Failed to connect to "${server.serverName}"`);
const ownerUri = await azdata.connection.getUriForConnection(connectionId);
const nodes = <azdata.objectexplorer.ObjectExplorerNode[]>await azdata.objectexplorer.getActiveConnectionNodes();
const index = nodes.findIndex(node => node.nodePath.includes(server.serverName));
const ownerUri = await azdata.connection.getUriForConnection(nodes[index].connectionId);
const now = new Date();
const databaseName = 'ADS_importBacpac_' + now.getTime().toString();

View File

@@ -12,7 +12,7 @@ import * as os from 'os';
import * as fs from 'fs';
import * as path from 'path';
import * as assert from 'assert';
import { getStandaloneServer, TestServerProfile } from './testConfig';
import { getStandaloneServer } from './testConfig';
import { promisify } from 'util';
let schemaCompareService: mssql.ISchemaCompareService;
@@ -25,7 +25,7 @@ const SERVER_CONNECTION_TIMEOUT: number = 3000;
const retryCount = 24; // 2 minutes
const folderPath = path.join(os.tmpdir(), 'SchemaCompareTest');
suite('Schema compare integration test suite @DacFx@', () => {
suite('Schema compare integration test suite', () => {
suiteSetup(async function () {
let attempts: number = 20;
while (attempts > 0) {
@@ -40,7 +40,6 @@ suite('Schema compare integration test suite @DacFx@', () => {
console.log(`Start schema compare tests`);
});
test('Schema compare dacpac to dacpac comparison and scmp @UNSTABLE@', async function () {
this.timeout(5 * 60 * 1000);
assert(schemaCompareService, 'Schema Compare Service Provider is not available');
const now = new Date();
const operationId = 'testOperationId_' + now.getTime().toString();
@@ -83,9 +82,16 @@ suite('Schema compare integration test suite @DacFx@', () => {
assert(openScmpResult.targetEndpointInfo.packageFilePath === target.packageFilePath, `Expected: target packageFilePath to be ${target.packageFilePath}, Actual: ${openScmpResult.targetEndpointInfo.packageFilePath}`);
});
test('Schema compare database to database comparison, script generation, and scmp @UNSTABLE@', async function () {
this.timeout(5 * 60 * 1000);
let server = await getStandaloneServer();
const ownerUri = await getConnectionUri(server);
await utils.connectToServer(server, SERVER_CONNECTION_TIMEOUT);
let nodes = <azdata.objectexplorer.ObjectExplorerNode[]>await azdata.objectexplorer.getActiveConnectionNodes();
assert(nodes.length > 0, `Expecting at least one active connection, actual: ${nodes.length}`);
let index = nodes.findIndex(node => node.nodePath.includes(server.serverName));
assert(index !== -1, `Failed to find server: "${server.serverName}" in OE tree`);
const ownerUri = await azdata.connection.getUriForConnection(nodes[index].connectionId);
const now = new Date();
const operationId = 'testOperationId_' + now.getTime().toString();
@@ -155,9 +161,16 @@ suite('Schema compare integration test suite @DacFx@', () => {
}
});
test('Schema compare dacpac to database comparison, script generation, and scmp @UNSTABLE@', async function () {
this.timeout(5 * 60 * 1000);
let server = await getStandaloneServer();
const ownerUri = await getConnectionUri(server);
await utils.connectToServer(server, SERVER_CONNECTION_TIMEOUT);
let nodes = <azdata.objectexplorer.ObjectExplorerNode[]>await azdata.objectexplorer.getActiveConnectionNodes();
assert(nodes.length > 0, `Expecting at least one active connection, actual: ${nodes.length}`);
let index = nodes.findIndex(node => node.nodePath.includes(server.serverName));
assert(index !== -1, `Failed to find server: "${server.serverName}" in OE tree`);
const ownerUri = await azdata.connection.getUriForConnection(nodes[index].connectionId);
const now = new Date();
const operationId = 'testOperationId_' + now.getTime().toString();
const targetDB: string = 'ads_schemaCompare_targetDB_' + now.getTime().toString();
@@ -215,7 +228,6 @@ suite('Schema compare integration test suite @DacFx@', () => {
}
});
test('Schema compare dacpac to dacpac comparison with include exclude @UNSTABLE@', async function () {
this.timeout(5 * 60 * 1000);
assert(schemaCompareService, 'Schema Compare Service Provider is not available');
const operationId = 'testOperationId_' + new Date().getTime().toString();
@@ -272,17 +284,6 @@ suite('Schema compare integration test suite @DacFx@', () => {
});
});
async function getConnectionUri(server: TestServerProfile): Promise<string> {
// Connext to server
let connectionId = await utils.connectToServer(server, SERVER_CONNECTION_TIMEOUT);
assert(connectionId, `Failed to connect to "${server.serverName}"`);
// Get connection uri
const ownerUri = await azdata.connection.getUriForConnection(connectionId);
return ownerUri;
}
function assertIncludeExcludeResult(result: mssql.SchemaCompareIncludeExcludeResult, expectedSuccess: boolean, expectedBlockingDependenciesLength: number, expectedAffectedDependenciesLength: number): void {
assert(result.success === expectedSuccess, `Operation success should have been ${expectedSuccess}. Actual: ${result.success}`);
if (result.blockingDependencies) {

View File

@@ -35,15 +35,9 @@ export async function connectToServer(connectionInfo: TestConnectionInfo, timeou
options: {}
};
await ensureConnectionViewOpened();
let result = <azdata.ConnectionResult>await azdata.connection.connect(connectionProfile);
assert(result.connected, `Failed to connect to "${connectionProfile.serverName}", error code: ${result.errorCode}, error message: ${result.errorMessage}`);
// Try connecting 3 times
let result = await retryFunction(
async () => {
let connection = <azdata.ConnectionResult>await azdata.connection.connect(connectionProfile);
assert(connection?.connected, `Failed to connect to "${connectionProfile.serverName}", error code: ${connection.errorCode}, error message: ${connection.errorMessage}`);
return connection;
}, 3);
//workaround
//wait for OE to load
await pollTimeout(async () => {
@@ -186,26 +180,6 @@ export async function runQuery(query: string, ownerUri: string): Promise<azdata.
}
export async function retryFunction<T>(fn: () => Promise<T>, retryCount: number): Promise<T> {
let attempts: number = 1;
while (attempts <= retryCount) {
try {
return await fn();
}
catch (e) {
console.error(`utils.retryFunction: Attempt #${attempts} from ${retryCount} failed. Error: ${e}`);
if (attempts === retryCount) {
throw e;
}
}
await sleep(10000);
attempts++;
}
throw new Error(`utils.retryFunction: Failed after ${attempts} attempts`);
}
export async function assertThrowsAsync(fn: () => Promise<any>, msg: string): Promise<void> {
let f = () => {
// Empty

View File

@@ -1,6 +1,6 @@
{
"downloadUrl": "https://github.com/Microsoft/sqltoolsservice/releases/download/v{#version#}/microsoft.sqltools.servicelayer-{#fileName#}",
"version": "3.0.0-release.60",
"version": "3.0.0-release.52",
"downloadFileNames": {
"Windows_86": "win-x86-netcoreapp3.1.zip",
"Windows_64": "win-x64-netcoreapp3.1.zip",

View File

@@ -1,6 +1,6 @@
{
"name": "kusto",
"version": "0.4.0",
"version": "0.3.2",
"publisher": "Microsoft",
"aiKey": "AIF-444c3af9-8e69-4462-ab49-4191e6ad1916",
"activationEvents": [

View File

@@ -60,7 +60,7 @@ export class GuestSessionManager {
connectionOptions['serverName'] = documentState.serverName;
connectionOptions['databaseName'] = documentState.databaseName;
connectionOptions['userName'] = 'liveshare';
connectionOptions['password'] = 'liveshare'; // [SuppressMessage("Microsoft.Security", "CS001:SecretInline", Justification="Stub value for testing")]
connectionOptions['password'] = 'liveshare';
connectionOptions['authenticationType'] = 'liveshare';
connectionOptions['savePassword'] = false;
connectionOptions['saveProfile'] = false;

View File

@@ -40,7 +40,7 @@ export class StatusProvider {
connectionOptions['serverName'] = args.profile.options['server'];
connectionOptions['databaseName'] = args.profile.options['database'];
connectionOptions['userName'] = 'liveshare';
connectionOptions['password'] = 'liveshare'; // [SuppressMessage("Microsoft.Security", "CS001:SecretInline", Justification="Stub value for testing")]
connectionOptions['password'] = 'liveshare';
connectionOptions['authenticationType'] = 'liveshare';
connectionOptions['savePassword'] = false;
connectionOptions['saveProfile'] = false;

View File

@@ -2,7 +2,7 @@
"name": "machine-learning",
"displayName": "%displayName%",
"description": "%description%",
"version": "0.6.0",
"version": "0.5.0",
"publisher": "Microsoft",
"preview": true,
"engines": {

View File

@@ -86,7 +86,7 @@ export function createViewContext(): ViewTestContext {
withProps: () => checkBoxBuilder,
withValidation: () => checkBoxBuilder
};
let inputBox: () => azdata.InputBoxComponent = () => Object.assign(<azdata.InputBoxComponent>Object.assign({}, componentBase), {
let inputBox: () => azdata.InputBoxComponent = () => Object.assign({}, componentBase, {
onTextChanged: onClick.event!,
onEnterKeyPressed: undefined!,
value: ''

View File

@@ -325,7 +325,7 @@
"watch-web": "npx webpack-cli --config extension-browser.webpack.config --mode none --watch --info-verbosity verbose"
},
"dependencies": {
"highlight.js": "10.4.1",
"highlight.js": "9.15.10",
"markdown-it": "^10.0.0",
"markdown-it-front-matter": "^0.2.1",
"vscode-extension-telemetry": "0.1.1",

View File

@@ -2131,10 +2131,10 @@ he@1.1.1:
resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd"
integrity sha1-k0EP0hsAlzUVH4howvJx80J+I/0=
highlight.js@10.4.1:
version "10.4.1"
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.4.1.tgz#d48fbcf4a9971c4361b3f95f302747afe19dbad0"
integrity sha512-yR5lWvNz7c85OhVAEAeFhVCc/GV4C30Fjzc/rCP0aCWzc1UUOPUk55dK/qdwTZHBvMZo+eZ2jpk62ndX/xMFlg==
highlight.js@9.15.10:
version "9.15.10"
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.15.10.tgz#7b18ed75c90348c045eef9ed08ca1319a2219ad2"
integrity sha512-RoV7OkQm0T3os3Dd2VHLNMoaoDVx77Wygln3n9l5YV172XonWG6rgQD3XnF/BuFFZw9A0TJgmMSO8FEWQgvcXw==
hmac-drbg@^1.0.0:
version "1.0.1"

View File

@@ -52,7 +52,7 @@ export class BookPinManager implements IBookPinManager {
let bookPathToChange: string = notebook.book.contentPath;
let pinnedBooks: IBookNotebook[] = getPinnedNotebooks();
let existingBookIndex = pinnedBooks.map(pinnedBookPath => path.normalize(pinnedBookPath?.notebookPath)).indexOf(path.normalize(bookPathToChange));
let existingBookIndex = pinnedBooks.map(pinnedBookPath => path.normalize(pinnedBookPath?.notebookPath)).indexOf(bookPathToChange);
if (existingBookIndex !== -1 && operation === PinBookOperation.Unpin) {
pinnedBooks.splice(existingBookIndex, 1);

View File

@@ -4,13 +4,11 @@
*--------------------------------------------------------------------------------------------*/
import * as rd from 'resource-deployment';
import { valueProviderService } from './services/valueProviderService';
import { optionsSourcesService } from './services/optionSourcesService';
export function getExtensionApi(): rd.IExtension {
return {
registerOptionsSourceProvider: (provider: rd.IOptionsSourceProvider) => optionsSourcesService.registerOptionsSourceProvider(provider),
registerValueProvider: (provider: rd.IValueProvider) => valueProviderService.registerValueProvider(provider)
registerOptionsSourceProvider: (provider: rd.IOptionsSourceProvider) => optionsSourcesService.registerOptionsSourceProvider(provider)
};
}

View File

@@ -244,8 +244,6 @@ export type ComponentCSSStyles = {
export interface IOptionsSource {
provider?: IOptionsSourceProvider
loadingText?: string,
loadingCompletedText?: string,
readonly variableNames?: { [index: string]: string; };
readonly providerId: string;
}
@@ -263,11 +261,6 @@ export interface DynamicEnablementInfo {
value: string
}
export interface ValueProviderInfo {
providerId: string,
triggerField: string
}
export interface FieldInfoBase {
labelWidth?: string;
inputWidth?: string;
@@ -314,8 +307,9 @@ export interface FieldInfo extends SubFieldInfo, FieldInfoBase {
editable?: boolean; // for editable drop-down,
enabled?: boolean | DynamicEnablementInfo;
isEvaluated?: boolean;
valueLookup?: string; // for fetching dropdown options
validationLookup?: string // for fetching text field validations
validations?: ValidationInfo[];
valueProvider?: ValueProviderInfo;
}
export interface KubeClusterContextFieldInfo extends FieldInfo {

View File

@@ -28,9 +28,7 @@ export const NewResourceGroupAriaLabel = localize('azure.resourceGroup.NewResour
export const realm = localize('deployCluster.Realm', "Realm");
export const unknownFieldTypeError = (type: FieldType) => localize('UnknownFieldTypeError', "Unknown field type: \"{0}\"", type);
export const optionsSourceAlreadyDefined = (optionsSourceId: string) => localize('optionsSource.alreadyDefined', "Options Source with id:{0} is already defined", optionsSourceId);
export const valueProviderAlreadyDefined = (providerId: string) => localize('valueProvider.alreadyDefined', "Value Provider with id:{0} is already defined", providerId);
export const noOptionsSourceDefined = (optionsSourceId: string) => localize('optionsSource.notDefined', "No Options Source defined for id: {0}", optionsSourceId);
export const noValueProviderDefined = (providerId: string) => localize('valueProvider.notDefined', "No Value Provider defined for id: {0}", providerId);
export const variableValueFetchForUnsupportedVariable = (variableName: string) => localize('getVariableValue.unknownVariableName', "Attempt to get variable value for unknown variable:{0}", variableName);
export const isPasswordFetchForUnsupportedVariable = (variableName: string) => localize('getIsPassword.unknownVariableName', "Attempt to get isPassword for unknown variable:{0}", variableName);
export const optionsNotDefined = (fieldType: FieldType) => localize('optionsNotDefined', "FieldInfo.options was not defined for field type: {0}", fieldType);

View File

@@ -3,24 +3,16 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import * as rd from 'resource-deployment';
import * as loc from '../localizedConstants';
class OptionsSourcesService {
private _optionsSourceStore = new Map<string, rd.IOptionsSourceProvider>();
registerOptionsSourceProvider(provider: rd.IOptionsSourceProvider): vscode.Disposable {
if (this._optionsSourceStore.has(provider.id)) {
throw new Error(loc.optionsSourceAlreadyDefined(provider.id));
registerOptionsSourceProvider(provider: rd.IOptionsSourceProvider): void {
if (this._optionsSourceStore.has(provider.optionsSourceId)) {
throw new Error(loc.optionsSourceAlreadyDefined(provider.optionsSourceId));
}
this._optionsSourceStore.set(provider.id, provider);
return {
dispose: () => this.unregisterOptionsSourceProvider(provider.id)
};
}
private unregisterOptionsSourceProvider(providerId: string): void {
this._optionsSourceStore.delete(providerId);
this._optionsSourceStore.set(provider.optionsSourceId, provider);
}
getOptionsSource(optionsSourceProviderId: string): rd.IOptionsSourceProvider {

View File

@@ -1,35 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import * as rd from 'resource-deployment';
import * as loc from '../localizedConstants';
class ValueProviderService {
private _valueProviderStore = new Map<string, rd.IValueProvider>();
registerValueProvider(provider: rd.IValueProvider): vscode.Disposable {
if (this._valueProviderStore.has(provider.id)) {
throw new Error(loc.valueProviderAlreadyDefined(provider.id));
}
this._valueProviderStore.set(provider.id, provider);
return {
dispose: () => this.unregisterValueProvider(provider.id)
};
}
private unregisterValueProvider(providerId: string): void {
this._valueProviderStore.delete(providerId);
}
getValueProvider(providerId: string): rd.IValueProvider {
const valueProvider = this._valueProviderStore.get(providerId);
if (valueProvider === undefined) {
throw new Error(loc.noValueProviderDefined(providerId));
}
return valueProvider;
}
}
export const valueProviderService = new ValueProviderService();

View File

@@ -3,11 +3,8 @@
* 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 events from 'events';
import * as cp from 'promisify-child-process';
import * as TypeMoq from 'typemoq';
import { Readable } from 'stream';
export class TestChildProcessPromise<T> implements cp.ChildProcessPromise {
@@ -106,117 +103,3 @@ export class TestChildProcessPromise<T> implements cp.ChildProcessPromise {
throw new Error('Method not implemented.');
}
}
export type ComponentAndMockComponentBuilder<C, B> = {
component: C,
mockBuilder: TypeMoq.IMock<B>
};
export function createModelViewMock(): {
modelBuilder: TypeMoq.IMock<azdata.ModelBuilder>,
modelView: TypeMoq.IMock<azdata.ModelView>
} {
const mockModelView = TypeMoq.Mock.ofType<azdata.ModelView>();
const mockModelBuilder = TypeMoq.Mock.ofType<azdata.ModelBuilder>();
const mockTextBuilder = createMockComponentBuilder<azdata.TextComponent>();
const mockGroupContainerBuilder = createMockContainerBuilder<azdata.GroupContainer>();
const mockFormContainerBuilder = createMockFormContainerBuilder();
mockModelBuilder.setup(b => b.text()).returns(() => mockTextBuilder.mockBuilder.object);
mockModelBuilder.setup(b => b.groupContainer()).returns(() => mockGroupContainerBuilder.mockBuilder.object);
mockModelBuilder.setup(b => b.formContainer()).returns(() => mockFormContainerBuilder.object);
mockModelView.setup(mv => mv.modelBuilder).returns(() => mockModelBuilder.object);
return {
modelBuilder: mockModelBuilder,
modelView: mockModelView
};
}
export function createMockComponentBuilder<C extends azdata.Component, B extends azdata.ComponentBuilder<C, any> = azdata.ComponentBuilder<C, any>>(component?: C): ComponentAndMockComponentBuilder<C, B> {
const mockComponentBuilder = TypeMoq.Mock.ofType<B>();
// Create a mocked dynamic component if we don't have a stub instance to use.
// Note that we don't use ofInstance here for the component because there's some limitations around properties that I was
// hitting preventing me from easily using TypeMoq. Passing in the stub instance lets users control the object being stubbed - which means
// they can use things like sinon to then override specific functions if desired.
if (!component) {
const mockComponent = TypeMoq.Mock.ofType<C>();
// Need to setup then for when a dynamic mocked object is resolved otherwise the test will hang : https://github.com/florinn/typemoq/issues/66
mockComponent.setup((x: any) => x.then).returns(() => undefined);
component = mockComponent.object;
}
// For now just have these be passthrough - can hook up additional functionality later if needed
mockComponentBuilder.setup(b => b.withProperties(TypeMoq.It.isAny())).returns(() => mockComponentBuilder.object);
mockComponentBuilder.setup(b => b.withValidation(TypeMoq.It.isAny())).returns(() => mockComponentBuilder.object);
mockComponentBuilder.setup(b => b.component()).returns(() => component! /*mockComponent.object*/);
return {
component: component!,
mockBuilder: mockComponentBuilder
};
}
export function createMockContainerBuilder<C extends azdata.Container<any, any>, B extends azdata.ContainerBuilder<C, any, any, any> = azdata.ContainerBuilder<C, any, any, any>>(): ComponentAndMockComponentBuilder<C, B> {
const mockContainerBuilder = createMockComponentBuilder<C, B>();
// For now just have these be passthrough - can hook up additional functionality later if needed
mockContainerBuilder.mockBuilder.setup(b => b.withItems(TypeMoq.It.isAny(), undefined)).returns(() => mockContainerBuilder.mockBuilder.object);
mockContainerBuilder.mockBuilder.setup(b => b.withLayout(TypeMoq.It.isAny())).returns(() => mockContainerBuilder.mockBuilder.object);
return mockContainerBuilder;
}
export function createMockFormContainerBuilder(): TypeMoq.IMock<azdata.FormBuilder> {
const mockContainerBuilder = createMockContainerBuilder<azdata.FormContainer, azdata.FormBuilder>();
mockContainerBuilder.mockBuilder.setup(b => b.withFormItems(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => mockContainerBuilder.mockBuilder.object);
return mockContainerBuilder.mockBuilder;
}
export class StubInputBox implements azdata.InputBoxComponent {
readonly id = 'input-box';
public enabled: boolean = false;
onTextChanged: vscode.Event<any> = undefined!;
onEnterKeyPressed: vscode.Event<string> = undefined!;
updateProperties(properties: { [key: string]: any }): Thenable<void> { throw new Error('Not implemented'); }
updateProperty(key: string, value: any): Thenable<void> { throw new Error('Not implemented'); }
updateCssStyles(cssStyles: { [key: string]: string }): Thenable<void> { throw new Error('Not implemented'); }
readonly onValidityChanged: vscode.Event<boolean> = undefined!;
readonly valid: boolean = true;
validate(): Thenable<boolean> { throw new Error('Not implemented'); }
focus(): Thenable<void> { return Promise.resolve(); }
}
export class StubCheckbox implements azdata.CheckBoxComponent {
private _onChanged = new vscode.EventEmitter<void>();
private _checked = false;
readonly id = 'stub-checkbox';
public enabled: boolean = false;
get checked(): boolean {
return this._checked;
}
set checked(value: boolean) {
this._checked = value;
this._onChanged.fire();
}
onChanged: vscode.Event<any> = this._onChanged.event;
updateProperties(properties: { [key: string]: any }): Thenable<void> { throw new Error('Not implemented'); }
updateProperty(key: string, value: any): Thenable<void> { throw new Error('Not implemented'); }
updateCssStyles(cssStyles: { [key: string]: string }): Thenable<void> { throw new Error('Not implemented'); }
readonly onValidityChanged: vscode.Event<boolean> = undefined!;
readonly valid: boolean = true;
validate(): Thenable<boolean> { throw new Error('Not implemented'); }
focus(): Thenable<void> { return Promise.resolve(); }
}

View File

@@ -1,107 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import 'mocha';
import * as vscode from 'vscode';
import * as TypeMoq from 'typemoq';
import { initializeWizardPage, InputComponent, InputComponentInfo, Validator, WizardPageContext } from '../../../ui/modelViewUtils';
import { FieldType } from '../../../interfaces';
import { IToolsService } from '../../../services/toolsService';
import { Deferred } from '../../utils';
import { createMockComponentBuilder, createModelViewMock as createMockModelView, StubCheckbox, StubInputBox } from '../../stubs';
import * as should from 'should';
import * as sinon from 'sinon';
describe('WizardPage', () => {
let mockModelBuilder: TypeMoq.IMock<azdata.ModelBuilder>;
let testWizardPage: WizardPageContext;
let contentRegistered: Deferred<void>;
before(function () {
contentRegistered = new Deferred<void>();
const mockWizardPage = TypeMoq.Mock.ofType<azdata.window.WizardPage>();
const mockModelView = createMockModelView();
mockModelBuilder = mockModelView.modelBuilder;
mockWizardPage.setup(p => p.registerContent(TypeMoq.It.isAny())).callback(async (handler: (view: azdata.ModelView) => Thenable<void>) => {
await handler(mockModelView.modelView.object);
contentRegistered.resolve();
});
const mockWizard = TypeMoq.Mock.ofType<azdata.window.Wizard>();
const mockToolsService = TypeMoq.Mock.ofType<IToolsService>();
testWizardPage = {
page: mockWizardPage.object,
container: mockWizard.object,
wizardInfo: {
title: 'TestWizard',
pages: [],
doneAction: {}
},
pageInfo: {
title: 'TestWizardPage',
sections: [
{
fields: [
{
label: 'Field1',
type: FieldType.Checkbox
},
{
label: 'Field2',
type: FieldType.Text,
enabled: {
target: 'Field1',
value: 'true'
}
}
]
}
]
},
inputComponents: {},
onNewDisposableCreated: (_disposable: vscode.Disposable): void => { },
onNewInputComponentCreated: (
name: string,
inputComponentInfo: InputComponentInfo<InputComponent>
): void => {
testWizardPage.inputComponents[name] = inputComponentInfo;
},
onNewValidatorCreated: (_validator: Validator): void => { },
toolsService: mockToolsService.object
};
});
it('dynamic enablement', async function (): Promise<void> {
const stubCheckbox = new StubCheckbox();
const mockCheckboxBuilder = createMockComponentBuilder<azdata.CheckBoxComponent>(stubCheckbox);
const stubInputBox = new StubInputBox();
// Stub out the enabled property so we can hook into when that's set to ensure we wait for the state to be updated
// before continuing the test
let enabled = false;
sinon.stub(stubInputBox, 'enabled').set(v => {
enabled = v;
enabledDeferred.resolve();
});
sinon.stub(stubInputBox, 'enabled').get(() => {
return enabled;
});
const mockInputBoxBuilder = createMockComponentBuilder<azdata.InputBoxComponent>(stubInputBox);
// Used to ensure that we wait until the enabled state is updated for our mocked components before continuing
let enabledDeferred = new Deferred();
mockModelBuilder.setup(b => b.checkBox()).returns(() => mockCheckboxBuilder.mockBuilder.object);
mockModelBuilder.setup(b => b.inputBox()).returns(() => mockInputBoxBuilder.mockBuilder.object);
initializeWizardPage(testWizardPage);
await contentRegistered.promise;
await enabledDeferred.promise;
should(stubInputBox.enabled).be.false('Input box should be disabled by default');
enabledDeferred = new Deferred();
stubCheckbox.checked = true;
// Now wait for the enabled state to be updated again
await enabledDeferred.promise;
should(stubInputBox.enabled).be.true('Input box should be enabled after target component value updated');
});
});

View File

@@ -4,31 +4,17 @@
*--------------------------------------------------------------------------------------------*/
declare module 'resource-deployment' {
import * as azdata from 'azdata';
import * as vscode from 'vscode';
export const enum ErrorType {
userCancelled,
}
export interface ErrorWithType extends Error {
readonly type: ErrorType;
}
export const enum extension {
name = 'Microsoft.resource-deployment'
}
export interface IOptionsSourceProvider {
readonly id: string,
readonly optionsSourceId: string,
getOptions(): Promise<string[] | azdata.CategoryValue[]> | string[] | azdata.CategoryValue[];
getVariableValue?: (variableName: string, input: string) => Promise<string> | string;
getIsPassword?: (variableName: string) => boolean | Promise<boolean>;
}
export interface IValueProvider {
readonly id: string,
getValue(triggerValue: string): Promise<string>;
}
/**
* Covers defining what the resource-deployment extension exports to other extensions
*
@@ -37,7 +23,6 @@ declare module 'resource-deployment' {
*/
export interface IExtension {
registerOptionsSourceProvider(provider: IOptionsSourceProvider): vscode.Disposable,
registerValueProvider(provider: IValueProvider): vscode.Disposable
registerOptionsSourceProvider(provider: IOptionsSourceProvider): void
}
}

View File

@@ -14,7 +14,6 @@ import { getDateTimeString, getErrorMessage, throwUnless } from '../common/utils
import { AzureAccountFieldInfo, AzureLocationsFieldInfo, ComponentCSSStyles, DialogInfoBase, FieldInfo, FieldType, FilePickerFieldInfo, instanceOfDynamicEnablementInfo, IOptionsSource, KubeClusterContextFieldInfo, LabelPosition, NoteBookEnvironmentVariablePrefix, OptionsInfo, OptionsType, PageInfoBase, RowInfo, SectionInfo, TextCSSStyles } from '../interfaces';
import * as loc from '../localizedConstants';
import { apiService } from '../services/apiService';
import { valueProviderService } from '../services/valueProviderService';
import { getDefaultKubeConfigPath, getKubeConfigClusterContexts } from '../services/kubeService';
import { optionsSourcesService } from '../services/optionSourcesService';
import { KubeCtlTool, KubeCtlToolName } from '../services/tools/kubeCtlTool';
@@ -39,7 +38,6 @@ export type InputComponentInfo<T extends InputComponent> = {
component: T;
labelComponent?: azdata.TextComponent;
getValue: () => Promise<InputValueType>;
setValue: (value: InputValueType) => void;
getDisplayValue?: () => Promise<string>;
onValueChanged: vscode.Event<void>;
isPassword?: boolean
@@ -202,7 +200,6 @@ export function createInputBoxInputInfo(view: azdata.ModelView, inputInfo: Input
return {
component: component,
getValue: async (): Promise<InputValueType> => component.value,
setValue: (value: InputValueType) => component.value = value?.toString(),
onValueChanged: component.onTextChanged
};
}
@@ -243,7 +240,6 @@ export function createCheckboxInputInfo(view: azdata.ModelView, info: { initialV
return {
component: checkbox,
getValue: async () => checkbox.checked ? 'true' : 'false',
setValue: (value: InputValueType) => checkbox.checked = value?.toString().toLowerCase() === 'true' ? true : false,
onValueChanged: checkbox.onChanged
};
}
@@ -269,7 +265,6 @@ export function createDropdownInputInfo(view: azdata.ModelView, info: { defaultV
return {
component: dropdown,
getValue: async (): Promise<InputValueType> => typeof dropdown.value === 'string' ? dropdown.value : dropdown.value?.name,
setValue: (value: InputValueType) => setDropdownValue(dropdown, value?.toString()),
getDisplayValue: async (): Promise<string> => (typeof dropdown.value === 'string' ? dropdown.value : dropdown.value?.displayName) || '',
onValueChanged: dropdown.onValueChanged,
};
@@ -336,7 +331,6 @@ export function initializeWizardPage(context: WizardPageContext): void {
});
}));
await hookUpDynamicEnablement(context);
await hookUpValueProviders(context);
const formBuilder = view.modelBuilder.formContainer().withFormItems(
sections.map(section => { return { title: '', component: section }; }),
{
@@ -377,8 +371,7 @@ async function hookUpDynamicEnablement(context: WizardPageContext): Promise<void
}
const updateFields = async () => {
const targetComponentValue = await targetComponent.getValue();
const valuesMatch = targetComponentValue === targetValue;
fieldComponent.component.enabled = valuesMatch;
fieldComponent.component.enabled = targetComponentValue === targetValue;
const isRequired = fieldComponent.component.enabled === false ? false : field.required;
if (fieldComponent.labelComponent) {
fieldComponent.labelComponent.requiredIndicator = isRequired;
@@ -387,41 +380,6 @@ async function hookUpDynamicEnablement(context: WizardPageContext): Promise<void
if ('required' in fieldComponent.component) {
fieldComponent.component.required = isRequired;
}
// When we disable the field then remove the placeholder if it exists so it's clear this field isn't needed
// We only do this for dynamic enablement since if a field is disabled through the JSON directly then it can't
// be modified anyways and so just should not use a placeholder value if they don't want one
if ('placeHolder' in fieldComponent.component) {
fieldComponent.component.placeHolder = valuesMatch ? field.placeHolder : '';
}
};
targetComponent.onValueChanged(() => {
updateFields();
});
await updateFields();
}
}));
}));
}
async function hookUpValueProviders(context: WizardPageContext): Promise<void> {
await Promise.all(context.pageInfo.sections.map(async section => {
if (!section.fields) {
return;
}
await Promise.all(section.fields.map(async field => {
if (field.valueProvider) {
const fieldKey = field.variableName || field.label;
const fieldComponent = context.inputComponents[fieldKey];
const targetComponent = context.inputComponents[field.valueProvider.triggerField];
if (!targetComponent) {
console.error(`Could not find target component ${field.valueProvider.triggerField} when hooking up value providers for ${field.label}`);
return;
}
const provider = valueProviderService.getValueProvider(field.valueProvider.providerId);
const updateFields = async () => {
const targetComponentValue = await targetComponent.getValue();
const newFieldValue = await provider.getValue(targetComponentValue?.toString() ?? '');
fieldComponent.setValue(newFieldValue);
};
targetComponent.onValueChanged(() => {
updateFields();
@@ -614,6 +572,7 @@ async function processOptionsTypeField(context: FieldContext): Promise<void> {
if (context.fieldInfo.options.source?.providerId) {
try {
context.fieldInfo.options.source.provider = optionsSourcesService.getOptionsSource(context.fieldInfo.options.source.providerId);
context.fieldInfo.options.values = await context.fieldInfo.options.source.provider.getOptions();
}
catch (e) {
disableControlButtons(context.container);
@@ -627,25 +586,16 @@ async function processOptionsTypeField(context: FieldContext): Promise<void> {
context.fieldInfo.subFields = context.fieldInfo.subFields || [];
}
let optionsComponent: RadioGroupLoadingComponentBuilder | azdata.DropDownComponent;
const options = context.fieldInfo.options;
const optionsSource = options.source;
if (context.fieldInfo.options.optionsType === OptionsType.Radio) {
let getRadioOptions: (() => Promise<OptionsInfo>) | undefined = undefined;
// If the options are provided for us then set up the callback to load those options async'ly
if (optionsSource?.provider) {
getRadioOptions = async () => {
return { defaultValue: options.defaultValue, values: await optionsSource.provider!.getOptions() };
};
}
optionsComponent = await processRadioOptionsTypeField(context, getRadioOptions);
optionsComponent = await processRadioOptionsTypeField(context);
} else {
throwUnless(context.fieldInfo.options.optionsType === OptionsType.Dropdown, loc.optionsTypeRadioOrDropdown);
optionsComponent = processDropdownOptionsTypeField(context);
}
const optionsSource = context.fieldInfo.options.source;
if (optionsSource?.provider) {
const optionsSourceProvider = optionsSource.provider;
await Promise.all(Object.keys(optionsSource?.variableNames ?? {}).map(async key => {
await Promise.all(Object.keys(context.fieldInfo.options.source?.variableNames ?? {}).map(async key => {
await configureOptionsSourceSubfields(context, optionsSource, key, optionsComponent, optionsSourceProvider);
}));
}
@@ -673,7 +623,6 @@ async function configureOptionsSourceSubfields(context: FieldContext, optionsSou
throw e;
}
},
setValue: (_value: InputValueType) => { throw new Error('Setting value of radio group isn\'t currently supported'); },
onValueChanged: optionsComponent.onValueChanged
});
}
@@ -710,7 +659,6 @@ function processNumberField(context: FieldContext): void {
const value = await input.getValue();
return typeof value === 'string' && value.length > 0 ? parseFloat(value) : value;
},
setValue: (value: InputValueType) => input.component.value = value?.toString(),
onValueChanged: input.onValueChanged
});
}
@@ -807,7 +755,6 @@ function processEvaluatedTextField(context: FieldContext): ReadOnlyFieldInputs {
readOnlyField.text!.value = await substituteVariableValues(context.inputComponents, context.fieldInfo.defaultValue);
return readOnlyField.text!.value;
},
setValue: (value: InputValueType) => readOnlyField.text!.value = value?.toString(),
onValueChanged: onChangedEmitter.event,
});
return readOnlyField;
@@ -974,8 +921,8 @@ async function processKubeConfigClusterPickerField(context: KubeClusterContextFi
}
async function processRadioOptionsTypeField(context: FieldContext, getRadioButtonInfo?: () => Promise<OptionsInfo>): Promise<RadioGroupLoadingComponentBuilder> {
return await createRadioOptions(context, getRadioButtonInfo);
async function processRadioOptionsTypeField(context: FieldContext): Promise<RadioGroupLoadingComponentBuilder> {
return await createRadioOptions(context);
}
@@ -986,31 +933,18 @@ async function createRadioOptions(context: FieldContext, getRadioButtonInfo?: ((
}
const label = createLabel(context.view, { text: context.fieldInfo.label, description: context.fieldInfo.description, required: context.fieldInfo.required, width: context.fieldInfo.labelWidth, cssStyles: context.fieldInfo.labelCSSStyles });
const radioGroupLoadingComponentBuilder = new RadioGroupLoadingComponentBuilder(context.view, context.onNewDisposableCreated, context.fieldInfo);
context.fieldInfo.labelPosition = LabelPosition.Left;
context.onNewInputComponentCreated(context.fieldInfo.variableName || context.fieldInfo.label, {
component: radioGroupLoadingComponentBuilder,
labelComponent: label,
getValue: async (): Promise<InputValueType> => radioGroupLoadingComponentBuilder.value,
setValue: (value: InputValueType) => { throw new Error('Setting value of radio group isn\'t currently supported'); },
getDisplayValue: async (): Promise<string> => radioGroupLoadingComponentBuilder.displayValue,
onValueChanged: radioGroupLoadingComponentBuilder.onValueChanged,
});
const options = context.fieldInfo.options as OptionsInfo;
let loadingText = options?.source?.loadingText;
let loadingCompletedText = options?.source?.loadingCompletedText;
if (loadingText || loadingCompletedText) {
radioGroupLoadingComponentBuilder.withProps({
showText: true,
loadingText: loadingText,
loadingCompletedText: loadingCompletedText
});
}
addLabelInputPairToContainer(context.view, context.components, label, radioGroupLoadingComponentBuilder.component(), context.fieldInfo);
// Start loading the options but continue on so that we can continue setting up the rest of the components - the group
// will show a loading spinner while the options are loaded
radioGroupLoadingComponentBuilder.loadOptions(
getRadioButtonInfo || options).catch(e => console.log('Error loading options for radio group ', e));
const options = context.fieldInfo.options as OptionsInfo;
await radioGroupLoadingComponentBuilder.loadOptions(
getRadioButtonInfo || options); // wait for the radioGroup to be fully initialized
return radioGroupLoadingComponentBuilder;
}
@@ -1199,7 +1133,6 @@ function createAzureSubscriptionDropdown(
const inputValue = (await subscriptionDropdown.getValue())?.toString() || '';
return subscriptionValueToSubscriptionMap.get(inputValue)?.id || inputValue;
},
setValue: (value: InputValueType) => setDropdownValue(subscriptionDropdown.component, value?.toString()),
getDisplayValue: subscriptionDropdown.getDisplayValue,
onValueChanged: subscriptionDropdown.onValueChanged
});
@@ -1461,18 +1394,3 @@ export function isInputBoxEmpty(input: azdata.InputBoxComponent): boolean {
return input.value === undefined || input.value === '';
}
/**
* Sets the dropdown value to the corresponding value from the list of current values, converting
* into a CategoryValue if necessary (using the name field).
* @param dropdown The dropdown component to set the value for
* @param value The value to set - either the direct string value or the name of the CategoryValue to use
*/
function setDropdownValue(dropdown: azdata.DropDownComponent, value: string = ''): void {
const values = dropdown.values ?? [];
if (typeof values[0] === 'object') {
dropdown.value = (<azdata.CategoryValue[]>values).find(v => v.name === value);
} else {
dropdown.value = value;
}
}

View File

@@ -12,7 +12,6 @@ import { DeploymentType, NotebookWizardDeploymentProvider, NotebookWizardInfo }
import { IPlatformService } from '../../services/platformService';
import { NotebookWizardAutoSummaryPage } from './notebookWizardAutoSummaryPage';
import { NotebookWizardPage } from './notebookWizardPage';
import { ErrorType, ErrorWithType } from 'resource-deployment';
export class NotebookWizardModel extends ResourceTypeModel {
private _inputComponents: InputComponents = {};
@@ -59,27 +58,16 @@ export class NotebookWizardModel extends ResourceTypeModel {
}
/**
* Generates the notebook and returns true if generation was done and so the wizard should be closed.
* Generates the notebook and returns true on successful generation
**/
public async onGenerateScript(): Promise<boolean> {
const lastPage = this.wizard.lastPage! as NotebookWizardPage;
if (lastPage.validatePage()) {
let notebook: Notebook | undefined;
try {
notebook = await this.prepareNotebookAndEnvironment();
} catch (e) {
const isUserCancelled = e instanceof Error && 'type' in e && (<ErrorWithType>e).type === ErrorType.userCancelled;
// user cancellation is a normal scenario, we just bail out of the wizard without actually opening the notebook, so rethrow for any other case
if (!isUserCancelled) {
throw e;
}
}
if (notebook) { // open the notebook if it was successfully prepared
await this.openNotebook(notebook);
}
return true; // generation done (or cancelled at user request) so close the wizard
const notebook = await this.prepareNotebookAndEnvironment();
await this.openNotebook(notebook);
return true;
} else {
return false; // validation failed so do not attempt to generate the notebook and do not close the wizard
return false;
}
}
@@ -94,7 +82,7 @@ export class NotebookWizardModel extends ResourceTypeModel {
return await this.notebookService.openNotebookWithContent(notebookPath, JSON.stringify(notebook, undefined, 4));
}
private async prepareNotebookAndEnvironment(): Promise<Notebook> {
private async prepareNotebookAndEnvironment() {
await setModelValues(this.inputComponents, this);
const env: NodeJS.ProcessEnv = process.env;
this.setEnvironmentVariables(env, (varName) => {

View File

@@ -2,7 +2,7 @@
"name": "schema-compare",
"displayName": "%displayName%",
"description": "%description%",
"version": "1.9.0",
"version": "1.8.0",
"publisher": "Microsoft",
"preview": false,
"engines": {
@@ -87,15 +87,13 @@
},
"devDependencies": {
"@types/mocha": "^5.2.5",
"@types/sinon": "^9.0.4",
"@types/node": "^12.11.7",
"mocha": "^5.2.0",
"mocha-junit-reporter": "^1.17.0",
"mocha-multi-reporters": "^1.1.7",
"should": "^13.2.1",
"typemoq": "^2.1.0",
"vscodetestcover": "^1.1.0",
"sinon": "^9.0.2"
"vscodetestcover": "^1.1.0"
},
"__metadata": {
"id": "37",

View File

@@ -0,0 +1,48 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import * as azdata from 'azdata';
/**
* Wrapper class to act as a facade over VSCode and Data APIs and allow us to test / mock callbacks into
* this API from our code
*/
export class ApiWrapper {
public openConnectionDialog(providers?: string[],
initialConnectionProfile?: azdata.IConnectionProfile,
connectionCompletionOptions?: azdata.IConnectionCompletionOptions): Thenable<azdata.connection.Connection> {
return azdata.connection.openConnectionDialog(providers, initialConnectionProfile, connectionCompletionOptions);
}
public registerCommand(command: string, callback: (...args: any[]) => any, thisArg?: any): vscode.Disposable {
return vscode.commands.registerCommand(command, callback, thisArg);
}
public getUriForConnection(connectionId: string): Thenable<string> {
return azdata.connection.getUriForConnection(connectionId);
}
public getConnections(activeConnectionsOnly?: boolean): Thenable<azdata.connection.ConnectionProfile[]> {
return azdata.connection.getConnections(activeConnectionsOnly);
}
public connect(connectionProfile: azdata.IConnectionProfile, saveConnection?: boolean, showDashboard?: boolean): Thenable<azdata.ConnectionResult> {
return azdata.connection.connect(connectionProfile, saveConnection, showDashboard);
}
public showErrorMessage(message: string, ...items: string[]): Thenable<string | undefined> {
return vscode.window.showErrorMessage(message, ...items);
}
public showWarningMessage(message: string, options?: vscode.MessageOptions, ...items: string[]): Thenable<string | undefined> {
if (options) {
return vscode.window.showWarningMessage(message, options, ...items);
}
else {
return vscode.window.showWarningMessage(message, ...items);
}
}
}

View File

@@ -4,10 +4,11 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { ApiWrapper } from './common/apiWrapper';
import { SchemaCompareMainWindow } from './schemaCompareMainWindow';
export async function activate(extensionContext: vscode.ExtensionContext): Promise<void> {
vscode.commands.registerCommand('schemaCompare.start', async (context: any) => { await new SchemaCompareMainWindow(undefined, extensionContext).start(context); });
vscode.commands.registerCommand('schemaCompare.start', async (context: any) => { await new SchemaCompareMainWindow(new ApiWrapper(), undefined, extensionContext).start(context); });
}
export function deactivate(): void {

View File

@@ -14,6 +14,7 @@ import { TelemetryReporter, TelemetryViews } from './telemetry';
import { getTelemetryErrorType, getEndpointName, verifyConnectionAndGetOwnerUri, getRootPath } from './utils';
import { SchemaCompareDialog } from './dialogs/schemaCompareDialog';
import { isNullOrUndefined } from 'util';
import { ApiWrapper } from './common/apiWrapper';
// Do not localize this, this is used to decide the icon for the editor.
// TODO : In future icon should be decided based on language id (scmp) and not resource name
@@ -50,8 +51,8 @@ export class SchemaCompareMainWindow {
private SchemaCompareActionMap: Map<Number, string>;
private operationId: string;
protected comparisonResult: mssql.SchemaCompareResult;
private sourceNameComponent: azdata.InputBoxComponent;
private targetNameComponent: azdata.InputBoxComponent;
private sourceNameComponent: azdata.TableComponent;
private targetNameComponent: azdata.TableComponent;
private deploymentOptions: mssql.DeploymentOptions;
private schemaCompareOptionDialog: SchemaCompareOptionsDialog;
private tablelistenersToDispose: vscode.Disposable[] = [];
@@ -68,7 +69,7 @@ export class SchemaCompareMainWindow {
public sourceEndpointInfo: mssql.SchemaCompareEndpointInfo;
public targetEndpointInfo: mssql.SchemaCompareEndpointInfo;
constructor(private schemaCompareService?: mssql.ISchemaCompareService, private extensionContext?: vscode.ExtensionContext) {
constructor(private apiWrapper: ApiWrapper, private schemaCompareService?: mssql.ISchemaCompareService, private extensionContext?: vscode.ExtensionContext) {
this.SchemaCompareActionMap = new Map<Number, string>();
this.SchemaCompareActionMap[mssql.SchemaUpdateAction.Delete] = loc.deleteAction;
this.SchemaCompareActionMap[mssql.SchemaUpdateAction.Change] = loc.changeAction;
@@ -86,7 +87,7 @@ export class SchemaCompareMainWindow {
let profile = context ? <azdata.IConnectionProfile>context.connectionProfile : undefined;
let sourceDacpac = context as string;
if (profile) {
let ownerUri = await azdata.connection.getUriForConnection((profile.id));
let ownerUri = await this.apiWrapper.getUriForConnection((profile.id));
this.sourceEndpointInfo = {
endpointType: mssql.SchemaCompareEndpointType.Database,
serverDisplayName: `${profile.serverName} ${profile.userName}`,
@@ -156,16 +157,26 @@ export class SchemaCompareMainWindow {
this.sourceName = getEndpointName(this.sourceEndpointInfo);
this.targetName = ' ';
this.sourceNameComponent = view.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({
value: this.sourceName,
title: this.sourceName,
enabled: false
this.sourceNameComponent = view.modelBuilder.table().withProperties<azdata.TableComponentProperties>({
data: [],
columns: [
{
value: this.sourceName,
headerCssClass: 'no-borders',
toolTip: this.sourceName
},
]
}).component();
this.targetNameComponent = view.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({
value: this.targetName,
title: this.targetName,
enabled: false
this.targetNameComponent = view.modelBuilder.table().withProperties<azdata.TableComponentProperties>({
data: [],
columns: [
{
value: this.targetName,
headerCssClass: 'no-borders',
toolTip: this.targetName
},
]
}).component();
this.resetButtons(ResetButtonState.noSourceTarget);
@@ -207,10 +218,10 @@ export class SchemaCompareMainWindow {
sourceTargetLabels.addItem(sourceLabel, { CSSStyles: { 'width': '55%', 'margin-left': '15px', 'font-size': 'larger', 'font-weight': 'bold' } });
sourceTargetLabels.addItem(targetLabel, { CSSStyles: { 'width': '45%', 'font-size': 'larger', 'font-weight': 'bold' } });
this.sourceTargetFlexLayout.addItem(this.sourceNameComponent, { CSSStyles: { 'width': '40%', 'height': '25px', 'margin-top': '10px', 'margin-left': '15px', 'margin-right': '10px' } });
this.sourceTargetFlexLayout.addItem(this.sourceNameComponent, { CSSStyles: { 'width': '40%', 'height': '25px', 'margin-top': '10px', 'margin-left': '15px' } });
this.sourceTargetFlexLayout.addItem(this.selectSourceButton, { CSSStyles: { 'margin-top': '10px' } });
this.sourceTargetFlexLayout.addItem(arrowLabel, { CSSStyles: { 'width': '10%', 'font-size': 'larger', 'text-align-last': 'center' } });
this.sourceTargetFlexLayout.addItem(this.targetNameComponent, { CSSStyles: { 'width': '40%', 'height': '25px', 'margin-top': '10px', 'margin-left': '15px', 'margin-right': '10px' } });
this.sourceTargetFlexLayout.addItem(this.targetNameComponent, { CSSStyles: { 'width': '40%', 'height': '25px', 'margin-top': '10px', 'margin-left': '15px' } });
this.sourceTargetFlexLayout.addItem(this.selectTargetButton, { CSSStyles: { 'margin-top': '10px' } });
this.loader = view.modelBuilder.loadingComponent().component();
@@ -248,14 +259,21 @@ export class SchemaCompareMainWindow {
this.sourceName = getEndpointName(this.sourceEndpointInfo);
this.targetName = getEndpointName(this.targetEndpointInfo);
this.sourceNameComponent.updateProperties({
value: this.sourceName,
title: this.sourceName
});
this.targetNameComponent.updateProperties({
value: this.targetName,
title: this.targetName
});
this.sourceNameComponent.updateProperty('columns', [
{
value: this.sourceName,
headerCssClass: 'no-borders',
toolTip: this.sourceName
},
]);
this.targetNameComponent.updateProperty('columns', [
{
value: this.targetName,
headerCssClass: 'no-borders',
toolTip: this.targetName
},
]);
if (!this.sourceName || !this.targetName || this.sourceName === ' ' || this.targetName === ' ') {
this.resetButtons(ResetButtonState.noSourceTarget);
} else {
@@ -277,11 +295,11 @@ export class SchemaCompareMainWindow {
}
this.comparisonResult = await service.schemaCompare(this.operationId, this.sourceEndpointInfo, this.targetEndpointInfo, azdata.TaskExecutionMode.execute, this.deploymentOptions);
if (!this.comparisonResult || !this.comparisonResult.success) {
TelemetryReporter.createErrorEvent(TelemetryViews.SchemaCompareMainWindow, 'SchemaComparisonFailed', undefined, getTelemetryErrorType(this.comparisonResult?.errorMessage))
TelemetryReporter.createErrorEvent(TelemetryViews.SchemaCompareMainWindow, 'SchemaComparisonFailed', undefined, getTelemetryErrorType(this.comparisonResult.errorMessage))
.withAdditionalProperties({
operationId: this.comparisonResult.operationId
}).send();
vscode.window.showErrorMessage(loc.compareErrorMessage(this.comparisonResult?.errorMessage));
this.apiWrapper.showErrorMessage(loc.compareErrorMessage(this.comparisonResult.errorMessage));
return;
}
TelemetryReporter.createActionEvent(TelemetryViews.SchemaCompareMainWindow, 'SchemaComparisonFinished')
@@ -389,76 +407,69 @@ export class SchemaCompareMainWindow {
this.tablelistenersToDispose.push(this.differencesTable.onCellAction(async (rowState) => {
let checkboxState = <azdata.ICheckboxCellActionEventArgs>rowState;
if (checkboxState) {
await this.applyIncludeExclude(checkboxState);
// show an info notification the first time when trying to exclude to notify the user that it may take some time to calculate affected dependencies
if (this.showIncludeExcludeWaitingMessage) {
this.showIncludeExcludeWaitingMessage = false;
vscode.window.showInformationMessage(loc.includeExcludeInfoMessage);
}
let diff = this.comparisonResult.differences[checkboxState.row];
const result = await service.schemaCompareIncludeExcludeNode(this.comparisonResult.operationId, diff, checkboxState.checked, azdata.TaskExecutionMode.execute);
let checkboxesToChange = [];
if (result.success) {
this.saveExcludeState(checkboxState);
// dependencies could have been included or excluded as a result, so save their exclude states
result.affectedDependencies.forEach(difference => {
// find the row of the difference and set its checkbox
const diffEntryKey = this.createDiffEntryKey(difference);
if (this.diffEntryRowMap.has(diffEntryKey)) {
const row = this.diffEntryRowMap.get(diffEntryKey);
checkboxesToChange.push({ row: row, column: 2, columnName: 'Include', checked: difference.included });
const dependencyCheckBoxState: azdata.ICheckboxCellActionEventArgs = {
checked: difference.included,
row: row,
column: 2,
columnName: undefined
};
this.saveExcludeState(dependencyCheckBoxState);
}
});
} else {
// failed because of dependencies
if (result.blockingDependencies) {
// show the first dependent that caused this to fail in the warning message
const diffEntryName = this.createName(diff.sourceValue ? diff.sourceValue : diff.targetValue);
const firstDependentName = this.createName(result.blockingDependencies[0].sourceValue ? result.blockingDependencies[0].sourceValue : result.blockingDependencies[0].targetValue);
let cannotExcludeMessage: string;
let cannotIncludeMessage: string;
if (firstDependentName) {
cannotExcludeMessage = loc.cannotExcludeMessageDependent(diffEntryName, firstDependentName);
cannotIncludeMessage = loc.cannotIncludeMessageDependent(diffEntryName, firstDependentName);
} else {
cannotExcludeMessage = loc.cannotExcludeMessage(diffEntryName);
cannotIncludeMessage = loc.cannotIncludeMessage(diffEntryName);
}
vscode.window.showWarningMessage(checkboxState.checked ? cannotIncludeMessage : cannotExcludeMessage);
} else {
vscode.window.showWarningMessage(result.errorMessage);
}
// set checkbox back to previous state
checkboxesToChange.push({ row: checkboxState.row, column: checkboxState.column, columnName: 'Include', checked: !checkboxState.checked });
}
if (checkboxesToChange.length > 0) {
this.differencesTable.updateCells = checkboxesToChange;
}
}
}));
}
public async applyIncludeExclude(checkboxState: azdata.ICheckboxCellActionEventArgs): Promise<void> {
const service = await this.getService();
// show an info notification the first time when trying to exclude to notify the user that it may take some time to calculate affected dependencies
if (this.showIncludeExcludeWaitingMessage) {
this.showIncludeExcludeWaitingMessage = false;
vscode.window.showInformationMessage(loc.includeExcludeInfoMessage);
}
let diff = this.comparisonResult.differences[checkboxState.row];
const result = await service.schemaCompareIncludeExcludeNode(this.comparisonResult.operationId, diff, checkboxState.checked, azdata.TaskExecutionMode.execute);
let checkboxesToChange = [];
if (result.success) {
this.saveExcludeState(checkboxState);
// dependencies could have been included or excluded as a result, so save their exclude states
result.affectedDependencies.forEach(difference => {
// find the row of the difference and set its checkbox
const diffEntryKey = this.createDiffEntryKey(difference);
if (this.diffEntryRowMap.has(diffEntryKey)) {
const row = this.diffEntryRowMap.get(diffEntryKey);
checkboxesToChange.push({ row: row, column: 2, columnName: 'Include', checked: difference.included });
const dependencyCheckBoxState: azdata.ICheckboxCellActionEventArgs = {
checked: difference.included,
row: row,
column: 2,
columnName: undefined
};
this.saveExcludeState(dependencyCheckBoxState);
}
});
} else {
// failed because of dependencies
if (result.blockingDependencies) {
// show the first dependent that caused this to fail in the warning message
const diffEntryName = this.createName(diff.sourceValue ? diff.sourceValue : diff.targetValue);
const firstDependentName = this.createName(result.blockingDependencies[0].sourceValue ? result.blockingDependencies[0].sourceValue : result.blockingDependencies[0].targetValue);
let cannotExcludeMessage: string;
let cannotIncludeMessage: string;
if (firstDependentName) {
cannotExcludeMessage = loc.cannotExcludeMessageDependent(diffEntryName, firstDependentName);
cannotIncludeMessage = loc.cannotIncludeMessageDependent(diffEntryName, firstDependentName);
} else {
cannotExcludeMessage = loc.cannotExcludeMessage(diffEntryName);
cannotIncludeMessage = loc.cannotIncludeMessage(diffEntryName);
}
vscode.window.showWarningMessage(checkboxState.checked ? cannotIncludeMessage : cannotExcludeMessage);
} else {
vscode.window.showWarningMessage(result.errorMessage);
}
// set checkbox back to previous state
checkboxesToChange.push({ row: checkboxState.row, column: checkboxState.column, columnName: 'Include', checked: !checkboxState.checked });
}
if (checkboxesToChange.length > 0) {
this.differencesTable.updateCells = checkboxesToChange;
}
}
// save state based on source name if present otherwise target name (parity with SSDT)
private saveExcludeState(rowState: azdata.ICheckboxCellActionEventArgs) {
if (rowState) {
if (this.differencesTable.data[rowState.row]?.length > 2) {
this.differencesTable.data[rowState.row][2] = rowState.checked;
}
this.differencesTable.data[rowState.row][2] = rowState.checked;
let diff = this.comparisonResult.differences[rowState.row];
let key = (diff.sourceValue && diff.sourceValue.length > 0) ? this.createName(diff.sourceValue) : this.createName(diff.targetValue);
if (key) {
@@ -636,7 +647,7 @@ export class SchemaCompareMainWindow {
});
}
public async cancelCompare() {
private async cancelCompare() {
TelemetryReporter.createActionEvent(TelemetryViews.SchemaCompareMainWindow, 'SchemaCompareCancelStarted')
.withAdditionalProperties({
@@ -680,30 +691,26 @@ export class SchemaCompareMainWindow {
}).component();
this.generateScriptButton.onDidClick(async (click) => {
await this.generateScript();
});
}
public async generateScript(): Promise<void> {
TelemetryReporter.createActionEvent(TelemetryViews.SchemaCompareMainWindow, 'SchemaCompareGenerateScriptStarted')
.withAdditionalProperties({
'startTime': Date.now().toString(),
'operationId': this.comparisonResult.operationId
}).send();
const service = await this.getService();
const result = await service.schemaCompareGenerateScript(this.comparisonResult.operationId, this.targetEndpointInfo.serverName, this.targetEndpointInfo.databaseName, azdata.TaskExecutionMode.script);
if (!result || !result.success) {
TelemetryReporter.createErrorEvent(TelemetryViews.SchemaCompareMainWindow, 'SchemaCompareGenerateScriptFailed', undefined, getTelemetryErrorType(result.errorMessage))
TelemetryReporter.createActionEvent(TelemetryViews.SchemaCompareMainWindow, 'SchemaCompareGenerateScriptStarted')
.withAdditionalProperties({
'startTime': Date.now().toString(),
'operationId': this.comparisonResult.operationId
}).send();
vscode.window.showErrorMessage(loc.generateScriptErrorMessage(result.errorMessage));
}
TelemetryReporter.createActionEvent(TelemetryViews.SchemaCompareMainWindow, 'SchemaCompareGenerateScriptEnded')
.withAdditionalProperties({
'endTime': Date.now().toString(),
'operationId': this.comparisonResult.operationId
}).send();
const service = await this.getService();
const result = await service.schemaCompareGenerateScript(this.comparisonResult.operationId, this.targetEndpointInfo.serverName, this.targetEndpointInfo.databaseName, azdata.TaskExecutionMode.script);
if (!result || !result.success) {
TelemetryReporter.createErrorEvent(TelemetryViews.SchemaCompareMainWindow, 'SchemaCompareGenerateScriptFailed', undefined, getTelemetryErrorType(result.errorMessage))
.withAdditionalProperties({
'operationId': this.comparisonResult.operationId
}).send();
vscode.window.showErrorMessage(loc.generateScriptErrorMessage(result.errorMessage));
}
TelemetryReporter.createActionEvent(TelemetryViews.SchemaCompareMainWindow, 'SchemaCompareGenerateScriptEnded')
.withAdditionalProperties({
'endTime': Date.now().toString(),
'operationId': this.comparisonResult.operationId
}).send();
});
}
private createOptionsButton(view: azdata.ModelView) {
@@ -734,47 +741,43 @@ export class SchemaCompareMainWindow {
},
}).component();
this.applyButton.onDidClick(async (click) => {
await this.publishChanges();
});
}
public async publishChanges(): Promise<void> {
// need only yes button - since the modal dialog has a default cancel
const yesString = loc.YesButtonText;
await vscode.window.showWarningMessage(loc.applyConfirmation, { modal: true }, yesString).then(async (result) => {
if (result === yesString) {
TelemetryReporter.createActionEvent(TelemetryViews.SchemaCompareMainWindow, 'SchemaCompareApplyStarted')
.withAdditionalProperties({
'startTime': Date.now().toString(),
'operationId': this.comparisonResult.operationId
}).send();
this.applyButton.onDidClick(async (click) => {
// disable apply and generate script buttons because the results are no longer valid after applying the changes
this.setButtonsForRecompare();
const service = await this.getService();
const result = await service.schemaComparePublishChanges(this.comparisonResult.operationId, this.targetEndpointInfo.serverName, this.targetEndpointInfo.databaseName, azdata.TaskExecutionMode.execute);
if (!result || !result.success) {
TelemetryReporter.createErrorEvent(TelemetryViews.SchemaCompareMainWindow, 'SchemaCompareApplyFailed', undefined, getTelemetryErrorType(result.errorMessage))
vscode.window.showWarningMessage(loc.applyConfirmation, { modal: true }, yesString).then(async (result) => {
if (result === yesString) {
TelemetryReporter.createActionEvent(TelemetryViews.SchemaCompareMainWindow, 'SchemaCompareApplyStarted')
.withAdditionalProperties({
'startTime': Date.now().toString(),
'operationId': this.comparisonResult.operationId
}).send();
vscode.window.showErrorMessage(loc.applyErrorMessage(result.errorMessage));
// reenable generate script and apply buttons if apply failed
this.generateScriptButton.enabled = true;
this.generateScriptButton.title = loc.generateScriptEnabledMessage;
this.applyButton.enabled = true;
this.applyButton.title = loc.applyEnabledMessage;
// disable apply and generate script buttons because the results are no longer valid after applying the changes
this.setButtonsForRecompare();
const service = await this.getService();
const result = await service.schemaComparePublishChanges(this.comparisonResult.operationId, this.targetEndpointInfo.serverName, this.targetEndpointInfo.databaseName, azdata.TaskExecutionMode.execute);
if (!result || !result.success) {
TelemetryReporter.createErrorEvent(TelemetryViews.SchemaCompareMainWindow, 'SchemaCompareApplyFailed', undefined, getTelemetryErrorType(result.errorMessage))
.withAdditionalProperties({
'operationId': this.comparisonResult.operationId
}).send();
vscode.window.showErrorMessage(loc.applyErrorMessage(result.errorMessage));
// reenable generate script and apply buttons if apply failed
this.generateScriptButton.enabled = true;
this.generateScriptButton.title = loc.generateScriptEnabledMessage;
this.applyButton.enabled = true;
this.applyButton.title = loc.applyEnabledMessage;
}
TelemetryReporter.createActionEvent(TelemetryViews.SchemaCompareMainWindow, 'SchemaCompareApplyEnded')
.withAdditionalProperties({
'endTime': Date.now().toString(),
'operationId': this.comparisonResult.operationId
}).send();
}
TelemetryReporter.createActionEvent(TelemetryViews.SchemaCompareMainWindow, 'SchemaCompareApplyEnded')
.withAdditionalProperties({
'endTime': Date.now().toString(),
'operationId': this.comparisonResult.operationId
}).send();
}
});
});
}
@@ -853,13 +856,23 @@ export class SchemaCompareMainWindow {
[this.sourceName, this.targetName] = [this.targetName, this.sourceName];
this.sourceNameComponent.updateProperties({
value: this.sourceName,
title: this.sourceName
columns: [
{
value: this.sourceName,
headerCssClass: 'no-borders',
toolTip: this.sourceName
},
]
});
this.targetNameComponent.updateProperties({
value: this.targetName,
title: this.targetName
columns: [
{
value: this.targetName,
headerCssClass: 'no-borders',
toolTip: this.targetName
},
]
});
// remember that source target have been toggled
@@ -909,64 +922,60 @@ export class SchemaCompareMainWindow {
}).component();
this.openScmpButton.onDidClick(async (click) => {
await this.openScmp();
TelemetryReporter.sendActionEvent(TelemetryViews.SchemaCompareMainWindow, 'SchemaCompareOpenScmpStarted');
const rootPath = getRootPath();
let fileUris = await vscode.window.showOpenDialog(
{
canSelectFiles: true,
canSelectFolders: false,
canSelectMany: false,
defaultUri: vscode.Uri.file(rootPath),
openLabel: loc.open,
filters: {
'scmp Files': ['scmp'],
}
}
);
if (!fileUris || fileUris.length === 0) {
return;
}
let fileUri = fileUris[0];
const service = await this.getService();
let startTime = Date.now();
const result = await service.schemaCompareOpenScmp(fileUri.fsPath);
if (!result || !result.success) {
TelemetryReporter.sendErrorEvent(TelemetryViews.SchemaCompareMainWindow, 'SchemaCompareOpenScmpFailed', undefined, getTelemetryErrorType(result.errorMessage));
vscode.window.showErrorMessage(loc.openScmpErrorMessage(result.errorMessage));
return;
}
this.sourceEndpointInfo = await this.constructEndpointInfo(result.sourceEndpointInfo, loc.sourceTitle, this.apiWrapper);
this.targetEndpointInfo = await this.constructEndpointInfo(result.targetEndpointInfo, loc.targetTitle, this.apiWrapper);
this.updateSourceAndTarget();
this.setDeploymentOptions(result.deploymentOptions);
this.scmpSourceExcludes = result.excludedSourceElements;
this.scmpTargetExcludes = result.excludedTargetElements;
this.sourceTargetSwitched = result.originalTargetName !== this.targetEndpointInfo.databaseName;
// clear out any old results
this.resetForNewCompare();
TelemetryReporter.createActionEvent(TelemetryViews.SchemaCompareMainWindow, 'SchemaCompareOpenScmpEnded')
.withAdditionalProperties({
elapsedTime: (Date.now() - startTime).toString()
}).send();
});
}
public async openScmp(): Promise<void> {
TelemetryReporter.sendActionEvent(TelemetryViews.SchemaCompareMainWindow, 'SchemaCompareOpenScmpStarted');
const rootPath = getRootPath();
let fileUris = await vscode.window.showOpenDialog(
{
canSelectFiles: true,
canSelectFolders: false,
canSelectMany: false,
defaultUri: vscode.Uri.file(rootPath),
openLabel: loc.open,
filters: {
'scmp Files': ['scmp'],
}
}
);
if (!fileUris || fileUris.length === 0) {
return;
}
let fileUri = fileUris[0];
const service = await this.getService();
let startTime = Date.now();
const result = await service.schemaCompareOpenScmp(fileUri.fsPath);
if (!result || !result.success) {
TelemetryReporter.sendErrorEvent(TelemetryViews.SchemaCompareMainWindow, 'SchemaCompareOpenScmpFailed', undefined, getTelemetryErrorType(result.errorMessage));
vscode.window.showErrorMessage(loc.openScmpErrorMessage(result.errorMessage));
return;
}
this.sourceEndpointInfo = await this.constructEndpointInfo(result.sourceEndpointInfo, loc.sourceTitle);
this.targetEndpointInfo = await this.constructEndpointInfo(result.targetEndpointInfo, loc.targetTitle);
this.updateSourceAndTarget();
this.setDeploymentOptions(result.deploymentOptions);
this.scmpSourceExcludes = result.excludedSourceElements;
this.scmpTargetExcludes = result.excludedTargetElements;
this.sourceTargetSwitched = result.originalTargetName !== this.targetEndpointInfo.databaseName;
// clear out any old results
this.resetForNewCompare();
TelemetryReporter.createActionEvent(TelemetryViews.SchemaCompareMainWindow, 'SchemaCompareOpenScmpEnded')
.withAdditionalProperties({
elapsedTime: (Date.now() - startTime).toString()
}).send();
}
private async constructEndpointInfo(endpoint: mssql.SchemaCompareEndpointInfo, caller: string): Promise<mssql.SchemaCompareEndpointInfo> {
private async constructEndpointInfo(endpoint: mssql.SchemaCompareEndpointInfo, caller: string, apiWrapper: ApiWrapper): Promise<mssql.SchemaCompareEndpointInfo> {
let ownerUri;
let endpointInfo;
if (endpoint && endpoint.endpointType === mssql.SchemaCompareEndpointType.Database) {
// only set endpoint info if able to connect to the database
ownerUri = await verifyConnectionAndGetOwnerUri(endpoint, caller);
ownerUri = await verifyConnectionAndGetOwnerUri(endpoint, caller, apiWrapper);
}
if (ownerUri) {
endpointInfo = endpoint;
@@ -998,46 +1007,42 @@ export class SchemaCompareMainWindow {
}).component();
this.saveScmpButton.onDidClick(async (click) => {
await this.saveScmp();
});
}
public async saveScmp(): Promise<void> {
const rootPath = getRootPath();
const filePath = await vscode.window.showSaveDialog(
{
defaultUri: vscode.Uri.file(rootPath),
saveLabel: loc.save,
filters: {
'scmp Files': ['scmp'],
const rootPath = getRootPath();
const filePath = await vscode.window.showSaveDialog(
{
defaultUri: vscode.Uri.file(rootPath),
saveLabel: loc.save,
filters: {
'scmp Files': ['scmp'],
}
}
);
if (!filePath) {
return;
}
);
if (!filePath) {
return;
}
// convert include/exclude maps to arrays of object ids
let sourceExcludes: mssql.SchemaCompareObjectId[] = this.convertExcludesToObjectIds(this.originalSourceExcludes);
let targetExcludes: mssql.SchemaCompareObjectId[] = this.convertExcludesToObjectIds(this.originalTargetExcludes);
// convert include/exclude maps to arrays of object ids
let sourceExcludes: mssql.SchemaCompareObjectId[] = this.convertExcludesToObjectIds(this.originalSourceExcludes);
let targetExcludes: mssql.SchemaCompareObjectId[] = this.convertExcludesToObjectIds(this.originalTargetExcludes);
let startTime = Date.now();
TelemetryReporter.sendActionEvent(TelemetryViews.SchemaCompareMainWindow, 'SchemaCompareSaveScmp');
const service = await this.getService();
const result = await service.schemaCompareSaveScmp(this.sourceEndpointInfo, this.targetEndpointInfo, azdata.TaskExecutionMode.execute, this.deploymentOptions, filePath.fsPath, sourceExcludes, targetExcludes);
if (!result || !result.success) {
TelemetryReporter.createErrorEvent(TelemetryViews.SchemaCompareMainWindow, 'SchemaCompareSaveScmpFailed', undefined, getTelemetryErrorType(result.errorMessage))
let startTime = Date.now();
TelemetryReporter.sendActionEvent(TelemetryViews.SchemaCompareMainWindow, 'SchemaCompareSaveScmp');
const service = await this.getService();
const result = await service.schemaCompareSaveScmp(this.sourceEndpointInfo, this.targetEndpointInfo, azdata.TaskExecutionMode.execute, this.deploymentOptions, filePath.fsPath, sourceExcludes, targetExcludes);
if (!result || !result.success) {
TelemetryReporter.createErrorEvent(TelemetryViews.SchemaCompareMainWindow, 'SchemaCompareSaveScmpFailed', undefined, getTelemetryErrorType(result.errorMessage))
.withAdditionalProperties({
operationId: this.comparisonResult.operationId
}).send();
vscode.window.showErrorMessage(loc.saveScmpErrorMessage(result.errorMessage));
}
TelemetryReporter.createActionEvent(TelemetryViews.SchemaCompareMainWindow, 'SchemaCompareSaveScmpEnded')
.withAdditionalProperties({
elapsedTime: (Date.now() - startTime).toString(),
operationId: this.comparisonResult.operationId
}).send();
vscode.window.showErrorMessage(loc.saveScmpErrorMessage(result.errorMessage));
}
TelemetryReporter.createActionEvent(TelemetryViews.SchemaCompareMainWindow, 'SchemaCompareSaveScmpEnded')
.withAdditionalProperties({
elapsedTime: (Date.now() - startTime).toString(),
operationId: this.comparisonResult.operationId
});
});
});
}
/**
@@ -1066,7 +1071,7 @@ export class SchemaCompareMainWindow {
}
private async getService(): Promise<mssql.ISchemaCompareService> {
if (this.schemaCompareService === null || this.schemaCompareService === undefined) {
if (isNullOrUndefined(this.schemaCompareService)) {
this.schemaCompareService = (vscode.extensions.getExtension(mssql.extension.name).exports as mssql.IExtension).schemaCompare;
}
return this.schemaCompareService;

View File

@@ -4,12 +4,13 @@
*--------------------------------------------------------------------------------------------*/
import * as should from 'should';
import * as azdata from 'azdata';
import * as vscode from 'vscode';
import * as mssql from '../../../mssql';
import * as TypeMoq from 'typemoq';
import * as loc from '../localizedConstants';
import 'mocha';
import * as sinon from 'sinon';
import { SchemaCompareDialog } from './../dialogs/schemaCompareDialog';
import { SchemaCompareMainWindow } from '../schemaCompareMainWindow';
import { SchemaCompareTestService, testStateScmp } from './testSchemaCompareService';
import { createContext, TestContext } from './testContext';
@@ -27,20 +28,15 @@ before(function (): void {
testContext = createContext();
});
describe('SchemaCompareMainWindow.start @DacFx@', function (): void {
describe('SchemaCompareMainWindow.start', function (): void {
before(() => {
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
mockExtensionContext.setup(x => x.extensionPath).returns(() => '');
});
this.afterEach(() => {
sinon.restore();
});
it('Should be correct when created.', async function (): Promise<void> {
let sc = new SchemaCompareTestService();
let result = new SchemaCompareMainWindowTest(sc, mockExtensionContext.object);
let result = new SchemaCompareMainWindowTest(testContext.apiWrapper.object, sc, mockExtensionContext.object);
await result.start(undefined);
should(result.getComparisonResult() === undefined);
@@ -56,7 +52,7 @@ describe('SchemaCompareMainWindow.start @DacFx@', function (): void {
it('Should start with the source as undefined', async function (): Promise<void> {
let sc = new SchemaCompareTestService();
let result = new SchemaCompareMainWindowTest(sc, mockExtensionContext.object);
let result = new SchemaCompareMainWindowTest(testContext.apiWrapper.object, sc, mockExtensionContext.object);
await result.start(undefined);
should.equal(result.sourceEndpointInfo, undefined);
@@ -66,8 +62,8 @@ describe('SchemaCompareMainWindow.start @DacFx@', function (): void {
it('Should start with the source as database', async function (): Promise<void> {
let sc = new SchemaCompareTestService();
let result = new SchemaCompareMainWindowTest(sc, mockExtensionContext.object);
await result.start({ connectionProfile: mockIConnectionProfile });
let result = new SchemaCompareMainWindowTest(testContext.apiWrapper.object, sc, mockExtensionContext.object);
await result.start({connectionProfile: mockIConnectionProfile});
should.notEqual(result.sourceEndpointInfo, undefined);
should.equal(result.sourceEndpointInfo.endpointType, mssql.SchemaCompareEndpointType.Database);
@@ -79,7 +75,7 @@ describe('SchemaCompareMainWindow.start @DacFx@', function (): void {
it('Should start with the source as dacpac.', async function (): Promise<void> {
let sc = new SchemaCompareTestService();
let result = new SchemaCompareMainWindowTest(sc, mockExtensionContext.object);
let result = new SchemaCompareMainWindowTest(testContext.apiWrapper.object, sc, mockExtensionContext.object);
const dacpacPath = mockFilePath;
await result.start(dacpacPath);
@@ -89,287 +85,7 @@ describe('SchemaCompareMainWindow.start @DacFx@', function (): void {
should.equal(result.targetEndpointInfo, undefined);
});
});
let showErrorMessageSpy: any;
let showWarningMessageStub: any;
let showOpenDialogStub: any;
describe('SchemaCompareMainWindow.results', function (): void {
before(() => {
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
mockExtensionContext.setup(x => x.extensionPath).returns(() => '');
});
this.afterEach(() => {
sinon.restore();
});
this.beforeEach(() => {
sinon.restore();
showErrorMessageSpy = sinon.spy(vscode.window, 'showErrorMessage');
});
it('Should show error if publish changes fails', async function (): Promise<void> {
let service = createServiceMock();
service.setup(x => x.schemaComparePublishChanges(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve({
success: false,
errorMessage: 'error1'
}));
showWarningMessageStub = sinon.stub(vscode.window, 'showWarningMessage').returns(<any>Promise.resolve('Yes'));
let schemaCompareResult = new SchemaCompareMainWindow(service.object, mockExtensionContext.object);
await schemaCompareResult.start(undefined);
schemaCompareResult.sourceEndpointInfo = setDacpacEndpointInfo(mocksource);
schemaCompareResult.targetEndpointInfo = setDacpacEndpointInfo(mocktarget);
await schemaCompareResult.execute();
await schemaCompareResult.publishChanges();
should(showErrorMessageSpy.calledOnce).be.true();
should.equal(showErrorMessageSpy.getCall(0).args[0], loc.applyErrorMessage('error1'));
});
it('Should show not error if publish changes succeed', async function (): Promise<void> {
let service = createServiceMock();
service.setup(x => x.schemaComparePublishChanges(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve({
success: true,
errorMessage: ''
}));
showWarningMessageStub = sinon.stub(vscode.window, 'showWarningMessage').returns(<any>Promise.resolve('Yes'));
let schemaCompareResult = new SchemaCompareMainWindow(service.object, mockExtensionContext.object);
await schemaCompareResult.start(undefined);
schemaCompareResult.sourceEndpointInfo = setDacpacEndpointInfo(mocksource);
schemaCompareResult.targetEndpointInfo = setDacpacEndpointInfo(mocktarget);
await schemaCompareResult.execute();
await schemaCompareResult.publishChanges();
should(showErrorMessageSpy.notCalled).be.true();
});
it('Should show error if openScmp fails', async function (): Promise<void> {
let service = createServiceMock();
let files: vscode.Uri[] = [vscode.Uri.parse('file:///test')];
service.setup(x => x.schemaCompareOpenScmp(TypeMoq.It.isAny())).returns(() => Promise.resolve({
sourceEndpointInfo: undefined,
targetEndpointInfo: undefined,
originalTargetName: 'string',
originalConnectionString: '',
deploymentOptions: undefined,
excludedSourceElements: [],
excludedTargetElements: [],
success: false,
errorMessage: 'error1'
}));
showWarningMessageStub = sinon.stub(vscode.window, 'showWarningMessage').returns(<any>Promise.resolve('Yes'));
showOpenDialogStub = sinon.stub(vscode.window, 'showOpenDialog').returns(<any>Promise.resolve(files));
let schemaCompareResult = new SchemaCompareMainWindow(service.object, mockExtensionContext.object);
await schemaCompareResult.start(undefined);
schemaCompareResult.sourceEndpointInfo = setDacpacEndpointInfo(mocksource);
schemaCompareResult.targetEndpointInfo = setDacpacEndpointInfo(mocktarget);
await schemaCompareResult.openScmp();
should(showErrorMessageSpy.calledOnce).be.true();
should.equal(showErrorMessageSpy.getCall(0).args[0], loc.openScmpErrorMessage('error1'));
});
it('Should show error if saveScmp fails', async function (): Promise<void> {
let service = createServiceMock();
let file: vscode.Uri = vscode.Uri.parse('file:///test');
service.setup(x => x.schemaCompareSaveScmp(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve({
sourceEndpointInfo: undefined,
targetEndpointInfo: undefined,
originalTargetName: 'string',
originalConnectionString: '',
deploymentOptions: undefined,
excludedSourceElements: [],
excludedTargetElements: [],
success: false,
errorMessage: 'error1'
}));
showWarningMessageStub = sinon.stub(vscode.window, 'showWarningMessage').returns(<any>Promise.resolve('Yes'));
showOpenDialogStub = sinon.stub(vscode.window, 'showSaveDialog').returns(<any>Promise.resolve(file));
let schemaCompareResult = new SchemaCompareMainWindow(service.object, mockExtensionContext.object);
await schemaCompareResult.start(undefined);
schemaCompareResult.sourceEndpointInfo = setDacpacEndpointInfo(mocksource);
schemaCompareResult.targetEndpointInfo = setDacpacEndpointInfo(mocktarget);
await schemaCompareResult.execute();
await schemaCompareResult.saveScmp();
should(showErrorMessageSpy.calledOnce).be.true();
should.equal(showErrorMessageSpy.getCall(0).args[0], loc.saveScmpErrorMessage('error1'));
});
it('Should show error if generateScript fails', async function (): Promise<void> {
let service = createServiceMock();
service.setup(x => x.schemaCompareGenerateScript(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve({
success: false,
errorMessage: 'error1'
}));
showWarningMessageStub = sinon.stub(vscode.window, 'showWarningMessage').returns(<any>Promise.resolve('Yes'));
let schemaCompareResult = new SchemaCompareMainWindow(service.object, mockExtensionContext.object);
await schemaCompareResult.start(undefined);
schemaCompareResult.sourceEndpointInfo = setDacpacEndpointInfo(mocksource);
schemaCompareResult.targetEndpointInfo = setDacpacEndpointInfo(mocktarget);
await schemaCompareResult.execute();
await schemaCompareResult.generateScript();
should(showErrorMessageSpy.calledOnce).be.true();
should.equal(showErrorMessageSpy.getCall(0).args[0], loc.generateScriptErrorMessage('error1'));
});
it('Should show error if cancel fails', async function (): Promise<void> {
let service = createServiceMock();
service.setup(x => x.schemaCompareCancel(TypeMoq.It.isAny())).returns(() => Promise.resolve({
success: false,
errorMessage: 'error1'
}));
showWarningMessageStub = sinon.stub(vscode.window, 'showWarningMessage').returns(<any>Promise.resolve('Yes'));
let schemaCompareResult = new SchemaCompareMainWindow(service.object, mockExtensionContext.object);
await schemaCompareResult.start(undefined);
schemaCompareResult.sourceEndpointInfo = setDacpacEndpointInfo(mocksource);
schemaCompareResult.targetEndpointInfo = setDacpacEndpointInfo(mocktarget);
await schemaCompareResult.execute();
await schemaCompareResult.cancelCompare();
should(showErrorMessageSpy.calledOnce).be.true();
should.equal(showErrorMessageSpy.getCall(0).args[0], loc.cancelErrorMessage('error1'));
});
it('Should show error if IncludeExcludeNode fails', async function (): Promise<void> {
let service = createServiceMock();
service.setup(x => x.schemaCompareIncludeExcludeNode(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve({
success: false,
errorMessage: '',
affectedDependencies: [],
blockingDependencies: [{
updateAction: 2,
differenceType: 0,
name: 'SqlTable',
sourceValue: ['dbo', 'table1'],
targetValue: null,
parent: null,
children: [{
updateAction: 2,
differenceType: 0,
name: 'SqlSimpleColumn',
sourceValue: ['dbo', 'table1', 'id'],
targetValue: null,
parent: null,
children: [],
sourceScript: '',
targetScript: null,
included: false
}],
sourceScript: 'CREATE TABLE [dbo].[table1](id int)',
targetScript: null,
included: true
}]
}));
showWarningMessageStub = sinon.stub(vscode.window, 'showWarningMessage').returns(<any>Promise.resolve(''));
let schemaCompareResult = new SchemaCompareMainWindow(service.object, mockExtensionContext.object);
await schemaCompareResult.start(undefined);
schemaCompareResult.sourceEndpointInfo = setDacpacEndpointInfo(mocksource);
schemaCompareResult.targetEndpointInfo = setDacpacEndpointInfo(mocktarget);
await schemaCompareResult.execute();
await schemaCompareResult.applyIncludeExclude({
row: 0,
column: 0,
columnName: 1,
checked: true
});
should(showWarningMessageStub.calledOnce).be.true();
});
it('Should not show warning if IncludeExcludeNode succeed', async function (): Promise<void> {
let service = createServiceMock();
service.setup(x => x.schemaCompareIncludeExcludeNode(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve({
success: true,
errorMessage: '',
affectedDependencies: [],
blockingDependencies: [{
updateAction: 2,
differenceType: 0,
name: 'SqlTable',
sourceValue: ['dbo', 'table1'],
targetValue: null,
parent: null,
children: [{
updateAction: 2,
differenceType: 0,
name: 'SqlSimpleColumn',
sourceValue: ['dbo', 'table1', 'id'],
targetValue: null,
parent: null,
children: [],
sourceScript: '',
targetScript: null,
included: false
}],
sourceScript: 'CREATE TABLE [dbo].[table1](id int)',
targetScript: null,
included: true
}]
}));
showWarningMessageStub = sinon.stub(vscode.window, 'showWarningMessage').returns(<any>Promise.resolve(''));
let schemaCompareResult = new SchemaCompareMainWindow(service.object, mockExtensionContext.object);
await schemaCompareResult.start(undefined);
schemaCompareResult.sourceEndpointInfo = setDacpacEndpointInfo(mocksource);
schemaCompareResult.targetEndpointInfo = setDacpacEndpointInfo(mocktarget);
await schemaCompareResult.execute();
await schemaCompareResult.applyIncludeExclude({
row: 0,
column: 0,
columnName: 1,
checked: false
});
should(showWarningMessageStub.notCalled).be.true();
});
it('Should not show error if user does not want to publish', async function (): Promise<void> {
let service = createServiceMock();
service.setup(x => x.schemaComparePublishChanges(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve({
success: true,
errorMessage: ''
}));
showWarningMessageStub = sinon.stub(vscode.window, 'showWarningMessage').returns(<any>Promise.resolve('No'));
let schemaCompareResult = new SchemaCompareMainWindow(service.object, mockExtensionContext.object);
await schemaCompareResult.start(undefined);
schemaCompareResult.sourceEndpointInfo = setDacpacEndpointInfo(mocksource);
schemaCompareResult.targetEndpointInfo = setDacpacEndpointInfo(mocktarget);
await schemaCompareResult.execute();
await schemaCompareResult.publishChanges();
should(showErrorMessageSpy.notCalled).be.true();
});
function createServiceMock() {
let sc = new SchemaCompareTestService(testStateScmp.SUCCESS_NOT_EQUAL);
let service = TypeMoq.Mock.ofInstance(new SchemaCompareTestService());
service.setup(x => x.schemaCompareGetDefaultOptions()).returns(x => sc.schemaCompareGetDefaultOptions());
service.setup(x => x.schemaCompare(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => sc.schemaCompare('', undefined, undefined, undefined, undefined));
return service;
}
});
let showErrorMessageStub: any;
describe('SchemaCompareMainWindow.execute', function (): void {
before(() => {
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
@@ -377,22 +93,16 @@ describe('SchemaCompareMainWindow.execute', function (): void {
testContext = createContext();
});
this.afterEach(() => {
sinon.restore();
});
this.beforeEach(() => {
sinon.restore();
beforeEach(function (): void {
testContext.apiWrapper.reset();
});
it('Should fail for failing Schema Compare service', async function (): Promise<void> {
let sc = new SchemaCompareTestService(testStateScmp.FAILURE);
showErrorMessageStub = sinon.stub(vscode.window, 'showErrorMessage').callsFake((message) => {
throw new Error(message);
});
testContext.apiWrapper.setup(x => x.showErrorMessage(TypeMoq.It.isAny())).returns((s) => { throw new Error(s); });
let result = new SchemaCompareMainWindowTest(testContext.apiWrapper.object, sc, mockExtensionContext.object);
let result = new SchemaCompareMainWindowTest(sc, mockExtensionContext.object);
await result.start(undefined);
should(result.getComparisonResult() === undefined);
@@ -406,7 +116,8 @@ describe('SchemaCompareMainWindow.execute', function (): void {
it('Should exit for failing Schema Compare service', async function (): Promise<void> {
let sc = new SchemaCompareTestService(testStateScmp.FAILURE);
let result = new SchemaCompareMainWindowTest(sc, mockExtensionContext.object);
testContext.apiWrapper.setup(x => x.showErrorMessage(TypeMoq.It.isAny())).returns(() => Promise.resolve(''));
let result = new SchemaCompareMainWindowTest(testContext.apiWrapper.object, sc, mockExtensionContext.object);
await result.start(undefined);
@@ -416,12 +127,13 @@ describe('SchemaCompareMainWindow.execute', function (): void {
result.targetEndpointInfo = setDacpacEndpointInfo(mocktarget);
await result.execute();
testContext.apiWrapper.verify(x => x.showErrorMessage(TypeMoq.It.isAny()), TypeMoq.Times.once());
});
it('Should disable script button and apply button for Schema Compare service for dacpac', async function (): Promise<void> {
let sc = new SchemaCompareTestService(testStateScmp.SUCCESS_NOT_EQUAL);
let result = new SchemaCompareMainWindowTest(sc, mockExtensionContext.object);
let result = new SchemaCompareMainWindowTest(testContext.apiWrapper.object, sc, mockExtensionContext.object);
await result.start(undefined);
@@ -433,7 +145,7 @@ describe('SchemaCompareMainWindow.execute', function (): void {
await result.execute();
//Generate script button and apply button should be disabled for dacpac comparison
result.verifyButtonsState({
result.verifyButtonsState( {
compareButtonState: true,
optionsButtonState: true,
switchButtonState: true,
@@ -444,13 +156,13 @@ describe('SchemaCompareMainWindow.execute', function (): void {
selectTargetButtonState: true,
generateScriptButtonState: false,
applyButtonState: false
});
} );
});
it('Should disable script button and apply button for Schema Compare service for database', async function (): Promise<void> {
let sc = new SchemaCompareTestService(testStateScmp.SUCCESS_NOT_EQUAL);
let result = new SchemaCompareMainWindowTest(sc, mockExtensionContext.object);
let result = new SchemaCompareMainWindowTest(testContext.apiWrapper.object, sc, mockExtensionContext.object);
await result.start(undefined);
@@ -462,7 +174,7 @@ describe('SchemaCompareMainWindow.execute', function (): void {
await result.execute();
//Generate script button and apply button should be enabled for database comparison
result.verifyButtonsState({
result.verifyButtonsState( {
compareButtonState: true,
optionsButtonState: true,
switchButtonState: true,
@@ -473,7 +185,7 @@ describe('SchemaCompareMainWindow.execute', function (): void {
selectTargetButtonState: true,
generateScriptButtonState: true,
applyButtonState: true
});
} );
});
});
@@ -489,18 +201,18 @@ describe('SchemaCompareMainWindow.updateSourceAndTarget', function (): void {
let sc = new SchemaCompareTestService();
let endpointInfo: mssql.SchemaCompareEndpointInfo;
let result = new SchemaCompareMainWindowTest(sc, mockExtensionContext.object);
let result = new SchemaCompareMainWindowTest(testContext.apiWrapper.object, sc, mockExtensionContext.object);
await result.start(undefined);
should(result.getComparisonResult() === undefined);
result.sourceEndpointInfo = { ...endpointInfo };
result.targetEndpointInfo = { ...endpointInfo };
result.sourceEndpointInfo = {...endpointInfo};
result.targetEndpointInfo = {...endpointInfo};
result.updateSourceAndTarget();
result.verifyButtonsState({
result.verifyButtonsState( {
compareButtonState: false,
optionsButtonState: false,
switchButtonState: false,
@@ -511,25 +223,25 @@ describe('SchemaCompareMainWindow.updateSourceAndTarget', function (): void {
selectTargetButtonState: true,
generateScriptButtonState: false,
applyButtonState: false
});
} );
});
it('Should set buttons appropriately when source endpoint is empty and target endpoint is populated', async function (): Promise<void> {
let sc = new SchemaCompareTestService();
let endpointInfo: mssql.SchemaCompareEndpointInfo;
let result = new SchemaCompareMainWindowTest(sc, mockExtensionContext.object);
let result = new SchemaCompareMainWindowTest(testContext.apiWrapper.object, sc, mockExtensionContext.object);
await result.start(undefined);
should(result.getComparisonResult() === undefined);
result.sourceEndpointInfo = { ...endpointInfo };
result.sourceEndpointInfo = {...endpointInfo};
result.targetEndpointInfo = setDacpacEndpointInfo(mocktarget);
result.updateSourceAndTarget();
result.verifyButtonsState({
result.verifyButtonsState( {
compareButtonState: false,
optionsButtonState: false,
switchButtonState: true,
@@ -540,14 +252,14 @@ describe('SchemaCompareMainWindow.updateSourceAndTarget', function (): void {
selectTargetButtonState: true,
generateScriptButtonState: false,
applyButtonState: false
});
} );
});
it('Should set buttons appropriately when source and target endpoints are populated', async function (): Promise<void> {
let sc = new SchemaCompareTestService();
let endpointInfo: mssql.SchemaCompareEndpointInfo;
let result = new SchemaCompareMainWindowTest(sc, mockExtensionContext.object);
let result = new SchemaCompareMainWindowTest(testContext.apiWrapper.object, sc, mockExtensionContext.object);
await result.start(undefined);
@@ -558,7 +270,7 @@ describe('SchemaCompareMainWindow.updateSourceAndTarget', function (): void {
result.updateSourceAndTarget();
result.verifyButtonsState({
result.verifyButtonsState( {
compareButtonState: true,
optionsButtonState: true,
switchButtonState: true,
@@ -569,7 +281,7 @@ describe('SchemaCompareMainWindow.updateSourceAndTarget', function (): void {
selectTargetButtonState: true,
generateScriptButtonState: false,
applyButtonState: false
});
} );
});
});

View File

@@ -25,14 +25,14 @@ before(function (): void {
testContext = createContext();
});
describe('SchemaCompareDialog.openDialog @DacFx@', function (): void {
describe('SchemaCompareDialog.openDialog', function (): void {
before(() => {
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
mockExtensionContext.setup(x => x.extensionPath).returns(() => '');
});
it('Should be correct when created.', async function (): Promise<void> {
let schemaCompareResult = new SchemaCompareMainWindow(undefined, mockExtensionContext.object);
let schemaCompareResult = new SchemaCompareMainWindow(testContext.apiWrapper.object, undefined, mockExtensionContext.object);
let dialog = new SchemaCompareDialog(schemaCompareResult);
await dialog.openDialog();
@@ -42,7 +42,7 @@ describe('SchemaCompareDialog.openDialog @DacFx@', function (): void {
});
it('Simulate ok button- with both endpoints set to dacpac', async function (): Promise<void> {
let schemaCompareResult = new SchemaCompareMainWindowTest(undefined, mockExtensionContext.object);
let schemaCompareResult = new SchemaCompareMainWindowTest(testContext.apiWrapper.object, undefined, mockExtensionContext.object);
await schemaCompareResult.start(undefined);
schemaCompareResult.sourceEndpointInfo = setDacpacEndpointInfo(mocksource);
schemaCompareResult.targetEndpointInfo = setDacpacEndpointInfo(mocktarget);
@@ -53,7 +53,7 @@ describe('SchemaCompareDialog.openDialog @DacFx@', function (): void {
await dialog.execute();
// Confirm that ok button got clicked
schemaCompareResult.verifyButtonsState({
schemaCompareResult.verifyButtonsState( {
compareButtonState: true,
optionsButtonState: true,
switchButtonState: true,
@@ -64,6 +64,6 @@ describe('SchemaCompareDialog.openDialog @DacFx@', function (): void {
selectTargetButtonState: true,
generateScriptButtonState: false,
applyButtonState: false
});
} );
});
});

View File

@@ -5,8 +5,11 @@
import * as vscode from 'vscode';
import * as path from 'path';
import * as TypeMoq from 'typemoq';
import { ApiWrapper } from '../common/apiWrapper';
export interface TestContext {
apiWrapper: TypeMoq.IMock<ApiWrapper>;
context: vscode.ExtensionContext;
}
@@ -14,6 +17,7 @@ export function createContext(): TestContext {
let extensionPath = path.join(__dirname, '..', '..');
return {
apiWrapper: TypeMoq.Mock.ofType(ApiWrapper),
context: {
subscriptions: [],
workspaceState: {

View File

@@ -7,6 +7,7 @@ import * as vscode from 'vscode';
import * as mssql from '../../../mssql';
import * as should from 'should';
import { SchemaCompareMainWindow } from '../schemaCompareMainWindow';
import { ApiWrapper } from '../common/apiWrapper';
export interface ButtonState {
compareButtonState: boolean;
@@ -23,9 +24,10 @@ export interface ButtonState {
export class SchemaCompareMainWindowTest extends SchemaCompareMainWindow {
constructor(
apiWrapper: ApiWrapper,
schemaCompareService: mssql.ISchemaCompareService,
extensionContext: vscode.ExtensionContext) {
super(schemaCompareService, extensionContext);
super(apiWrapper, schemaCompareService, extensionContext);
}
// only for test

View File

@@ -47,7 +47,7 @@ export class SchemaCompareTestService implements mssql.ISchemaCompareService {
throw new Error('Method not implemented.');
}
schemaCompare(operationId: string, sourceEndpointInfo: mssql.SchemaCompareEndpointInfo, targetEndpointInfo: mssql.SchemaCompareEndpointInfo, taskExecutionMode: azdata.TaskExecutionMode, deploymentOptions: mssql.DeploymentOptions): Thenable<mssql.SchemaCompareResult> {
schemaCompare(operationId: string, sourceEndpointInfo: mssql.SchemaCompareEndpointInfo, targetEndpointInfo: mssql.SchemaCompareEndpointInfo, taskExecutionMode: azdata.TaskExecutionMode): Thenable<mssql.SchemaCompareResult> {
let result: mssql.SchemaCompareResult;
if (this.testState === testStateScmp.FAILURE) {
result = {

View File

@@ -5,7 +5,6 @@
import * as should from 'should';
import * as azdata from 'azdata';
import * as vscode from 'vscode';
import * as mssql from '../../../mssql';
import * as loc from '../localizedConstants';
import * as TypeMoq from 'typemoq';
@@ -16,15 +15,10 @@ import { promises as fs } from 'fs';
import { getEndpointName, verifyConnectionAndGetOwnerUri, exists } from '../utils';
import { mockDacpacEndpoint, mockDatabaseEndpoint, mockFilePath, mockConnectionInfo, shouldThrowSpecificError, mockConnectionResult, mockConnectionProfile } from './testUtils';
import { createContext, TestContext } from './testContext';
import * as sinon from 'sinon';
let testContext: TestContext;
describe('utils: Tests to verify getEndpointName @DacFx@', function (): void {
afterEach(() => {
sinon.restore();
});
describe('utils: Tests to verify getEndpointName', function (): void {
it('Should generate correct endpoint information', () => {
let endpointInfo: mssql.SchemaCompareEndpointInfo;
@@ -58,7 +52,7 @@ describe('utils: Basic tests to verify verifyConnectionAndGetOwnerUri', function
it('Should return undefined for endpoint as dacpac', async function (): Promise<void> {
let ownerUri = undefined;
ownerUri = await verifyConnectionAndGetOwnerUri(mockDacpacEndpoint, 'test');
ownerUri = await verifyConnectionAndGetOwnerUri(mockDacpacEndpoint, 'test', testContext.apiWrapper.object);
should(ownerUri).equal(undefined);
});
@@ -68,7 +62,7 @@ describe('utils: Basic tests to verify verifyConnectionAndGetOwnerUri', function
let testDatabaseEndpoint: mssql.SchemaCompareEndpointInfo = { ...mockDatabaseEndpoint };
testDatabaseEndpoint.connectionDetails = undefined;
ownerUri = await verifyConnectionAndGetOwnerUri(testDatabaseEndpoint, 'test');
ownerUri = await verifyConnectionAndGetOwnerUri(testDatabaseEndpoint, 'test', testContext.apiWrapper.object);
should(ownerUri).equal(undefined);
});
@@ -79,10 +73,6 @@ describe('utils: In-depth tests to verify verifyConnectionAndGetOwnerUri', funct
testContext = createContext();
});
afterEach(() => {
sinon.restore();
});
it('Should throw an error asking to make a connection', async function (): Promise<void> {
let getConnectionsResults: azdata.connection.ConnectionProfile[] = [];
let connection = { ...mockConnectionResult };
@@ -90,14 +80,12 @@ describe('utils: In-depth tests to verify verifyConnectionAndGetOwnerUri', funct
testDatabaseEndpoint.connectionDetails = { ...mockConnectionInfo };
const getConnectionString = loc.getConnectionString('test');
sinon.stub(azdata.connection, 'connect').returns(<any>Promise.resolve(connection));
sinon.stub(azdata.connection, 'getUriForConnection').returns(<any>Promise.resolve(undefined));
sinon.stub(azdata.connection, 'getConnections').returns(<any>Promise.resolve(getConnectionsResults));
sinon.stub(vscode.window, 'showWarningMessage').callsFake((message) => {
throw new Error(message);
});
testContext.apiWrapper.setup(x => x.connect(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => { return Promise.resolve(connection); });
testContext.apiWrapper.setup(x => x.getUriForConnection(TypeMoq.It.isAny())).returns(() => { return Promise.resolve(undefined); });
testContext.apiWrapper.setup(x => x.getConnections(TypeMoq.It.isAny())).returns(() => { return Promise.resolve(getConnectionsResults); });
testContext.apiWrapper.setup(x => x.showWarningMessage(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns((s) => { throw new Error(s); });
await shouldThrowSpecificError(async () => await verifyConnectionAndGetOwnerUri(testDatabaseEndpoint, 'test'), getConnectionString);
await shouldThrowSpecificError(async () => await verifyConnectionAndGetOwnerUri(testDatabaseEndpoint, 'test', testContext.apiWrapper.object), getConnectionString);
});
it('Should throw an error for login failure', async function (): Promise<void> {
@@ -106,15 +94,12 @@ describe('utils: In-depth tests to verify verifyConnectionAndGetOwnerUri', funct
let testDatabaseEndpoint: mssql.SchemaCompareEndpointInfo = { ...mockDatabaseEndpoint };
testDatabaseEndpoint.connectionDetails = { ...mockConnectionInfo };
sinon.stub(azdata.connection, 'connect').returns(<any>Promise.resolve(connection));
sinon.stub(azdata.connection, 'getUriForConnection').returns(<any>Promise.resolve(undefined));
sinon.stub(azdata.connection, 'getConnections').returns(<any>Promise.resolve(getConnectionsResults));
sinon.stub(vscode.window, 'showWarningMessage').returns(<any>Promise.resolve(loc.YesButtonText));
sinon.stub(vscode.window, 'showErrorMessage').callsFake((message) => {
throw new Error(message);
});
testContext.apiWrapper.setup(x => x.connect(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => { return Promise.resolve(connection); });
testContext.apiWrapper.setup(x => x.getUriForConnection(TypeMoq.It.isAny())).returns(() => { return Promise.resolve(undefined); });
testContext.apiWrapper.setup(x => x.getConnections(TypeMoq.It.isAny())).returns(() => { return Promise.resolve(getConnectionsResults); });
testContext.apiWrapper.setup(x => x.showErrorMessage(TypeMoq.It.isAny())).returns((s) => { throw new Error(s); });
await shouldThrowSpecificError(async () => await verifyConnectionAndGetOwnerUri(testDatabaseEndpoint, 'test'), connection.errorMessage);
await shouldThrowSpecificError(async () => await verifyConnectionAndGetOwnerUri(testDatabaseEndpoint, 'test', testContext.apiWrapper.object), connection.errorMessage);
});
it('Should throw an error for login failure with openConnectionDialog but no ownerUri', async function (): Promise<void> {
@@ -123,18 +108,13 @@ describe('utils: In-depth tests to verify verifyConnectionAndGetOwnerUri', funct
let testDatabaseEndpoint: mssql.SchemaCompareEndpointInfo = { ...mockDatabaseEndpoint };
testDatabaseEndpoint.connectionDetails = { ...mockConnectionInfo };
sinon.stub(azdata.connection, 'connect').returns(<any>Promise.resolve(connection));
sinon.stub(azdata.connection, 'getUriForConnection').returns(<any>Promise.resolve(undefined));
sinon.stub(azdata.connection, 'openConnectionDialog').returns(<any>Promise.resolve({
connectionId: 'id'
}));
sinon.stub(azdata.connection, 'getConnections').returns(<any>Promise.resolve(getConnectionsResults));
sinon.stub(vscode.window, 'showWarningMessage').returns(<any>Promise.resolve(loc.YesButtonText));
sinon.stub(vscode.window, 'showErrorMessage').callsFake((message) => {
throw new Error(message);
});
testContext.apiWrapper.setup(x => x.connect(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => { return Promise.resolve(connection); });
testContext.apiWrapper.setup(x => x.getUriForConnection(TypeMoq.It.isAny())).returns(() => { return Promise.resolve(undefined); });
testContext.apiWrapper.setup(x => x.getConnections(TypeMoq.It.isAny())).returns(() => { return Promise.resolve(getConnectionsResults); });
testContext.apiWrapper.setup(x => x.showWarningMessage(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => { return Promise.resolve(loc.YesButtonText); });
testContext.apiWrapper.setup(x => x.openConnectionDialog(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => { return Promise.resolve(undefined); });
await shouldThrowSpecificError(async () => await verifyConnectionAndGetOwnerUri(testDatabaseEndpoint, 'test'), connection.errorMessage);
await shouldThrowSpecificError(async () => await verifyConnectionAndGetOwnerUri(testDatabaseEndpoint, 'test', testContext.apiWrapper.object), connection.errorMessage);
});
it('Should not throw an error and set ownerUri appropriately', async function (): Promise<void> {
@@ -144,10 +124,10 @@ describe('utils: In-depth tests to verify verifyConnectionAndGetOwnerUri', funct
let expectedOwnerUri: string = 'providerName:MSSQL|authenticationType:SqlLogin|database:My Database|server:My Server|user:My User|databaseDisplayName:My Database';
testDatabaseEndpoint.connectionDetails = { ...mockConnectionInfo };
sinon.stub(azdata.connection, 'connect').returns(<any>Promise.resolve(connection));
sinon.stub(azdata.connection, 'getUriForConnection').returns(<any>Promise.resolve(expectedOwnerUri));
testContext.apiWrapper.setup(x => x.connect(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => { return Promise.resolve(connection); });
testContext.apiWrapper.setup(x => x.getUriForConnection(TypeMoq.It.isAny())).returns(() => { return Promise.resolve(expectedOwnerUri); });
ownerUri = await verifyConnectionAndGetOwnerUri(testDatabaseEndpoint, 'test');
ownerUri = await verifyConnectionAndGetOwnerUri(testDatabaseEndpoint, 'test', testContext.apiWrapper.object);
should(ownerUri).equal(expectedOwnerUri);
});

View File

@@ -8,6 +8,7 @@ import * as vscode from 'vscode';
import * as mssql from '../../mssql';
import * as os from 'os';
import * as loc from './localizedConstants';
import { ApiWrapper } from './common/apiWrapper';
import { promises as fs } from 'fs';
export interface IPackageInfo {
@@ -31,7 +32,7 @@ export function getPackageInfo(packageJson: any): IPackageInfo {
* @param msg The error message to map
*/
export function getTelemetryErrorType(msg: string): string {
if (msg && msg.indexOf('Object reference not set to an instance of an object') !== -1) {
if (msg.indexOf('Object reference not set to an instance of an object') !== -1) {
return 'ObjectReferenceNotSet';
}
else {
@@ -84,18 +85,18 @@ function connectionInfoToConnectionProfile(details: azdata.ConnectionInfo): azda
};
}
export async function verifyConnectionAndGetOwnerUri(endpoint: mssql.SchemaCompareEndpointInfo, caller: string): Promise<string | undefined> {
export async function verifyConnectionAndGetOwnerUri(endpoint: mssql.SchemaCompareEndpointInfo, caller: string, apiWrapper: ApiWrapper): Promise<string | undefined> {
let ownerUri = undefined;
if (endpoint.endpointType === mssql.SchemaCompareEndpointType.Database && endpoint.connectionDetails) {
let connectionProfile = await connectionInfoToConnectionProfile(endpoint.connectionDetails);
let connection = await azdata.connection.connect(connectionProfile, false, false);
let connection = await apiWrapper.connect(connectionProfile, false, false);
if (connection) {
ownerUri = await azdata.connection.getUriForConnection(connection.connectionId);
ownerUri = await apiWrapper.getUriForConnection(connection.connectionId);
if (!ownerUri) {
let connectionList = await azdata.connection.getConnections(true);
let connectionList = await apiWrapper.getConnections(true);
let userConnection;
userConnection = connectionList.find(connection =>
@@ -108,18 +109,18 @@ export async function verifyConnectionAndGetOwnerUri(endpoint: mssql.SchemaCompa
if (userConnection === undefined) {
const getConnectionString = loc.getConnectionString(caller);
// need only yes button - since the modal dialog has a default cancel
let result = await vscode.window.showWarningMessage(getConnectionString, { modal: true }, loc.YesButtonText);
let result = await apiWrapper.showWarningMessage(getConnectionString, { modal: true }, loc.YesButtonText);
if (result === loc.YesButtonText) {
userConnection = await azdata.connection.openConnectionDialog(undefined, connectionProfile);
userConnection = await apiWrapper.openConnectionDialog(undefined, connectionProfile);
}
}
if (userConnection !== undefined) {
ownerUri = await azdata.connection.getUriForConnection(userConnection.connectionId);
ownerUri = await apiWrapper.getUriForConnection(userConnection.connectionId);
}
}
if (!ownerUri && connection.errorMessage) {
vscode.window.showErrorMessage(connection.errorMessage);
apiWrapper.showErrorMessage(connection.errorMessage);
}
}
}

View File

@@ -182,42 +182,6 @@
resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.2.tgz#26520bf09abe4a5644cd5414e37125a8954241dd"
integrity sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==
"@sinonjs/commons@^1", "@sinonjs/commons@^1.6.0", "@sinonjs/commons@^1.7.0", "@sinonjs/commons@^1.8.1":
version "1.8.1"
resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.1.tgz#e7df00f98a203324f6dc7cc606cad9d4a8ab2217"
integrity sha512-892K+kWUUi3cl+LlqEWIDrhvLgdL79tECi8JZUyq6IviKy/DNhuzCRlbHUjxK89f4ypPMMaFnFuR9Ie6DoIMsw==
dependencies:
type-detect "4.0.8"
"@sinonjs/fake-timers@^6.0.0", "@sinonjs/fake-timers@^6.0.1":
version "6.0.1"
resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz#293674fccb3262ac782c7aadfdeca86b10c75c40"
integrity sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==
dependencies:
"@sinonjs/commons" "^1.7.0"
"@sinonjs/formatio@^5.0.1":
version "5.0.1"
resolved "https://registry.yarnpkg.com/@sinonjs/formatio/-/formatio-5.0.1.tgz#f13e713cb3313b1ab965901b01b0828ea6b77089"
integrity sha512-KaiQ5pBf1MpS09MuA0kp6KBQt2JUOQycqVG1NZXvzeaXe5LGFqAKueIS0bw4w0P9r7KuBSVdUk5QjXsUdu2CxQ==
dependencies:
"@sinonjs/commons" "^1"
"@sinonjs/samsam" "^5.0.2"
"@sinonjs/samsam@^5.0.2", "@sinonjs/samsam@^5.2.0":
version "5.3.0"
resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-5.3.0.tgz#1d2f0743dc54bf13fe9d508baefacdffa25d4329"
integrity sha512-hXpcfx3aq+ETVBwPlRFICld5EnrkexXuXDwqUNhDdr5L8VjvMeSRwyOa0qL7XFmR+jVWR4rUZtnxlG7RX72sBg==
dependencies:
"@sinonjs/commons" "^1.6.0"
lodash.get "^4.4.2"
type-detect "^4.0.8"
"@sinonjs/text-encoding@^0.7.1":
version "0.7.1"
resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz#8da5c6530915653f3a1f38fd5f101d8c3f8079c5"
integrity sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==
"@types/mocha@^5.2.5":
version "5.2.6"
resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-5.2.6.tgz#b8622d50557dd155e9f2f634b7d68fd38de5e94b"
@@ -228,18 +192,6 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.7.tgz#01e4ea724d9e3bd50d90c11fd5980ba317d8fa11"
integrity sha512-E6Zn0rffhgd130zbCbAr/JdXfXkoOUFAKNs/rF8qnafSJ8KYaA/j3oz7dcwal+lYjLA7xvdd5J4wdYpCTlP8+w==
"@types/sinon@^9.0.4":
version "9.0.9"
resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-9.0.9.tgz#115843b491583f924080f684b6d0d7438344f73c"
integrity sha512-z/y8maYOQyYLyqaOB+dYQ6i0pxKLOsfwCmHmn4T7jS/SDHicIslr37oE3Dg8SCqKrKeBy6Lemu7do2yy+unLrw==
dependencies:
"@types/sinonjs__fake-timers" "*"
"@types/sinonjs__fake-timers@*":
version "6.0.2"
resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.2.tgz#3a84cf5ec3249439015e14049bd3161419bf9eae"
integrity sha512-dIPoZ3g5gcx9zZEszaxLSVTvMReD3xxyyDnQUjA6IYDG9Ba2AV0otMPs+77sG9ojB4Qr2N2Vk5RnKeuA0X/0bg==
ads-extension-telemetry@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/ads-extension-telemetry/-/ads-extension-telemetry-1.0.0.tgz#840b363a6ad958447819b9bc59fdad3e49de31a9"
@@ -443,11 +395,6 @@ diff@3.5.0:
resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==
diff@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
emitter-listener@^1.0.1, emitter-listener@^1.1.1:
version "1.1.2"
resolved "https://registry.yarnpkg.com/emitter-listener/-/emitter-listener-1.1.2.tgz#56b140e8f6992375b3d7cb2cab1cc7432d9632e8"
@@ -554,11 +501,6 @@ is-buffer@~1.1.1:
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==
isarray@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=
istanbul-lib-coverage@^2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz#675f0ab69503fad4b1d849f736baaca803344f49"
@@ -634,16 +576,6 @@ json5@^2.1.2:
dependencies:
minimist "^1.2.5"
just-extend@^4.0.2:
version "4.1.1"
resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-4.1.1.tgz#158f1fdb01f128c411dc8b286a7b4837b3545282"
integrity sha512-aWgeGFW67BP3e5181Ep1Fv2v8z//iBJfrvyTnq8wG86vEESwmonn1zPBJ0VfmT9CJq2FIT0VsETtrNFm2a+SHA==
lodash.get@^4.4.2:
version "4.4.2"
resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=
lodash@^4.16.4, lodash@^4.17.13, lodash@^4.17.4:
version "4.17.19"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b"
@@ -743,17 +675,6 @@ ms@^2.1.1:
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
nise@^4.0.4:
version "4.0.4"
resolved "https://registry.yarnpkg.com/nise/-/nise-4.0.4.tgz#d73dea3e5731e6561992b8f570be9e363c4512dd"
integrity sha512-bTTRUNlemx6deJa+ZyoCUTRvH3liK5+N6VQZ4NIw90AgDXY6iPnsqplNFf6STcj+ePk0H/xqxnP75Lr0J0Fq3A==
dependencies:
"@sinonjs/commons" "^1.7.0"
"@sinonjs/fake-timers" "^6.0.0"
"@sinonjs/text-encoding" "^0.7.1"
just-extend "^4.0.2"
path-to-regexp "^1.7.0"
once@^1.3.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
@@ -771,13 +692,6 @@ path-parse@^1.0.6:
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c"
integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==
path-to-regexp@^1.7.0:
version "1.8.0"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a"
integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==
dependencies:
isarray "0.0.1"
pify@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231"
@@ -871,19 +785,6 @@ should@^13.2.1:
should-type-adaptors "^1.0.1"
should-util "^1.0.0"
sinon@^9.0.2:
version "9.2.1"
resolved "https://registry.yarnpkg.com/sinon/-/sinon-9.2.1.tgz#64cc88beac718557055bd8caa526b34a2231be6d"
integrity sha512-naPfsamB5KEE1aiioaoqJ6MEhdUs/2vtI5w1hPAXX/UwvoPjXcwh1m5HiKx0HGgKR8lQSoFIgY5jM6KK8VrS9w==
dependencies:
"@sinonjs/commons" "^1.8.1"
"@sinonjs/fake-timers" "^6.0.1"
"@sinonjs/formatio" "^5.0.1"
"@sinonjs/samsam" "^5.2.0"
diff "^4.0.2"
nise "^4.0.4"
supports-color "^7.1.0"
source-map@^0.5.0:
version "0.5.7"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
@@ -937,11 +838,6 @@ to-fast-properties@^2.0.0:
resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e"
integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=
type-detect@4.0.8, type-detect@^4.0.8:
version "4.0.8"
resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==
typemoq@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/typemoq/-/typemoq-2.1.0.tgz#4452ce360d92cf2a1a180f0c29de2803f87af1e8"

View File

@@ -1,47 +0,0 @@
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
<path opacity="0.4" d="M57.1348 30.379C62.6278 31.873 64.0168 32.817 68.9678 36.845C68.9308 36.781 68.8898 36.725 68.8478 36.656C68.6178 36.287 68.3678 35.936 68.1168 35.594C68.0228 35.474 67.9308 35.341 67.8318 35.221C67.6088 34.946 67.3688 34.694 67.1318 34.441C66.7601 34.0511 66.3654 33.6837 65.9498 33.341L65.9378 33.331C64.6334 32.2806 63.1432 31.4848 61.5448 30.985L61.3448 30.919C61.0068 30.819 60.6628 30.7323 60.3128 30.659C60.0398 30.604 59.7608 30.559 59.4808 30.521C59.2658 30.49 59.0528 30.448 58.8358 30.427C58.3244 30.3817 57.8121 30.362 57.2988 30.368L57.1348 30.379Z" fill="white"/>
<path d="M63.7865 24.777C60.1445 24.777 57.1865 23.708 57.1865 22.388V35.1C57.1865 36.408 60.0865 37.471 63.6925 37.488H63.7865C67.4295 37.488 70.3865 36.42 70.3865 35.1V22.388C70.3775 23.708 67.4245 24.777 63.7865 24.777Z" fill="url(#paint0_linear)"/>
<path d="M70.3775 22.388C70.3775 23.708 67.4245 24.777 63.7775 24.777C60.1305 24.777 57.1865 23.708 57.1865 22.388C57.1865 21.068 60.1405 20 63.7865 20C67.4325 20 70.3865 21.069 70.3865 22.388" fill="#E8E8E8"/>
<path d="M68.8376 22.2C68.8376 23.04 66.5746 23.719 63.7826 23.719C60.9906 23.719 58.7266 23.039 58.7266 22.2C58.7266 21.361 60.9896 20.682 63.7866 20.682C66.5836 20.682 68.8426 21.361 68.8426 22.2" fill="#50E6FF"/>
<path d="M63.7861 22.545C62.4301 22.5141 61.0784 22.7088 59.7861 23.121C62.393 23.9195 65.1792 23.9195 67.7861 23.121C66.4939 22.7088 65.1422 22.5141 63.7861 22.545Z" fill="#198AB3"/>
<path d="M79.6268 53.279C79.5407 50.42 78.441 47.6848 76.5241 45.5618C74.6073 43.4389 71.9982 42.0666 69.1628 41.69C69.0828 33.65 62.1088 27.158 53.5118 27.158C50.2817 27.0992 47.113 28.0452 44.4439 29.8652C41.7747 31.6853 39.7368 34.2895 38.6118 37.318C31.4408 38.41 25.9668 44.19 25.9668 51.16C25.9668 58.91 32.7308 65.192 41.0748 65.192C41.5238 65.192 41.9678 65.169 42.4068 65.134H66.8738C67.0926 65.1298 67.3099 65.0962 67.5198 65.034C74.2568 64.761 79.6268 59.6 79.6268 53.279Z" fill="url(#paint1_linear)"/>
<path d="M78.4631 29.993C76.8101 27.833 73.9731 26.783 70.3771 26.739V30.339C72.8971 30.381 74.7321 31.019 75.6161 32.178C78.6511 36.156 73.1371 49.2 57.2631 61.4C41.3891 73.6 27.4111 75.561 24.3781 71.581C22.6591 69.327 23.7251 64.151 27.8231 57.881C27.0849 56.6401 26.5543 55.2869 26.2521 53.875C20.3921 61.915 18.2761 69.501 21.5311 73.775C26.7481 80.616 43.7221 76.362 59.4441 64.275C75.1661 52.188 83.6791 36.833 78.4631 29.993Z" fill="#0072C6"/>
<path d="M48.1607 58.2C41.9137 58.2 36.8467 56.365 36.8467 54.1V75.9C36.8467 78.144 41.8297 79.967 48.0067 80H48.1617C54.4107 80 59.4757 78.166 59.4757 75.9V54.1C59.4747 56.368 54.4087 58.2 48.1607 58.2Z" fill="url(#paint2_linear)"/>
<path d="M59.4747 54.105C59.4747 56.368 54.4087 58.205 48.1607 58.205C41.9127 58.205 36.8467 56.37 36.8467 54.105C36.8467 51.84 41.9137 50.005 48.1607 50.005C54.4077 50.005 59.4747 51.84 59.4747 54.105Z" fill="#E8E8E8"/>
<path d="M56.8343 53.773C56.8343 55.214 52.9513 56.379 48.1613 56.379C43.3713 56.379 39.4883 55.212 39.4883 53.773C39.4883 52.334 43.3723 51.168 48.1613 51.168C52.9503 51.168 56.8343 52.335 56.8343 53.773Z" fill="#50E6FF"/>
<path d="M48.161 54.373C45.832 54.3188 43.5102 54.6531 41.291 55.362C43.5054 56.094 45.8295 56.4381 48.161 56.379C50.4928 56.438 52.8173 56.0939 55.032 55.362C52.8125 54.6531 50.4904 54.3188 48.161 54.373Z" fill="#198AB3"/>
<path d="M55.0376 69.236V63.276H53.3996V70.568H57.7426V69.236H55.0376ZM42.4516 66.3C42.1242 66.163 41.8186 65.9787 41.5446 65.753C41.4712 65.6784 41.4137 65.5897 41.3757 65.4922C41.3377 65.3947 41.32 65.2905 41.3236 65.186C41.3212 65.0809 41.345 64.9769 41.3928 64.8833C41.4407 64.7897 41.5111 64.7096 41.5976 64.65C41.8194 64.5055 42.0814 64.4354 42.3456 64.45C42.9449 64.4411 43.532 64.619 44.0256 64.959V63.439C43.4585 63.2352 42.858 63.1399 42.2556 63.158C41.5614 63.1232 40.877 63.333 40.3216 63.751C40.0878 63.9386 39.9009 64.178 39.7755 64.4503C39.6502 64.7226 39.59 65.0204 39.5996 65.32C39.6295 65.8027 39.8048 66.265 40.1026 66.6461C40.4003 67.0272 40.8065 67.3092 41.2676 67.455C41.6553 67.6177 42.0203 67.8298 42.3536 68.086C42.4376 68.1565 42.505 68.2445 42.5513 68.3438C42.5976 68.4431 42.6216 68.5514 42.6216 68.661C42.624 68.767 42.5997 68.8719 42.5509 68.9661C42.5022 69.0603 42.4306 69.1407 42.3426 69.2C42.1083 69.348 41.8332 69.418 41.5566 69.4C40.8452 69.3956 40.1599 69.1314 39.6296 68.657V70.284C40.2158 70.5731 40.8644 70.7129 41.5176 70.691C42.2542 70.734 42.9846 70.5341 43.5966 70.122C43.8436 69.9355 44.0413 69.6916 44.1725 69.4113C44.3038 69.1311 44.3645 68.8231 44.3496 68.514C44.3638 68.061 44.2095 67.6188 43.9166 67.273C43.4972 66.8553 42.9993 66.5246 42.4516 66.3ZM51.6046 69.1C51.9623 68.4678 52.1622 67.7586 52.1875 67.0327C52.2127 66.3068 52.0625 65.5855 51.7496 64.93C51.4753 64.3788 51.0456 63.9199 50.5136 63.61C49.966 63.297 49.3444 63.1367 48.7136 63.146C48.0448 63.1342 47.3847 63.2998 46.8006 63.626C46.2431 63.9468 45.7924 64.4247 45.5046 65C45.1895 65.6263 45.0316 66.32 45.0446 67.021C45.036 67.664 45.1814 68.2997 45.4686 68.875C45.7381 69.4151 46.1539 69.8687 46.6686 70.184C47.1975 70.5059 47.8017 70.6831 48.4206 70.698L49.9316 72.392H52.0666L49.9526 70.434C50.6424 70.1962 51.2269 69.7242 51.6046 69.1ZM49.9616 68.653C49.8014 68.8546 49.596 69.0155 49.362 69.1229C49.1279 69.2303 48.8719 69.281 48.6146 69.271C48.3571 69.2789 48.1013 69.2251 47.8688 69.1141C47.6362 69.0031 47.4335 68.8382 47.2776 68.633C46.9469 68.1227 46.7717 67.5272 46.7733 66.9191C46.7749 66.311 46.9532 65.7165 47.2866 65.208C47.4479 64.9999 47.656 64.8329 47.8941 64.7206C48.1322 64.6083 48.3935 64.554 48.6566 64.562C48.9127 64.5529 49.167 64.607 49.3973 64.7195C49.6275 64.832 49.8264 64.9994 49.9766 65.207C50.3225 65.7234 50.491 66.3384 50.4566 66.959C50.4936 67.5653 50.3186 68.1655 49.9616 68.657V68.653Z" fill="url(#paint3_radial)"/>
<defs>
<linearGradient id="paint0_linear" x1="57.1865" y1="29.936" x2="70.3875" y2="29.936" gradientUnits="userSpaceOnUse">
<stop stop-color="#005BA1"/>
<stop offset="0.068" stop-color="#0060A9"/>
<stop offset="0.356" stop-color="#0071C8"/>
<stop offset="0.517" stop-color="#0078D4"/>
<stop offset="0.642" stop-color="#0074CD"/>
<stop offset="0.82" stop-color="#006ABB"/>
<stop offset="1" stop-color="#005BA1"/>
</linearGradient>
<linearGradient id="paint1_linear" x1="52.7968" y1="65.192" x2="52.7968" y2="27.156" gradientUnits="userSpaceOnUse">
<stop stop-color="#198AB3"/>
<stop offset="0.097" stop-color="#209EC5"/>
<stop offset="0.242" stop-color="#28B6DA"/>
<stop offset="0.396" stop-color="#2EC7E9"/>
<stop offset="0.565" stop-color="#31D1F2"/>
<stop offset="0.775" stop-color="#32D4F5"/>
</linearGradient>
<linearGradient id="paint2_linear" x1="870.658" y1="18260.5" x2="1382.73" y2="18260.5" gradientUnits="userSpaceOnUse">
<stop stop-color="#005BA1"/>
<stop offset="0.068" stop-color="#0060A9"/>
<stop offset="0.356" stop-color="#0071C8"/>
<stop offset="0.517" stop-color="#0078D4"/>
<stop offset="0.642" stop-color="#0074CD"/>
<stop offset="0.82" stop-color="#006ABB"/>
<stop offset="1" stop-color="#005BA1"/>
</linearGradient>
<radialGradient id="paint3_radial" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(48.7896 67.771) scale(12.478)">
<stop stop-color="#F2F2F2"/>
<stop offset="0.58" stop-color="#EEEEEE"/>
<stop offset="1" stop-color="#E6E6E6"/>
</radialGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 7.3 KiB

View File

@@ -2,7 +2,7 @@
"name": "sql-database-projects",
"displayName": "SQL Database Projects",
"description": "The SQL Database Projects extension for Azure Data Studio allows users to develop and publish database schemas.",
"version": "0.6.0",
"version": "0.5.1",
"publisher": "Microsoft",
"preview": true,
"engines": {
@@ -365,7 +365,6 @@
},
"dependencies": {
"@types/xml-formatter": "^1.1.0",
"ads-extension-telemetry": "^1.0.0",
"fast-glob": "^3.1.0",
"promisify-child-process": "^3.1.1",
"vscode-languageclient": "^5.3.0-next.1",

View File

@@ -1,4 +0,0 @@
CREATE EXTERNAL DATA SOURCE [@@OBJECT_NAME@@] WITH
(
LOCATION = '@@LOCATION@@'
)

View File

@@ -1,5 +0,0 @@
CREATE EXTERNAL STREAM [@@OBJECT_NAME@@] WITH
(
DATA_SOURCE = [@@DATA_SOURCE_NAME@@],
LOCATION = N'@@LOCATION@@'@@OPTIONS@@
)

View File

@@ -5,5 +5,6 @@
EXEC sys.sp_create_streaming_job @NAME = '@@OBJECT_NAME@@', @STATEMENT = 'INSERT INTO SqlOutputStream SELECT
timeCreated,
streamColumn1 as Id
streamColumn1 as column1,
streamColumn2 as column2
FROM EdgeHubInputStream'

View File

@@ -1,5 +0,0 @@
CREATE EXTERNAL FILE FORMAT [@@OBJECT_NAME@@] WITH
(
FORMAT_TYPE = JSON,
DATA_COMPRESSION = 'org.apache.hadoop.io.compress.GzipCodec'
)

View File

@@ -23,13 +23,9 @@ export const MicrosoftDatatoolsSchemaSqlSql = 'Microsoft.Data.Tools.Schema.Sql.S
export const databaseSchemaProvider = 'DatabaseSchemaProvider';
// Project Provider
export const emptySqlDatabaseProjectTypeId = 'EmptySqlDbProj';
export const emptyProjectTypeDisplayName = localize('emptyProjectTypeDisplayName', "SQL Database");
export const emptyProjectTypeDescription = localize('emptyProjectTypeDescription', "Develop and publish schemas for SQL databases starting from an empty project");
export const edgeSqlDatabaseProjectTypeId = 'SqlDbEdgeProj';
export const edgeProjectTypeDisplayName = localize('edgeProjectTypeDisplayName', "SQL Edge");
export const edgeProjectTypeDescription = localize('edgeProjectTypeDescription', "Start with the core pieces to develop and publish schemas for SQL Edge");
export const sqlDatabaseProjectTypeId = 'sqldbproj';
export const projectTypeDisplayName = localize('projectTypeDisplayName', "SQL Database");
export const projectTypeDescription = localize('projectTypeDescription', "Design, edit, and publish schemas for SQL databases");
// commands
export const revealFileInOsCommand = 'revealFileInOS';
@@ -122,7 +118,7 @@ export const databaseProject = localize('databaseProject', "Database project");
// Create Project From Database dialog strings
export const createProjectFromDatabaseDialogName = localize('createProjectFromDatabaseDialogName', "Create project from database");
export const createProjectFromDatabaseDialogName = localize('createProjectFromDatabaseDialogName', "Create Project From Database");
export const createProjectDialogOkButtonText = localize('createProjectDialogOkButtonText', "Create");
export const sourceDatabase = localize('sourceDatabase', "Source database");
export const targetProject = localize('targetProject', "Target project");
@@ -130,13 +126,9 @@ export const createProjectSettings = localize('createProjectSettings', "Settings
export const projectNameLabel = localize('projectNameLabel', "Name");
export const projectNamePlaceholderText = localize('projectNamePlaceholderText', "Enter project name");
export const projectLocationLabel = localize('projectLocationLabel', "Location");
export const projectLocationPlaceholderText = localize('projectLocationPlaceholderText', "Select location to create project");
export const projectLocationPlaceholderText = localize('projectLocationPlaceholderText', "Enter project location");
export const browseButtonText = localize('browseButtonText', "Browse folder");
export const folderStructureLabel = localize('folderStructureLabel', "Folder structure");
export const addProjectToCurrentWorkspace = localize('addProjectToCurrentWorkspace', "This project will be added to the current workspace.");
export const newWorkspaceWillBeCreated = localize('newWorkspaceWillBeCreated', "A new workspace will be created for this project.");
export const workspaceLocationTitle = localize('workspaceLocationTitle', "Workspace location");
export const workspace = localize('workspace', "Workspace");
// Error messages
@@ -204,9 +196,6 @@ export const scriptFriendlyName = localize('scriptFriendlyName', "Script");
export const tableFriendlyName = localize('tableFriendlyName', "Table");
export const viewFriendlyName = localize('viewFriendlyName', "View");
export const storedProcedureFriendlyName = localize('storedProcedureFriendlyName', "Stored Procedure");
export const dataSourceFriendlyName = localize('dataSource', "Data Source");
export const fileFormatFriendlyName = localize('fileFormat', "File Format");
export const externalStreamFriendlyName = localize('externalStream', "External Stream");
export const externalStreamingJobFriendlyName = localize('externalStreamingJobFriendlyName', "External Streaming Job");
export const preDeployScriptFriendlyName = localize('preDeployScriptFriendlyName', "Script.PreDeployment");
export const postDeployScriptFriendlyName = localize('postDeployScriptFriendlyName', "Script.PostDeployment");

View File

@@ -14,7 +14,6 @@ export class IconPathHelper {
private static extensionContext: vscode.ExtensionContext;
public static databaseProject: IconPath;
public static colorfulSqlProject: IconPath;
public static sqlEdgeProject: IconPath;
public static dataSourceGroup: IconPath;
public static dataSourceSql: IconPath;
@@ -34,7 +33,6 @@ export class IconPathHelper {
IconPathHelper.databaseProject = IconPathHelper.makeIcon('databaseProject');
IconPathHelper.colorfulSqlProject = IconPathHelper.makeIcon('colorfulSqlProject', true);
IconPathHelper.sqlEdgeProject = IconPathHelper.makeIcon('sqlEdgeProject', true);
IconPathHelper.dataSourceGroup = IconPathHelper.makeIcon('dataSourceGroup');
IconPathHelper.dataSourceSql = IconPathHelper.makeIcon('dataSource-sql');

View File

@@ -1,17 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import AdsTelemetryReporter from 'ads-extension-telemetry';
import * as Utils from './utils';
const packageJson = require('../package.json');
let packageInfo = Utils.getPackageInfo(packageJson)!;
export const TelemetryReporter = new AdsTelemetryReporter(packageInfo.name, packageInfo.version, packageInfo.aiKey);
export enum TelemetryViews {
}

View File

@@ -11,15 +11,12 @@ export namespace cssStyles {
export const fontWeightBold = { 'font-weight': 'bold' };
export const titleFontSize = 13;
export const publishDialogLabelWidth = '205px';
export const publishDialogTextboxWidth = '190px';
export const labelWidth = '205px';
export const textboxWidth = '190px';
export const addDatabaseReferenceDialogLabelWidth = '215px';
export const addDatabaseReferenceInputboxWidth = '220px';
export const createProjectFromDatabaseLabelWidth = '110px';
export const createProjectFromDatabaseTextboxWidth = '320px';
// font-styles
export namespace fontStyle {
export const normal = 'normal';

Some files were not shown because too many files have changed in this diff Show More