mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-17 11:01:37 -05:00
Compare commits
58 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
91522caa67 | ||
|
|
15b8fa72ec | ||
|
|
2cf3357301 | ||
|
|
aee8bc2759 | ||
|
|
adf848c1ef | ||
|
|
7ad328ee95 | ||
|
|
2f18753b1f | ||
|
|
e182649adc | ||
|
|
a74119038f | ||
|
|
e169005571 | ||
|
|
b10b52e4fe | ||
|
|
5f04a4d499 | ||
|
|
d6e1e8eb52 | ||
|
|
9977e83380 | ||
|
|
099e94fa2d | ||
|
|
7732f5c0d1 | ||
|
|
151a18e3ee | ||
|
|
e59de59e61 | ||
|
|
f96fd911c1 | ||
|
|
6c89c61b0d | ||
|
|
97a4de4111 | ||
|
|
a70dce7855 | ||
|
|
757ac1d4aa | ||
|
|
4092b6493b | ||
|
|
6349f1bd49 | ||
|
|
0c82024cf3 | ||
|
|
131e0a6b45 | ||
|
|
aeb22011d2 | ||
|
|
3d82074656 | ||
|
|
6dc07e5804 | ||
|
|
89d515d3ae | ||
|
|
9df56c5c0f | ||
|
|
048f85d918 | ||
|
|
4d3443c192 | ||
|
|
f69dc6a445 | ||
|
|
fde031be48 | ||
|
|
08735c9434 | ||
|
|
f748a8c7bb | ||
|
|
67e3d2ebdb | ||
|
|
d17ca1561f | ||
|
|
6f731fcd9e | ||
|
|
d86e1eec10 | ||
|
|
cb567989da | ||
|
|
40675fcadb | ||
|
|
0299ef1d83 | ||
|
|
f544ca3be0 | ||
|
|
273f40e2b3 | ||
|
|
8027993ab4 | ||
|
|
1078d67728 | ||
|
|
593cb45a50 | ||
|
|
0b1239b755 | ||
|
|
a0ef594792 | ||
|
|
e2cf607896 | ||
|
|
f0eeb76846 | ||
|
|
5da30e6111 | ||
|
|
a9eb6880d4 | ||
|
|
426f1ae99b | ||
|
|
64dd0f0cad |
@@ -12,6 +12,10 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
2
.github/label-actions.yml
vendored
Normal file
2
.github/label-actions.yml
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
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."
|
||||
15
.github/workflows/on-label.yml
vendored
Normal file
15
.github/workflows/on-label.yml
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
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 }}
|
||||
@@ -1,5 +1,14 @@
|
||||
# Change Log
|
||||
|
||||
## 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
|
||||
|
||||
14
README.md
14
README.md
@@ -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=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
|
||||
[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
|
||||
|
||||
@@ -43,6 +43,7 @@ function createDefaultConfig(quality: string): Config {
|
||||
}
|
||||
|
||||
function getConfig(quality: string): Promise<Config> {
|
||||
console.log(`Getting config for quality ${quality}`);
|
||||
const client = new DocumentClient(process.env['AZURE_DOCUMENTDB_ENDPOINT']!, { masterKey: process.env['AZURE_DOCUMENTDB_MASTERKEY'] });
|
||||
const collection = 'dbs/builds/colls/config';
|
||||
const query = {
|
||||
@@ -52,13 +53,13 @@ function getConfig(quality: string): Promise<Config> {
|
||||
]
|
||||
};
|
||||
|
||||
return new Promise<Config>((c, e) => {
|
||||
return retry(() => new Promise<Config>((c, e) => {
|
||||
client.queryDocuments(collection, query, { enableCrossPartitionQuery: true }).toArray((err, results) => {
|
||||
if (err && err.code !== 409) { return e(err); }
|
||||
|
||||
c(!results || results.length === 0 ? createDefaultConfig(quality) : results[0] as any as Config);
|
||||
});
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
interface Asset {
|
||||
@@ -86,6 +87,7 @@ function createOrUpdate(commit: string, quality: string, platform: string, type:
|
||||
updateTries++;
|
||||
|
||||
return new Promise<void>((c, e) => {
|
||||
console.log(`Querying existing documents to update...`);
|
||||
client.queryDocuments(collection, updateQuery, { enableCrossPartitionQuery: true }).toArray((err, results) => {
|
||||
if (err) { return e(err); }
|
||||
if (results.length !== 1) { return e(new Error('No documents')); }
|
||||
@@ -101,6 +103,7 @@ function createOrUpdate(commit: string, quality: string, platform: string, type:
|
||||
release.updates[platform] = type;
|
||||
}
|
||||
|
||||
console.log(`Replacing existing document with updated version`);
|
||||
client.replaceDocument(release._self, release, err => {
|
||||
if (err && err.code === 409 && updateTries < 5) { return c(update()); }
|
||||
if (err) { return e(err); }
|
||||
@@ -112,7 +115,8 @@ function createOrUpdate(commit: string, quality: string, platform: string, type:
|
||||
});
|
||||
}
|
||||
|
||||
return new Promise<void>((c, e) => {
|
||||
return retry(() => new Promise<void>((c, e) => {
|
||||
console.log(`Attempting to create document`);
|
||||
client.createDocument(collection, release, err => {
|
||||
if (err && err.code === 409) { return c(update()); }
|
||||
if (err) { return e(err); }
|
||||
@@ -120,7 +124,7 @@ function createOrUpdate(commit: string, quality: string, platform: string, type:
|
||||
console.log('Build successfully published.');
|
||||
c();
|
||||
});
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
async function assertContainer(blobService: azure.BlobService, quality: string): Promise<void> {
|
||||
@@ -188,7 +192,6 @@ async function publish(commit: string, quality: string, platform: string, type:
|
||||
console.log(`Blob ${quality}, ${blobName} already exists, not publishing again.`);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Uploading blobs to Azure storage...');
|
||||
|
||||
await uploadBlob(blobService, quality, blobName, file);
|
||||
@@ -247,6 +250,22 @@ async function publish(commit: string, quality: string, platform: string, type:
|
||||
await createOrUpdate(commit, quality, platform, type, release, asset, isUpdate);
|
||||
}
|
||||
|
||||
const RETRY_TIMES = 10;
|
||||
async function retry<T>(fn: () => Promise<T>): Promise<T> {
|
||||
for (let run = 1; run <= RETRY_TIMES; run++) {
|
||||
try {
|
||||
return await fn();
|
||||
} catch (err) {
|
||||
if (!/ECONNRESET/.test(err.message)) {
|
||||
throw err;
|
||||
}
|
||||
console.log(`Caught error ${err} - ${run}/${RETRY_TIMES}`);
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('Retried too many times');
|
||||
}
|
||||
|
||||
function main(): void {
|
||||
const commit = process.env['BUILD_SOURCEVERSION'];
|
||||
|
||||
|
||||
@@ -134,11 +134,10 @@
|
||||
" if arc_admin_password != confirm_password:\n",
|
||||
" sys.exit(f'Passwords do not match.')\n",
|
||||
"\n",
|
||||
"os.environ[\"SPN_CLIENT_ID\"] = spn_client_id\n",
|
||||
"os.environ[\"SPN_CLIENT_TENANTID\"] = spn_client_tenantid\n",
|
||||
"if \"AZDATA_NB_VAR_SPN_CLIENT_SECRET\" in os.environ:\n",
|
||||
" os.environ[\"SPN_CLIENT_SECRET\"] = os.environ[\"AZDATA_NB_VAR_SPN_CLIENT_SECRET\"]\n",
|
||||
" print(os.environ[\"SPN_CLIENT_TENANTID\"])\n",
|
||||
"os.environ[\"SPN_CLIENT_ID\"] = sp_client_id\n",
|
||||
"os.environ[\"SPN_TENANT_ID\"] = sp_tenant_id\n",
|
||||
"if \"AZDATA_NB_VAR_SP_CLIENT_SECRET\" in os.environ:\n",
|
||||
" os.environ[\"SPN_CLIENT_SECRET\"] = os.environ[\"AZDATA_NB_VAR_SP_CLIENT_SECRET\"]\n",
|
||||
"os.environ[\"SPN_AUTHORITY\"] = \"https://login.microsoftonline.com\""
|
||||
],
|
||||
"metadata": {
|
||||
|
||||
@@ -114,6 +114,8 @@
|
||||
"# 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()"
|
||||
],
|
||||
|
||||
@@ -114,6 +114,8 @@
|
||||
"# 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()"
|
||||
],
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
"name": "arc",
|
||||
"displayName": "%arc.displayName%",
|
||||
"description": "%arc.description%",
|
||||
"version": "0.6.3",
|
||||
"version": "0.7.0",
|
||||
"publisher": "Microsoft",
|
||||
"preview": true,
|
||||
"license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/main/LICENSE.txt",
|
||||
"icon": "images/extension.png",
|
||||
"engines": {
|
||||
"vscode": "*",
|
||||
"azdata": ">=1.23.0"
|
||||
"azdata": ">=1.25.0"
|
||||
},
|
||||
"activationEvents": [
|
||||
"onCommand:arc.connectToController",
|
||||
@@ -200,7 +200,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "%arc.data.controller.data.controller.create.azureconfig.title%",
|
||||
"title": "%arc.data.controller.create.azureconfig.title%",
|
||||
"sections": [
|
||||
{
|
||||
"title": "%arc.data.controller.project.details.title%",
|
||||
@@ -218,7 +218,7 @@
|
||||
},
|
||||
{
|
||||
"type": "azure_locations",
|
||||
"label": "%arc.data.controller.arc.data.controller.location%",
|
||||
"label": "%arc.data.controller.location%",
|
||||
"defaultValue": "eastus",
|
||||
"required": true,
|
||||
"locationVariableName": "AZDATA_NB_VAR_ARC_DATA_CONTROLLER_LOCATION",
|
||||
@@ -244,7 +244,7 @@
|
||||
"fields": [
|
||||
{
|
||||
"type": "readonly_text",
|
||||
"label": "%arc.data.controller.data.controller.connectivitymode.description%",
|
||||
"label": "%arc.data.controller.connectivitymode.description%",
|
||||
"labelWidth": "600px"
|
||||
},
|
||||
{
|
||||
@@ -268,19 +268,38 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "%arc.data.controller.spnclient%",
|
||||
"variableName": "AZDATA_NB_VAR_SPN_CLIENT_ID",
|
||||
"type": "readonly_text",
|
||||
"label": "%arc.data.controller.serviceprincipal.description%",
|
||||
"labelWidth": "600px",
|
||||
"links": [
|
||||
{
|
||||
"text": "%arc.data.controller.readmore%",
|
||||
"url": "https://docs.microsoft.com/azure/azure-arc/data/upload-metrics"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "%arc.data.controller.spclientid%",
|
||||
"description": "%arc.data.controller.spclientid.description%",
|
||||
"variableName": "AZDATA_NB_VAR_SP_CLIENT_ID",
|
||||
"type": "text",
|
||||
"required": true,
|
||||
"defaultValue": "",
|
||||
"placeHolder": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
|
||||
"enabled": {
|
||||
"target": "AZDATA_NB_VAR_ARC_DATA_CONTROLLER_CONNECTIVITY_MODE",
|
||||
"value": "direct"
|
||||
}
|
||||
},
|
||||
"validations" : [{
|
||||
"type": "regex_match",
|
||||
"regex": "^[0-9A-Fa-f]{8}-([0-9A-Fa-f]{4}-){3}[0-9A-Fa-f]{12}$",
|
||||
"description": "%arc.data.controller.spclientid.validation.description%"
|
||||
}]
|
||||
},
|
||||
{
|
||||
"label": "%arc.data.controller.spnclientsecret%",
|
||||
"variableName": "AZDATA_NB_VAR_SPN_CLIENT_SECRET",
|
||||
"label": "%arc.data.controller.spclientsecret%",
|
||||
"description": "%arc.data.controller.spclientsecret.description%",
|
||||
"variableName": "AZDATA_NB_VAR_SP_CLIENT_SECRET",
|
||||
"type": "password",
|
||||
"required": true,
|
||||
"defaultValue": "",
|
||||
@@ -290,38 +309,45 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "%arc.data.controller.spntenant%",
|
||||
"variableName": "AZDATA_NB_VAR_SPN_CLIENT_TENANTID",
|
||||
"label": "%arc.data.controller.sptenantid%",
|
||||
"description": "%arc.data.controller.sptenantid.description%",
|
||||
"variableName": "AZDATA_NB_VAR_SP_TENANT_ID",
|
||||
"type": "text",
|
||||
"required": true,
|
||||
"defaultValue": "",
|
||||
"enabled": {
|
||||
"target": "AZDATA_NB_VAR_ARC_DATA_CONTROLLER_CONNECTIVITY_MODE",
|
||||
"value": "direct"
|
||||
}
|
||||
"enabled": false,
|
||||
"valueProvider": {
|
||||
"providerId": "subscription-id-to-tenant-id",
|
||||
"triggerField": "AZDATA_NB_VAR_ARC_SUBSCRIPTION"
|
||||
},
|
||||
"validations" : [{
|
||||
"type": "regex_match",
|
||||
"regex": "^[0-9A-Fa-f]{8}-([0-9A-Fa-f]{4}-){3}[0-9A-Fa-f]{12}$",
|
||||
"description": "%arc.data.controller.sptenantid.validation.description%"
|
||||
}]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "%arc.data.controller.data.controller.create.controllerconfig.title%",
|
||||
"title": "%arc.data.controller.create.controllerconfig.title%",
|
||||
"sections": [
|
||||
{
|
||||
"title": "%arc.data.controller.data.controller.details.title%",
|
||||
"title": "%arc.data.controller.details.title%",
|
||||
"fields": [
|
||||
{
|
||||
"type": "readonly_text",
|
||||
"label": "%arc.data.controller.data.controller.details.description%",
|
||||
"label": "%arc.data.controller.details.description%",
|
||||
"labelWidth": "600px"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"label": "%arc.data.controller.arc.data.controller.namespace%",
|
||||
"label": "%arc.data.controller.namespace%",
|
||||
"validations" : [{
|
||||
"type": "regex_match",
|
||||
"regex": "^[a-z0-9]([-a-z0-9]{0,61}[a-z0-9])?$",
|
||||
"description": "%arc.data.controller.arc.data.controller.namespace.validation.description%"
|
||||
"description": "%arc.data.controller.namespace.validation.description%"
|
||||
}],
|
||||
"defaultValue": "arc",
|
||||
"required": true,
|
||||
@@ -329,11 +355,11 @@
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"label": "%arc.data.controller.arc.data.controller.name%",
|
||||
"label": "%arc.data.controller.name%",
|
||||
"validations" : [{
|
||||
"type": "regex_match",
|
||||
"regex": "^[a-z0-9]([-.a-z0-9]{0,251}[a-z0-9])?$",
|
||||
"description": "%arc.data.controller.arc.data.controller.name.validation.description%"
|
||||
"description": "%arc.data.controller.name.validation.description%"
|
||||
}],
|
||||
"defaultValue": "arc-dc",
|
||||
"required": true,
|
||||
@@ -374,7 +400,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "%arc.data.controller.data.controller.create.summary.title%",
|
||||
"title": "%arc.data.controller.create.summary.title%",
|
||||
"isSummaryPage": true,
|
||||
"fieldHeight": "16px",
|
||||
"sections": [
|
||||
@@ -528,18 +554,6 @@
|
||||
{
|
||||
"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",
|
||||
@@ -564,6 +578,18 @@
|
||||
{
|
||||
"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",
|
||||
|
||||
@@ -20,18 +20,18 @@
|
||||
"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.data.controller.create.azureconfig.title": "Azure and Connectivity Configuration",
|
||||
"arc.data.controller.data.controller.connectivitymode.description": "Select the connectivity mode for the controller.",
|
||||
"arc.data.controller.data.controller.create.controllerconfig.title": "Controller Configuration",
|
||||
"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",
|
||||
"arc.data.controller.project.details.title": "Azure details",
|
||||
"arc.data.controller.project.details.description": "Select the subscription to manage deployed resources and costs. Use resource groups like folders to organize and manage all your resources.",
|
||||
"arc.data.controller.data.controller.details.title": "Data controller details",
|
||||
"arc.data.controller.data.controller.details.description": "Provide a namespace, name and storage class for your Azure Arc data controller. This name will be used to identify your Arc instance for remote management and monitoring.",
|
||||
"arc.data.controller.arc.data.controller.namespace": "Data controller namespace",
|
||||
"arc.data.controller.arc.data.controller.namespace.validation.description": "Namespace must consist of lower case alphanumeric characters or '-', start/end with an alphanumeric character, and be 63 characters or fewer in length.",
|
||||
"arc.data.controller.arc.data.controller.name": "Data controller name",
|
||||
"arc.data.controller.arc.data.controller.name.validation.description": "Name must consist of lower case alphanumeric characters, '-' or '.', start/end with an alphanumeric character and be 253 characters or less in length.",
|
||||
"arc.data.controller.arc.data.controller.location": "Location",
|
||||
"arc.data.controller.details.title": "Data controller details",
|
||||
"arc.data.controller.details.description": "Provide a namespace, name and storage class for your Azure Arc data controller. This name will be used to identify your Arc instance for remote management and monitoring.",
|
||||
"arc.data.controller.namespace": "Data controller namespace",
|
||||
"arc.data.controller.namespace.validation.description": "Namespace must consist of lower case alphanumeric characters or '-', start/end with an alphanumeric character, and be 63 characters or fewer in length.",
|
||||
"arc.data.controller.name": "Data controller name",
|
||||
"arc.data.controller.name.validation.description": "Name must consist of lower case alphanumeric characters, '-' or '.', start/end with an alphanumeric character and be 253 characters or less in length.",
|
||||
"arc.data.controller.location": "Location",
|
||||
"arc.data.controller.admin.account.title": "Administrator account",
|
||||
"arc.data.controller.admin.account.name": "Data controller login",
|
||||
"arc.data.controller.admin.account.password": "Password",
|
||||
@@ -39,10 +39,16 @@
|
||||
"arc.data.controller.connectivitymode": "Connectivity Mode",
|
||||
"arc.data.controller.direct": "Direct",
|
||||
"arc.data.controller.indirect": "Indirect",
|
||||
"arc.data.controller.spnclient": "SPN Client ID",
|
||||
"arc.data.controller.spnclientsecret": "SPN Client Secret",
|
||||
"arc.data.controller.spntenant": "SPN Tenant ID",
|
||||
"arc.data.controller.data.controller.create.summary.title": "Review your configuration",
|
||||
"arc.data.controller.serviceprincipal.description": "When deploying a controller in direct connected mode a Service Principal is required for uploading metrics to Azure. {0} about how to create this Service Principal and assign it the correct roles.",
|
||||
"arc.data.controller.spclientid": "Service Principal Client ID",
|
||||
"arc.data.controller.spclientid.description": "The Application (client) ID of the created Service Principal",
|
||||
"arc.data.controller.spclientid.validation.description": "The client ID must be a GUID in the format xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
|
||||
"arc.data.controller.spclientsecret": "Service Principal Client Secret",
|
||||
"arc.data.controller.spclientsecret.description": "The password generated during creation of the Service Principal",
|
||||
"arc.data.controller.sptenantid": "Service Principal Tenant ID",
|
||||
"arc.data.controller.sptenantid.description": "The Tenant ID of the Service Principal. This must be the same as the Tenant ID of the subscription selected to create this controller for.",
|
||||
"arc.data.controller.sptenantid.validation.description": "The tenant ID must be a GUID in the format xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
|
||||
"arc.data.controller.create.summary.title": "Review your configuration",
|
||||
"arc.data.controller.summary.arc.data.controller": "Azure Arc data controller",
|
||||
"arc.data.controller.summary.estimated.cost.per.month": "Estimated cost per month",
|
||||
"arc.data.controller.summary.arc.by.microsoft" : "by Microsoft",
|
||||
@@ -65,7 +71,8 @@
|
||||
"arc.data.controller.summary.data.controller.namespace": "Data controller namespace",
|
||||
"arc.data.controller.summary.controller": "Controller",
|
||||
"arc.data.controller.summary.location": "Location",
|
||||
"arc.data.controller.arc.data.controller.agreement": "I accept {0} and {1}.",
|
||||
"arc.data.controller.agreement": "I accept {0} and {1}.",
|
||||
"arc.data.controller.readmore": "Read more",
|
||||
"microsoft.agreement.privacy.statement":"Microsoft Privacy Statement",
|
||||
"deploy.script.action":"Script to notebook",
|
||||
"deploy.done.action":"Deploy",
|
||||
|
||||
@@ -4,11 +4,17 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
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),
|
||||
@@ -16,12 +22,13 @@ 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();
|
||||
throw new UserCancelledError(loc.userCancelledError);
|
||||
}
|
||||
return model.password;
|
||||
}
|
||||
|
||||
@@ -9,8 +9,6 @@ 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
|
||||
|
||||
@@ -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;
|
||||
rdApi.registerOptionsSourceProvider(new ArcControllersOptionsSourceProvider(treeDataProvider));
|
||||
context.subscriptions.push(rdApi.registerOptionsSourceProvider(new ArcControllersOptionsSourceProvider(treeDataProvider)));
|
||||
|
||||
return arcApi(treeDataProvider);
|
||||
}
|
||||
|
||||
@@ -139,7 +139,6 @@ 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");
|
||||
@@ -172,6 +171,7 @@ 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");
|
||||
@@ -205,3 +205,4 @@ export const noPasswordFound = (controllerName: string) => localize('noPasswordF
|
||||
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");
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import { ControllerInfo, ResourceType } from 'arc';
|
||||
import * as azdataExt from 'azdata-ext';
|
||||
import * as vscode from 'vscode';
|
||||
import { UserCancelledError } from '../common/utils';
|
||||
import { UserCancelledError } from '../common/api';
|
||||
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();
|
||||
throw new UserCancelledError(loc.userCancelledError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,9 @@ 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, UserCancelledError } from '../common/utils';
|
||||
import { createCredentialId, parseIpAndPort } from '../common/utils';
|
||||
import { credentialNamespace } from '../constants';
|
||||
import * as loc from '../localizedConstants';
|
||||
import { ConnectToSqlDialog } from '../ui/dialogs/connectSqlDialog';
|
||||
|
||||
@@ -17,7 +17,7 @@ import { AzureArcTreeDataProvider } from '../ui/tree/azureArcTreeDataProvider';
|
||||
*/
|
||||
export class ArcControllersOptionsSourceProvider implements rd.IOptionsSourceProvider {
|
||||
private _cacheManager = new CacheManager<string, string>();
|
||||
readonly optionsSourceId = 'arc.controllers';
|
||||
readonly id = 'arc.controllers';
|
||||
constructor(private _treeProvider: AzureArcTreeDataProvider) { }
|
||||
|
||||
async getOptions(): Promise<string[] | azdata.CategoryValue[]> {
|
||||
|
||||
@@ -49,6 +49,7 @@ 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.'); }
|
||||
}
|
||||
},
|
||||
|
||||
@@ -11,7 +11,8 @@ import * as sinon from 'sinon';
|
||||
import * as TypeMoq from 'typemoq';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import * as vscode from 'vscode';
|
||||
import { UserCancelledError } from '../../common/utils';
|
||||
import * as loc from '../../localizedConstants';
|
||||
import { UserCancelledError } from '../../common/api';
|
||||
import { ControllerModel } from '../../models/controllerModel';
|
||||
import { ConnectToControllerDialog } from '../../ui/dialogs/connectControllerDialog';
|
||||
import { AzureArcTreeDataProvider } from '../../ui/tree/azureArcTreeDataProvider';
|
||||
@@ -39,7 +40,7 @@ describe('ControllerModel', function (): 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', username: 'admin', name: 'arc', rememberPassword: true, resources: [] });
|
||||
await should(model.azdataLogin()).be.rejectedWith(new UserCancelledError());
|
||||
await should(model.azdataLogin()).be.rejectedWith(new UserCancelledError(loc.userCancelledError));
|
||||
});
|
||||
|
||||
it('Reads password from cred store', async function (): Promise<void> {
|
||||
@@ -64,7 +65,7 @@ describe('ControllerModel', function (): void {
|
||||
});
|
||||
|
||||
it('Prompt for password when not in cred store', async function (): Promise<void> {
|
||||
const password = 'password123';
|
||||
const password = 'password123'; // [SuppressMessage("Microsoft.Security", "CS001:SecretInline", Justification="Stub value for testing")]
|
||||
|
||||
// Set up cred store to return empty password
|
||||
const credProviderMock = TypeMoq.Mock.ofType<azdata.CredentialProvider>();
|
||||
@@ -90,7 +91,7 @@ describe('ControllerModel', function (): void {
|
||||
});
|
||||
|
||||
it('Prompt for password when rememberPassword is true but prompt reconnect is true', async function (): Promise<void> {
|
||||
const password = 'password123';
|
||||
const password = 'password123'; // [SuppressMessage("Microsoft.Security", "CS001:SecretInline", Justification="Stub value for testing")]
|
||||
// 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' }));
|
||||
@@ -116,7 +117,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';
|
||||
const password = 'password123'; // [SuppressMessage("Microsoft.Security", "CS001:SecretInline", Justification="Stub value for testing")]
|
||||
// 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' }));
|
||||
|
||||
@@ -179,7 +179,6 @@ 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();
|
||||
@@ -197,7 +196,6 @@ 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();
|
||||
@@ -318,6 +316,7 @@ 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;
|
||||
@@ -328,6 +327,7 @@ 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;
|
||||
|
||||
@@ -157,7 +157,9 @@ 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.info.name,
|
||||
this.saveArgs,
|
||||
this._postgresModel.engineVersion);
|
||||
} catch (err) {
|
||||
// If an error occurs while editing the instance then re-enable the save button since
|
||||
// the edit wasn't successfully applied
|
||||
@@ -225,7 +227,6 @@ 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();
|
||||
@@ -243,7 +244,6 @@ 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,6 +448,7 @@ 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;
|
||||
@@ -458,6 +459,7 @@ 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;
|
||||
|
||||
@@ -157,6 +157,7 @@ export class PostgresOverviewPage extends DashboardPage {
|
||||
adminPassword: true,
|
||||
noWait: true
|
||||
},
|
||||
this._postgresModel.engineVersion,
|
||||
{ 'AZDATA_PASSWORD': password });
|
||||
vscode.window.showInformationMessage(loc.passwordReset);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import { MiaaResourceInfo, ResourceInfo, ResourceType } from 'arc';
|
||||
import * as vscode from 'vscode';
|
||||
import { UserCancelledError } from '../../common/utils';
|
||||
import { UserCancelledError } from '../../common/api';
|
||||
import * as loc from '../../localizedConstants';
|
||||
import { ControllerModel, Registration } from '../../models/controllerModel';
|
||||
import { MiaaModel } from '../../models/miaaModel';
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "asde-deployment",
|
||||
"displayName": "%extension-displayName%",
|
||||
"description": "%extension-description%",
|
||||
"version": "0.4.0",
|
||||
"version": "0.4.1",
|
||||
"publisher": "Microsoft",
|
||||
"preview": true,
|
||||
"license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/main/LICENSE.txt",
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
"name": "azdata",
|
||||
"displayName": "%azdata.displayName%",
|
||||
"description": "%azdata.description%",
|
||||
"version": "0.4.1",
|
||||
"version": "0.5.0",
|
||||
"publisher": "Microsoft",
|
||||
"preview": true,
|
||||
"license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/main/LICENSE.txt",
|
||||
"icon": "images/extension.png",
|
||||
"engines": {
|
||||
"vscode": "*",
|
||||
"azdata": ">=1.23.0"
|
||||
"azdata": ">=1.25.0"
|
||||
},
|
||||
"activationEvents": [
|
||||
"*"
|
||||
|
||||
@@ -102,10 +102,11 @@ 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, additionalEnvVars);
|
||||
return azdataToolService.localAzdata.arc.postgres.server.edit(name, args, engineVersion, additionalEnvVars);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -118,6 +118,7 @@ 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'); }
|
||||
@@ -131,6 +132,7 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
rdApi.registerOptionsSourceProvider(new ArcControllerConfigProfilesOptionsSource(azdataApi));
|
||||
context.subscriptions.push(rdApi.registerOptionsSourceProvider(new ArcControllerConfigProfilesOptionsSource(azdataApi)));
|
||||
|
||||
return azdataApi;
|
||||
}
|
||||
|
||||
@@ -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 optionsSourceId = 'arc.controller.config.profiles';
|
||||
readonly id = 'arc.controller.config.profiles';
|
||||
constructor(private _azdataExtApi: azdataExt.IExtension) { }
|
||||
async getOptions(): Promise<string[]> {
|
||||
const isEulaAccepted = await this._azdataExtApi.isEulaAccepted();
|
||||
|
||||
@@ -262,6 +262,7 @@ declare module 'azdata-ext' {
|
||||
replaceEngineSettings?: boolean,
|
||||
workers?: number
|
||||
},
|
||||
engineVersion?: string,
|
||||
additionalEnvVars?: { [key: string]: string }): Promise<AzdataOutput<void>>
|
||||
}
|
||||
},
|
||||
|
||||
@@ -321,6 +321,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@azure/arm-resourcegraph": "^2.0.0",
|
||||
"@azure/arm-storage": "^15.1.0",
|
||||
"@azure/arm-subscriptions": "1.0.0",
|
||||
"axios": "^0.19.2",
|
||||
"qs": "^6.9.1",
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
declare module 'azureResource' {
|
||||
import { TreeDataProvider } from 'vscode';
|
||||
import { DataProvider, Account, TreeItem } from 'azdata';
|
||||
import { FileShareItem, ListContainerItem } from '@azure/arm-storage/esm/models';
|
||||
export namespace azureResource {
|
||||
|
||||
export const enum AzureResourceType {
|
||||
@@ -18,7 +19,8 @@ declare module 'azureResource' {
|
||||
kustoClusters = 'microsoft.kusto/clusters',
|
||||
azureArcPostgresServer = 'microsoft.azuredata/postgresinstances',
|
||||
postgresServer = 'microsoft.dbforpostgresql/servers',
|
||||
azureArcService = 'microsoft.azuredata/datacontrollers'
|
||||
azureArcService = 'microsoft.azuredata/datacontrollers',
|
||||
storageAccount = 'microsoft.storage/storageaccounts',
|
||||
}
|
||||
|
||||
export interface IAzureResourceProvider extends DataProvider {
|
||||
@@ -75,7 +77,10 @@ declare module 'azureResource' {
|
||||
fullName: string;
|
||||
defaultDatabaseName: string;
|
||||
}
|
||||
export interface BlobContainer extends ListContainerItem {
|
||||
}
|
||||
|
||||
|
||||
export interface FileShare extends FileShareItem {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,14 +5,18 @@
|
||||
|
||||
import { ResourceGraphClient } from '@azure/arm-resourcegraph';
|
||||
import { TokenCredentials } from '@azure/ms-rest-js';
|
||||
import axios, { AxiosRequestConfig } from 'axios';
|
||||
import * as azdata from 'azdata';
|
||||
import { GetResourceGroupsResult, GetSubscriptionsResult, ResourceQueryResult } from 'azurecore';
|
||||
import { HttpGetRequestResult, GetResourceGroupsResult, GetSubscriptionsResult, ResourceQueryResult, GetBlobContainersResult, GetFileSharesResult } from 'azurecore';
|
||||
import { azureResource } from 'azureResource';
|
||||
import { EOL } from 'os';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { AppContext } from '../appContext';
|
||||
import { invalidAzureAccount, invalidTenant, unableToFetchTokenError } from '../localizedConstants';
|
||||
import { AzureResourceServiceNames } from './constants';
|
||||
import { IAzureResourceSubscriptionFilterService, IAzureResourceSubscriptionService } from './interfaces';
|
||||
import { AzureResourceGroupService } from './providers/resourceGroup/resourceGroupService';
|
||||
import { StorageManagementClient } from '@azure/arm-storage';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
@@ -106,7 +110,7 @@ export function equals(one: any, other: any): boolean {
|
||||
export async function getResourceGroups(appContext: AppContext, account?: azdata.Account, subscription?: azureResource.AzureResourceSubscription, ignoreErrors: boolean = false): Promise<GetResourceGroupsResult> {
|
||||
const result: GetResourceGroupsResult = { resourceGroups: [], errors: [] };
|
||||
if (!account?.properties?.tenants || !Array.isArray(account.properties.tenants) || !subscription) {
|
||||
const error = new Error(localize('azure.accounts.getResourceGroups.invalidParamsError', "Invalid account or subscription"));
|
||||
const error = new Error(invalidAzureAccount);
|
||||
if (!ignoreErrors) {
|
||||
throw error;
|
||||
}
|
||||
@@ -146,7 +150,7 @@ export async function runResourceQuery<T extends azureResource.AzureGraphResourc
|
||||
query: string): Promise<ResourceQueryResult<T>> {
|
||||
const result: ResourceQueryResult<T> = { resources: [], errors: [] };
|
||||
if (!account?.properties?.tenants || !Array.isArray(account.properties.tenants)) {
|
||||
const error = new Error(localize('azure.accounts.runResourceQuery.errors.invalidAccount', "Invalid account"));
|
||||
const error = new Error(invalidAzureAccount);
|
||||
if (!ignoreErrors) {
|
||||
throw error;
|
||||
}
|
||||
@@ -157,7 +161,7 @@ export async function runResourceQuery<T extends azureResource.AzureGraphResourc
|
||||
// Check our subscriptions to ensure we have valid ones
|
||||
subscriptions.forEach(subscription => {
|
||||
if (!subscription.tenant) {
|
||||
const error = new Error(localize('azure.accounts.runResourceQuery.errors.noTenantSpecifiedForSubscription', "Invalid tenant for subscription"));
|
||||
const error = new Error(invalidTenant);
|
||||
if (!ignoreErrors) {
|
||||
throw error;
|
||||
}
|
||||
@@ -188,7 +192,7 @@ export async function runResourceQuery<T extends azureResource.AzureGraphResourc
|
||||
resourceClient = new ResourceGraphClient(credential, { baseUri: account.properties.providerSettings.settings.armResource.endpoint });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
const error = new Error(localize('azure.accounts.runResourceQuery.errors.unableToFetchToken', "Unable to get token for tenant {0}", tenant.id));
|
||||
const error = new Error(unableToFetchTokenError(tenant.id));
|
||||
result.errors.push(error);
|
||||
continue;
|
||||
}
|
||||
@@ -227,7 +231,7 @@ export async function runResourceQuery<T extends azureResource.AzureGraphResourc
|
||||
export async function getSubscriptions(appContext: AppContext, account?: azdata.Account, ignoreErrors: boolean = false): Promise<GetSubscriptionsResult> {
|
||||
const result: GetSubscriptionsResult = { subscriptions: [], errors: [] };
|
||||
if (!account?.properties?.tenants || !Array.isArray(account.properties.tenants)) {
|
||||
const error = new Error(localize('azure.accounts.getSubscriptions.invalidParamsError', "Invalid account"));
|
||||
const error = new Error(invalidAzureAccount);
|
||||
if (!ignoreErrors) {
|
||||
throw error;
|
||||
}
|
||||
@@ -261,7 +265,7 @@ export async function getSubscriptions(appContext: AppContext, account?: azdata.
|
||||
export async function getSelectedSubscriptions(appContext: AppContext, account?: azdata.Account, ignoreErrors: boolean = false): Promise<GetSubscriptionsResult> {
|
||||
const result: GetSubscriptionsResult = { subscriptions: [], errors: [] };
|
||||
if (!account?.properties?.tenants || !Array.isArray(account.properties.tenants)) {
|
||||
const error = new Error(localize('azure.accounts.getSelectedSubscriptions.invalidParamsError', "Invalid account"));
|
||||
const error = new Error(invalidAzureAccount);
|
||||
if (!ignoreErrors) {
|
||||
throw error;
|
||||
}
|
||||
@@ -284,3 +288,189 @@ export async function getSelectedSubscriptions(appContext: AppContext, account?:
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* makes a GET request to Azure REST apis. Currently, it only supports GET ARM queries.
|
||||
*/
|
||||
export async function makeHttpGetRequest(account: azdata.Account, subscription: azureResource.AzureResourceSubscription, ignoreErrors: boolean = false, url: string): Promise<HttpGetRequestResult> {
|
||||
const result: HttpGetRequestResult = { response: {}, errors: [] };
|
||||
|
||||
if (!account?.properties?.tenants || !Array.isArray(account.properties.tenants)) {
|
||||
const error = new Error(invalidAzureAccount);
|
||||
if (!ignoreErrors) {
|
||||
throw error;
|
||||
}
|
||||
result.errors.push(error);
|
||||
}
|
||||
|
||||
if (!subscription.tenant) {
|
||||
const error = new Error(invalidTenant);
|
||||
if (!ignoreErrors) {
|
||||
throw error;
|
||||
}
|
||||
result.errors.push(error);
|
||||
}
|
||||
if (result.errors.length > 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
let securityToken: { token: string, tokenType?: string };
|
||||
try {
|
||||
securityToken = await azdata.accounts.getAccountSecurityToken(
|
||||
account,
|
||||
subscription.tenant!,
|
||||
azdata.AzureResource.ResourceManagement
|
||||
);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
const error = new Error(unableToFetchTokenError(subscription.tenant));
|
||||
if (!ignoreErrors) {
|
||||
throw error;
|
||||
}
|
||||
result.errors.push(error);
|
||||
}
|
||||
if (result.errors.length > 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const config: AxiosRequestConfig = {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${securityToken.token}`
|
||||
},
|
||||
validateStatus: () => true // Never throw
|
||||
};
|
||||
|
||||
const response = await axios.get(url, config);
|
||||
|
||||
if (response.status !== 200) {
|
||||
let errorMessage: string[] = [];
|
||||
errorMessage.push(response.status.toString());
|
||||
errorMessage.push(response.statusText);
|
||||
if (response.data && response.data.error) {
|
||||
errorMessage.push(`${response.data.error.code} : ${response.data.error.message}`);
|
||||
}
|
||||
const error = new Error(errorMessage.join(EOL));
|
||||
if (!ignoreErrors) {
|
||||
throw error;
|
||||
}
|
||||
result.errors.push(error);
|
||||
}
|
||||
|
||||
result.response = response;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function getBlobContainers(account: azdata.Account, subscription: azureResource.AzureResourceSubscription, storageAccounts: azureResource.AzureGraphResource, ignoreErrors: boolean): Promise<GetBlobContainersResult> {
|
||||
let result: GetBlobContainersResult = { blobContainer: undefined, errors: [] };
|
||||
|
||||
if (!account?.properties?.tenants || !Array.isArray(account.properties.tenants)) {
|
||||
const error = new Error(invalidAzureAccount);
|
||||
if (!ignoreErrors) {
|
||||
throw error;
|
||||
}
|
||||
result.errors.push(error);
|
||||
}
|
||||
|
||||
if (!subscription.tenant) {
|
||||
const error = new Error(invalidTenant);
|
||||
if (!ignoreErrors) {
|
||||
throw error;
|
||||
}
|
||||
result.errors.push(error);
|
||||
}
|
||||
if (result.errors.length > 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
let securityToken: { token: string, tokenType?: string };
|
||||
let credential: TokenCredentials;
|
||||
try {
|
||||
securityToken = await azdata.accounts.getAccountSecurityToken(
|
||||
account,
|
||||
subscription.tenant!,
|
||||
azdata.AzureResource.ResourceManagement
|
||||
);
|
||||
const token = securityToken.token;
|
||||
const tokenType = securityToken.tokenType;
|
||||
credential = new TokenCredentials(token, tokenType);
|
||||
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
const error = new Error(unableToFetchTokenError(subscription.tenant));
|
||||
if (!ignoreErrors) {
|
||||
throw error;
|
||||
}
|
||||
result.errors.push(error);
|
||||
}
|
||||
|
||||
try {
|
||||
const client = new StorageManagementClient(<any>credential, subscription.id);
|
||||
result.blobContainer = await client.blobContainers.list(storageAccounts.resourceGroup, storageAccounts.name);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
if (!ignoreErrors) {
|
||||
throw err;
|
||||
}
|
||||
result.errors.push(err);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function getFileShares(account: azdata.Account, subscription: azureResource.AzureResourceSubscription, storageAccounts: azureResource.AzureGraphResource, ignoreErrors: boolean): Promise<GetFileSharesResult> {
|
||||
let result: GetFileSharesResult = { fileShares: undefined, errors: [] };
|
||||
|
||||
if (!account?.properties?.tenants || !Array.isArray(account.properties.tenants)) {
|
||||
const error = new Error(invalidAzureAccount);
|
||||
if (!ignoreErrors) {
|
||||
throw error;
|
||||
}
|
||||
result.errors.push(error);
|
||||
}
|
||||
|
||||
if (!subscription.tenant) {
|
||||
const error = new Error(invalidTenant);
|
||||
if (!ignoreErrors) {
|
||||
throw error;
|
||||
}
|
||||
result.errors.push(error);
|
||||
}
|
||||
if (result.errors.length > 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
let securityToken: { token: string, tokenType?: string };
|
||||
let credential: TokenCredentials;
|
||||
try {
|
||||
securityToken = await azdata.accounts.getAccountSecurityToken(
|
||||
account,
|
||||
subscription.tenant!,
|
||||
azdata.AzureResource.ResourceManagement
|
||||
);
|
||||
const token = securityToken.token;
|
||||
const tokenType = securityToken.tokenType;
|
||||
credential = new TokenCredentials(token, tokenType);
|
||||
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
const error = new Error(unableToFetchTokenError(subscription.tenant));
|
||||
if (!ignoreErrors) {
|
||||
throw error;
|
||||
}
|
||||
result.errors.push(error);
|
||||
}
|
||||
|
||||
try {
|
||||
const client = new StorageManagementClient(<any>credential, subscription.id);
|
||||
result.fileShares = await client.fileShares.list(storageAccounts.resourceGroup, storageAccounts.name);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
if (!ignoreErrors) {
|
||||
throw err;
|
||||
}
|
||||
result.errors.push(err);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
20
extensions/azurecore/src/azurecore.d.ts
vendored
20
extensions/azurecore/src/azurecore.d.ts
vendored
@@ -6,6 +6,8 @@
|
||||
declare module 'azurecore' {
|
||||
import * as azdata from 'azdata';
|
||||
import { azureResource } from 'azureResource';
|
||||
import { BlobContainersListResponse, FileSharesListResponse } from '@azure/arm-storage/esm/models';
|
||||
|
||||
/**
|
||||
* Covers defining what the azurecore extension exports to other extensions
|
||||
*
|
||||
@@ -66,8 +68,14 @@ declare module 'azurecore' {
|
||||
}
|
||||
|
||||
export interface IExtension {
|
||||
getSubscriptions(account?: azdata.Account, ignoreErrors?: boolean, selectedOnly?: boolean): Thenable<GetSubscriptionsResult>;
|
||||
getResourceGroups(account?: azdata.Account, subscription?: azureResource.AzureResourceSubscription, ignoreErrors?: boolean): Thenable<GetResourceGroupsResult>;
|
||||
getSubscriptions(account?: azdata.Account, ignoreErrors?: boolean, selectedOnly?: boolean): Promise<GetSubscriptionsResult>;
|
||||
getResourceGroups(account?: azdata.Account, subscription?: azureResource.AzureResourceSubscription, ignoreErrors?: boolean): Promise<GetResourceGroupsResult>;
|
||||
getSqlManagedInstances(account: azdata.Account, subscriptions: azureResource.AzureResourceSubscription[], ignoreErrors?: boolean): Promise<GetSqlManagedInstancesResult>;
|
||||
getSqlServers(account: azdata.Account, subscriptions: azureResource.AzureResourceSubscription[], ignoreErrors?: boolean): Promise<GetSqlServersResult>;
|
||||
getSqlVMServers(account: azdata.Account, subscriptions: azureResource.AzureResourceSubscription[], ignoreErrors?: boolean): Promise<GetSqlVMServersResult>;
|
||||
getStorageAccounts(account: azdata.Account, subscriptions: azureResource.AzureResourceSubscription[], ignoreErrors?: boolean): Promise<GetStorageAccountResult>;
|
||||
getBlobContainers(account: azdata.Account, subscription: azureResource.AzureResourceSubscription, storageAccount: azureResource.AzureGraphResource, ignoreErrors?: boolean): Promise<GetBlobContainersResult>;
|
||||
getFileShares(account: azdata.Account, subscription: azureResource.AzureResourceSubscription, storageAccount: azureResource.AzureGraphResource, ignoreErrors?: boolean): Promise<GetFileSharesResult>;
|
||||
/**
|
||||
* Converts a region value (@see AzureRegion) into the localized Display Name
|
||||
* @param region The region value
|
||||
@@ -76,10 +84,18 @@ declare module 'azurecore' {
|
||||
provideResources(): azureResource.IAzureResourceProvider[];
|
||||
|
||||
runGraphQuery<T extends azureResource.AzureGraphResource>(account: azdata.Account, subscriptions: azureResource.AzureResourceSubscription[], ignoreErrors: boolean, query: string): Promise<ResourceQueryResult<T>>;
|
||||
makeHttpGetRequest(account: azdata.Account, subscription: azureResource.AzureResourceSubscription, ignoreErrors: boolean, url: string): Promise<HttpGetRequestResult>;
|
||||
}
|
||||
|
||||
export type GetSubscriptionsResult = { subscriptions: azureResource.AzureResourceSubscription[], errors: Error[] };
|
||||
export type GetResourceGroupsResult = { resourceGroups: azureResource.AzureResourceResourceGroup[], errors: Error[] };
|
||||
export type GetSqlManagedInstancesResult = { resources: azureResource.AzureGraphResource[], errors: Error[] };
|
||||
export type GetSqlServersResult = {resources: azureResource.AzureGraphResource[], errors: Error[]};
|
||||
export type GetSqlVMServersResult = {resources: azureResource.AzureGraphResource[], errors: Error[]};
|
||||
export type GetStorageAccountResult = {resources: azureResource.AzureGraphResource[], errors: Error[]};
|
||||
export type GetBlobContainersResult = {blobContainer: BlobContainersListResponse | undefined, errors: Error[]};
|
||||
export type GetFileSharesResult = {fileShares: FileSharesListResponse | undefined, errors: Error[]};
|
||||
|
||||
export type ResourceQueryResult<T extends azureResource.AzureGraphResource> = { resources: T[], errors: Error[] };
|
||||
export type HttpGetRequestResult = { response: any, errors: Error[] };
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ 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';
|
||||
@@ -86,8 +87,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('connectionDialog/azureResourceExplorer', connectionDialogTree));
|
||||
pushDisposable(vscode.window.registerTreeDataProvider('azureResourceExplorer', azureResourceTree));
|
||||
pushDisposable(vscode.window.registerTreeDataProvider('connectionDialog/azureResourceExplorer', connectionDialogTree));
|
||||
pushDisposable(vscode.workspace.onDidChangeConfiguration(e => onDidChangeConfiguration(e), this));
|
||||
registerAzureResourceCommands(appContext, azureResourceTree, connectionDialogTree);
|
||||
azdata.dataprotocol.registerDataGridProvider(new AzureDataGridProvider(appContext));
|
||||
@@ -105,13 +106,47 @@ 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> {
|
||||
getSubscriptions(account?: azdata.Account, ignoreErrors?: boolean, selectedOnly: boolean = false): Promise<azurecore.GetSubscriptionsResult> {
|
||||
return selectedOnly
|
||||
? azureResourceUtils.getSelectedSubscriptions(appContext, account, ignoreErrors)
|
||||
: azureResourceUtils.getSubscriptions(appContext, account, ignoreErrors);
|
||||
},
|
||||
getResourceGroups(account?: azdata.Account, subscription?: azureResource.AzureResourceSubscription, ignoreErrors?: boolean): Thenable<azurecore.GetResourceGroupsResult> { return azureResourceUtils.getResourceGroups(appContext, account, subscription, ignoreErrors); },
|
||||
getResourceGroups(account?: azdata.Account, subscription?: azureResource.AzureResourceSubscription, ignoreErrors?: boolean): Promise<azurecore.GetResourceGroupsResult> { return azureResourceUtils.getResourceGroups(appContext, account, subscription, ignoreErrors); },
|
||||
provideResources(): azureResource.IAzureResourceProvider[] {
|
||||
const arcFeaturedEnabled = vscode.workspace.getConfiguration(constants.extensionConfigSectionName).get('enableArcFeatures');
|
||||
const providers: azureResource.IAzureResourceProvider[] = [
|
||||
@@ -129,12 +164,50 @@ export async function activate(context: vscode.ExtensionContext): Promise<azurec
|
||||
}
|
||||
return providers;
|
||||
},
|
||||
getSqlManagedInstances(account: azdata.Account,
|
||||
subscriptions: azureResource.AzureResourceSubscription[],
|
||||
ignoreErrors: boolean): Promise<azurecore.GetSqlManagedInstancesResult> {
|
||||
return azureResourceUtils.runResourceQuery(account, subscriptions, ignoreErrors, `where type == "${azureResource.AzureResourceType.sqlManagedInstance}"`);
|
||||
},
|
||||
getSqlServers(account: azdata.Account,
|
||||
subscriptions: azureResource.AzureResourceSubscription[],
|
||||
ignoreErrors: boolean): Promise<azurecore.GetSqlServersResult> {
|
||||
return azureResourceUtils.runResourceQuery(account, subscriptions, ignoreErrors, `where type == "${azureResource.AzureResourceType.sqlServer}"`);
|
||||
},
|
||||
getSqlVMServers(account: azdata.Account,
|
||||
subscriptions: azureResource.AzureResourceSubscription[],
|
||||
ignoreErrors: boolean): Promise<azurecore.GetSqlVMServersResult> {
|
||||
return azureResourceUtils.runResourceQuery(account, subscriptions, ignoreErrors, `where type == "${azureResource.AzureResourceType.virtualMachines}" and properties.storageProfile.imageReference.publisher == "microsoftsqlserver"`);
|
||||
},
|
||||
getStorageAccounts(account: azdata.Account,
|
||||
subscriptions: azureResource.AzureResourceSubscription[],
|
||||
ignoreErrors: boolean): Promise<azurecore.GetStorageAccountResult> {
|
||||
return azureResourceUtils.runResourceQuery(account, subscriptions, ignoreErrors, `where type == "${azureResource.AzureResourceType.storageAccount}"`);
|
||||
},
|
||||
getBlobContainers(account: azdata.Account,
|
||||
subscription: azureResource.AzureResourceSubscription,
|
||||
storageAccount: azureResource.AzureGraphResource,
|
||||
ignoreErrors: boolean): Promise<azurecore.GetBlobContainersResult> {
|
||||
return azureResourceUtils.getBlobContainers(account, subscription, storageAccount, ignoreErrors);
|
||||
},
|
||||
getFileShares(account: azdata.Account,
|
||||
subscription: azureResource.AzureResourceSubscription,
|
||||
storageAccount: azureResource.AzureGraphResource,
|
||||
ignoreErrors: boolean): Promise<azurecore.GetFileSharesResult> {
|
||||
return azureResourceUtils.getFileShares(account, subscription, storageAccount, ignoreErrors);
|
||||
},
|
||||
getRegionDisplayName: utils.getRegionDisplayName,
|
||||
runGraphQuery<T extends azureResource.AzureGraphResource>(account: azdata.Account,
|
||||
subscriptions: azureResource.AzureResourceSubscription[],
|
||||
ignoreErrors: boolean,
|
||||
query: string): Promise<azurecore.ResourceQueryResult<T>> {
|
||||
return azureResourceUtils.runResourceQuery(account, subscriptions, ignoreErrors, query);
|
||||
},
|
||||
makeHttpGetRequest(account: azdata.Account,
|
||||
subscription: azureResource.AzureResourceSubscription,
|
||||
ignoreErrors: boolean,
|
||||
url: string) {
|
||||
return azureResourceUtils.makeHttpGetRequest(account, subscription, ignoreErrors, url);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -74,3 +74,10 @@ export const azureArcPostgresServer = localize('azurecore.azureArcPostgres', "Az
|
||||
|
||||
export const unableToOpenAzureLink = localize('azure.unableToOpenAzureLink', "Unable to open link, missing required values");
|
||||
export const azureResourcesGridTitle = localize('azure.azureResourcesGridTitle', "Azure Resources (Preview)");
|
||||
|
||||
// Azure Request Errors
|
||||
export const invalidAzureAccount = localize('azurecore.invalidAzureAccount', "Invalid account");
|
||||
export const invalidTenant = localize('azurecore.invalidTenant', "Invalid tenant for subscription");
|
||||
export function unableToFetchTokenError(tenant: string): string {
|
||||
return localize('azurecore.unableToFetchToken', "Unable to get token for tenant {0}", tenant);
|
||||
}
|
||||
|
||||
1
extensions/azurecore/src/typings/ref.d.ts
vendored
1
extensions/azurecore/src/typings/ref.d.ts
vendored
@@ -7,4 +7,5 @@
|
||||
/// <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'/>
|
||||
|
||||
@@ -11,6 +11,15 @@
|
||||
"@azure/ms-rest-js" "^1.8.1"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@azure/arm-storage@^15.1.0":
|
||||
version "15.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@azure/arm-storage/-/arm-storage-15.1.0.tgz#fa14b5e532babf39b47c5cffe89de5aa062e1f80"
|
||||
integrity sha512-IWomHlT7eEnCSMDHH/z5/XyPHhGAIPmWYgHkIyYB2YQt+Af+hWvE1NIwI79Eeiu+Am4U8BKUsXWmWKqDYh0Srg==
|
||||
dependencies:
|
||||
"@azure/ms-rest-azure-js" "^2.0.1"
|
||||
"@azure/ms-rest-js" "^2.0.4"
|
||||
tslib "^1.10.0"
|
||||
|
||||
"@azure/arm-subscriptions@1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@azure/arm-subscriptions/-/arm-subscriptions-1.0.0.tgz#ab65a5cd4d8b8c878ff6621428f29137b84eb1d6"
|
||||
@@ -28,6 +37,14 @@
|
||||
"@azure/ms-rest-js" "^1.8.10"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@azure/ms-rest-azure-js@^2.0.1":
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@azure/ms-rest-azure-js/-/ms-rest-azure-js-2.0.1.tgz#fa1b38f039b3ee48a9e086a88c8a5b5b7776491c"
|
||||
integrity sha512-5e+A710O7gRFISoV4KI/ZyLQbKmjXxQZ1L8Z/sx7jSUQqmswjTnN4yyIZxs5JzfLVkobU0rXxbi5/LVzaI8QXQ==
|
||||
dependencies:
|
||||
"@azure/ms-rest-js" "^2.0.4"
|
||||
tslib "^1.10.0"
|
||||
|
||||
"@azure/ms-rest-js@^1.1.0", "@azure/ms-rest-js@^1.8.1", "@azure/ms-rest-js@^1.8.10":
|
||||
version "1.8.14"
|
||||
resolved "https://registry.yarnpkg.com/@azure/ms-rest-js/-/ms-rest-js-1.8.14.tgz#657fc145db20b6eb3d58d1a2055473aa72eb609d"
|
||||
@@ -42,6 +59,22 @@
|
||||
uuid "^3.2.1"
|
||||
xml2js "^0.4.19"
|
||||
|
||||
"@azure/ms-rest-js@^2.0.4":
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@azure/ms-rest-js/-/ms-rest-js-2.1.0.tgz#41bc541984983b5242dfbcf699ea281acd045946"
|
||||
integrity sha512-4BXLVImYRt+jcUmEJ5LUWglI8RBNVQndY6IcyvQ4U8O4kIXdmlRz3cJdA/RpXf5rKT38KOoTO2T6Z1f6Z1HDBg==
|
||||
dependencies:
|
||||
"@types/node-fetch" "^2.3.7"
|
||||
"@types/tunnel" "0.0.1"
|
||||
abort-controller "^3.0.0"
|
||||
form-data "^2.5.0"
|
||||
node-fetch "^2.6.0"
|
||||
tough-cookie "^3.0.1"
|
||||
tslib "^1.10.0"
|
||||
tunnel "0.0.6"
|
||||
uuid "^3.3.2"
|
||||
xml2js "^0.4.19"
|
||||
|
||||
"@babel/code-frame@^7.8.3":
|
||||
version "7.8.3"
|
||||
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.8.3.tgz#33e25903d7481181534e12ec0a25f16b6fcf419e"
|
||||
@@ -275,6 +308,14 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-5.2.7.tgz#315d570ccb56c53452ff8638738df60726d5b6ea"
|
||||
integrity sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==
|
||||
|
||||
"@types/node-fetch@^2.3.7":
|
||||
version "2.5.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.7.tgz#20a2afffa882ab04d44ca786449a276f9f6bbf3c"
|
||||
integrity sha512-o2WVNf5UhWRkxlf6eq+jMZDu7kjgpgJfl4xVNlvryc95O/6F2ld8ztKX+qu+Rjyet93WAWm5LjeX9H5FGkODvw==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
form-data "^3.0.0"
|
||||
|
||||
"@types/node@*":
|
||||
version "13.9.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-13.9.5.tgz#59738bf30b31aea1faa2df7f4a5f55613750cf00"
|
||||
@@ -324,6 +365,13 @@
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/tunnel@0.0.1":
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/tunnel/-/tunnel-0.0.1.tgz#0d72774768b73df26f25df9184273a42da72b19c"
|
||||
integrity sha512-AOqu6bQu5MSWwYvehMXLukFHnupHrpZ8nvgae5Ggie9UwzDR1CCwoXgSSWNZJuyOlCdfdsWMA5F2LlmvyoTv8A==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/ws@^6.0.4":
|
||||
version "6.0.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-6.0.4.tgz#7797707c8acce8f76d8c34b370d4645b70421ff1"
|
||||
@@ -331,6 +379,13 @@
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
abort-controller@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392"
|
||||
integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==
|
||||
dependencies:
|
||||
event-target-shim "^5.0.0"
|
||||
|
||||
ansi-regex@^2.0.0:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df"
|
||||
@@ -466,7 +521,7 @@ color-name@1.1.3:
|
||||
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
|
||||
integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
|
||||
|
||||
combined-stream@^1.0.6:
|
||||
combined-stream@^1.0.6, combined-stream@^1.0.8:
|
||||
version "1.0.8"
|
||||
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
|
||||
integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
|
||||
@@ -596,6 +651,11 @@ escape-string-regexp@1.0.5, escape-string-regexp@^1.0.5:
|
||||
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
|
||||
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
|
||||
|
||||
event-target-shim@^5.0.0:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789"
|
||||
integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==
|
||||
|
||||
expand-template@^2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c"
|
||||
@@ -617,6 +677,15 @@ form-data@^2.3.2, form-data@^2.5.0:
|
||||
combined-stream "^1.0.6"
|
||||
mime-types "^2.1.12"
|
||||
|
||||
form-data@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.0.tgz#31b7e39c85f1355b7139ee0c647cf0de7f83c682"
|
||||
integrity sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg==
|
||||
dependencies:
|
||||
asynckit "^0.4.0"
|
||||
combined-stream "^1.0.8"
|
||||
mime-types "^2.1.12"
|
||||
|
||||
fs-constants@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
|
||||
@@ -733,6 +802,11 @@ ini@~1.3.0:
|
||||
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
|
||||
integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==
|
||||
|
||||
ip-regex@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9"
|
||||
integrity sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=
|
||||
|
||||
is-buffer@~1.1.1:
|
||||
version "1.1.6"
|
||||
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
|
||||
@@ -1009,6 +1083,11 @@ node-abi@^2.7.0:
|
||||
dependencies:
|
||||
semver "^5.4.1"
|
||||
|
||||
node-fetch@^2.6.0:
|
||||
version "2.6.1"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
|
||||
integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
|
||||
|
||||
noop-logger@^0.1.1:
|
||||
version "0.1.1"
|
||||
resolved "https://registry.yarnpkg.com/noop-logger/-/noop-logger-0.1.1.tgz#94a2b1633c4f1317553007d8966fd0e841b6a4c2"
|
||||
@@ -1389,6 +1468,20 @@ tough-cookie@^2.4.3:
|
||||
psl "^1.1.28"
|
||||
punycode "^2.1.1"
|
||||
|
||||
tough-cookie@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-3.0.1.tgz#9df4f57e739c26930a018184887f4adb7dca73b2"
|
||||
integrity sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg==
|
||||
dependencies:
|
||||
ip-regex "^2.1.0"
|
||||
psl "^1.1.28"
|
||||
punycode "^2.1.1"
|
||||
|
||||
tslib@^1.10.0:
|
||||
version "1.14.1"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
|
||||
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
||||
|
||||
tslib@^1.9.2, tslib@^1.9.3:
|
||||
version "1.11.1"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.11.1.tgz#eb15d128827fbee2841549e171f45ed338ac7e35"
|
||||
@@ -1425,7 +1518,7 @@ util-deprecate@^1.0.1, util-deprecate@~1.0.1:
|
||||
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
|
||||
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
|
||||
|
||||
uuid@^3.2.1:
|
||||
uuid@^3.2.1, uuid@^3.3.2:
|
||||
version "3.4.0"
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
|
||||
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
{
|
||||
"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?{"serverName":"localhost,","providerName":"MSSQL","authenticationType":"SqlLogin","userName":"sa","password":""}\"><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
|
||||
}
|
||||
]
|
||||
}
|
||||
18
extensions/data-workspace/images/Open_existing_Project.svg
Normal file
18
extensions/data-workspace/images/Open_existing_Project.svg
Normal file
@@ -0,0 +1,18 @@
|
||||
<svg id="f43e4801-4f55-4d5f-909d-6739feccec92" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100" height="100" viewBox="0 0 100 100">
|
||||
<defs>
|
||||
<linearGradient id="b7240381-76e4-42bd-96fb-e944ca59ba0a" x1="50" y1="87.092" x2="50" y2="31.71" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#0078d4"/>
|
||||
<stop offset="1" stop-color="#5ea0ef"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g>
|
||||
<path d="M22.615,39.642h54.77V70.185a1.836,1.836,0,0,1-1.836,1.836h-51.1a1.836,1.836,0,0,1-1.836-1.836Z" fill="url(#b7240381-76e4-42bd-96fb-e944ca59ba0a)"/>
|
||||
<path d="M24.451,27.979h51.1a1.836,1.836,0,0,1,1.836,1.836v9.827H22.615V29.783A1.835,1.835,0,0,1,24.451,27.979Z" fill="#8fc9f9"/>
|
||||
<g>
|
||||
<circle cx="27.869" cy="34.304" r="1.713" fill="#0078d4"/>
|
||||
<circle cx="34.364" cy="34.304" r="1.713" fill="#0078d4"/>
|
||||
<circle cx="40.86" cy="34.304" r="1.713" fill="#0078d4"/>
|
||||
</g>
|
||||
<path d="M22.615,39.642h54.77V70.185a1.836,1.836,0,0,1-1.836,1.836h-51.1a1.836,1.836,0,0,1-1.836-1.836Z" fill="#feffff" opacity="0.07"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
16
extensions/data-workspace/images/Open_existing_Workspace.svg
Normal file
16
extensions/data-workspace/images/Open_existing_Workspace.svg
Normal file
@@ -0,0 +1,16 @@
|
||||
<svg id="a7873e7b-9149-4583-acd5-327a6cca8a74" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100" height="100" viewBox="0 0 100 100">
|
||||
<defs>
|
||||
<linearGradient id="fa1a2df5-b243-4ccd-8fb8-af2408d310fc" x1="324" y1="432.822" x2="324" y2="483.565" gradientTransform="translate(-274 -408)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#5ea0ef"/>
|
||||
<stop offset="1" stop-color="#0078d4"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g>
|
||||
<path d="M24.225,30.625H75.807a1.612,1.612,0,0,1,1.644,1.483V67.892a1.579,1.579,0,0,1-1.644,1.483H24.225a1.612,1.612,0,0,1-1.676-1.483V32.172a1.612,1.612,0,0,1,1.674-1.547Z" fill="url(#fa1a2df5-b243-4ccd-8fb8-af2408d310fc)"/>
|
||||
<path d="M45.5,36.008h9.284a.452.452,0,0,1,.452.452h0v9.671a.419.419,0,0,1-.419.419H45.7a.451.451,0,0,1-.451-.451V36.428a.418.418,0,0,1,.29-.42Z" fill="#fff"/>
|
||||
<path d="M45.5,51.58h9.284a.487.487,0,0,1,.452.451V61.7a.42.42,0,0,1-.419.42H45.7a.452.452,0,0,1-.451-.452h0V52a.416.416,0,0,1,.29-.419ZM62.589,36.008h9.285a.452.452,0,0,1,.451.452h0v9.671a.419.419,0,0,1-.419.419H62.75A.451.451,0,0,1,62.3,46.1h0V36.428A.418.418,0,0,1,62.589,36.008Z" fill="#83b9f9"/>
|
||||
<path d="M62.5,51.58h9.284a.487.487,0,0,1,.452.451V61.7a.42.42,0,0,1-.419.42H62.7a.452.452,0,0,1-.451-.452h0V52a.416.416,0,0,1,.29-.419Z" fill="#83b9f9"/>
|
||||
<path d="M29.255,51.58h9.284a.487.487,0,0,1,.452.451V61.7a.42.42,0,0,1-.419.42H29.448A.452.452,0,0,1,29,61.67h0V52a.414.414,0,0,1,.29-.419Z" fill="#fff"/>
|
||||
<path d="M29.255,36.008h9.284a.489.489,0,0,1,.452.452v9.671a.419.419,0,0,1-.419.419H29.448A.451.451,0,0,1,29,46.1h0V36.428a.416.416,0,0,1,.29-.42Z" fill="#fff"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
@@ -1 +0,0 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#231f20;}.cls-2{fill:#212121;}</style></defs><title>file_16x16</title><polygon class="cls-1" points="13.59 2.21 13.58 2.22 13.58 2.2 13.59 2.21"/><path class="cls-2" d="M8.71,0,14,5.29V16H2V0ZM3,15H13V6H8V1H3ZM9,1.71V5h3.29Z"/></svg>
|
||||
|
Before Width: | Height: | Size: 351 B |
@@ -1 +0,0 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#fff;}</style></defs><title>file_inverse_16x16</title><polygon class="cls-1" points="13.59 2.21 13.58 2.22 13.58 2.2 13.59 2.21"/><path class="cls-1" d="M8.71,0,14,5.29V16H2V0ZM3,15H13V6H8V1H3ZM9,1.71V5h3.29Z"/></svg>
|
||||
|
Before Width: | Height: | Size: 335 B |
@@ -27,4 +27,9 @@ export class DataWorkspaceExtension implements IExtension {
|
||||
get defaultProjectSaveLocation(): vscode.Uri | undefined {
|
||||
return defaultProjectSaveLocation();
|
||||
}
|
||||
|
||||
validateWorkspace(): Promise<boolean> {
|
||||
return this.workspaceService.validateWorkspace();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
10
extensions/data-workspace/src/dataworkspace.d.ts
vendored
10
extensions/data-workspace/src/dataworkspace.d.ts
vendored
@@ -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,6 +33,11 @@ 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>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -55,8 +60,9 @@ declare module 'dataworkspace' {
|
||||
*
|
||||
* @param name Create a project
|
||||
* @param location the parent directory of the project
|
||||
* @param projectTypeId the identifier of the selected project type
|
||||
*/
|
||||
createProject(name: string, location: vscode.Uri): Promise<vscode.Uri>;
|
||||
createProject(name: string, location: vscode.Uri, projectTypeId: string): Promise<vscode.Uri>;
|
||||
|
||||
/**
|
||||
* Gets the supported project types
|
||||
|
||||
@@ -84,8 +84,8 @@ export class NewProjectDialog extends DialogBase {
|
||||
]
|
||||
};
|
||||
}),
|
||||
iconHeight: '50px',
|
||||
iconWidth: '50px',
|
||||
iconHeight: '75px',
|
||||
iconWidth: '75px',
|
||||
cardWidth: '170px',
|
||||
cardHeight: '170px',
|
||||
ariaLabel: constants.TypeTitle,
|
||||
|
||||
@@ -22,16 +22,10 @@ export class OpenExistingDialog extends DialogBase {
|
||||
private _targetTypes = [
|
||||
{
|
||||
name: constants.Project,
|
||||
icon: {
|
||||
dark: this.extensionContext.asAbsolutePath('images/file_inverse.svg'),
|
||||
light: this.extensionContext.asAbsolutePath('images/file.svg')
|
||||
}
|
||||
icon: this.extensionContext.asAbsolutePath('images/Open_existing_Project.svg')
|
||||
}, {
|
||||
name: constants.Workspace,
|
||||
icon: {
|
||||
dark: this.extensionContext.asAbsolutePath('images/file_inverse.svg'), // temporary - still waiting for real icon from UX
|
||||
light: this.extensionContext.asAbsolutePath('images/file.svg')
|
||||
}
|
||||
icon: this.extensionContext.asAbsolutePath('images/Open_existing_Workspace.svg')
|
||||
}
|
||||
];
|
||||
|
||||
@@ -97,8 +91,8 @@ export class OpenExistingDialog extends DialogBase {
|
||||
]
|
||||
};
|
||||
}),
|
||||
iconHeight: '50px',
|
||||
iconWidth: '50px',
|
||||
iconHeight: '100px',
|
||||
iconWidth: '100px',
|
||||
cardWidth: '170px',
|
||||
cardHeight: '170px',
|
||||
ariaLabel: constants.TypeTitle,
|
||||
|
||||
@@ -174,7 +174,7 @@ export class WorkspaceService implements IWorkspaceService {
|
||||
async createProject(name: string, location: vscode.Uri, projectTypeId: string): Promise<vscode.Uri> {
|
||||
const provider = ProjectProviderRegistry.getProviderByProjectType(projectTypeId);
|
||||
if (provider) {
|
||||
const projectFile = await provider.createProject(name, location);
|
||||
const projectFile = await provider.createProject(name, location, projectTypeId);
|
||||
this.addProjectsToWorkspace([projectFile]);
|
||||
this._onDidWorkspaceProjectsChange.fire();
|
||||
return projectFile;
|
||||
|
||||
@@ -29,7 +29,7 @@ export function createProjectProvider(projectTypes: IProjectType[]): IProjectPro
|
||||
getProjectTreeDataProvider: (projectFile: vscode.Uri): Promise<vscode.TreeDataProvider<any>> => {
|
||||
return Promise.resolve(treeDataProvider);
|
||||
},
|
||||
createProject: (name: string, location: vscode.Uri): Promise<vscode.Uri> => {
|
||||
createProject: (name: string, location: vscode.Uri, projectTypeId: string): Promise<vscode.Uri> => {
|
||||
return Promise.resolve(location);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -16,6 +16,7 @@ export const developers: string[] = [
|
||||
'ktech99',
|
||||
'kburtram',
|
||||
'lucyzhang929',
|
||||
'saiavishkarsreerama',
|
||||
'smartguest',
|
||||
'udeeshagautam',
|
||||
'VasuBhog'
|
||||
|
||||
@@ -17,19 +17,21 @@ import { promisify } from 'util';
|
||||
|
||||
const retryCount = 24; // 2 minutes
|
||||
const dacpac1: string = path.join(__dirname, '../../testData/Database1.dacpac');
|
||||
suite('Dacpac integration test suite', () => {
|
||||
|
||||
|
||||
suite('Dacpac integration test suite @DacFx@', () => {
|
||||
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();
|
||||
await utils.connectToServer(server);
|
||||
const connectionId = await utils.connectToServer(server);
|
||||
assert(connectionId, `Failed to connect to "${server.serverName}"`);
|
||||
|
||||
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 ownerUri = await azdata.connection.getUriForConnection(connectionId);
|
||||
const now = new Date();
|
||||
const databaseName = 'ADS_deployDacpac_' + now.getTime().toString();
|
||||
|
||||
@@ -70,12 +72,13 @@ suite('Dacpac integration test suite', () => {
|
||||
|
||||
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 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 connectionId = await utils.connectToServer(server);
|
||||
assert(connectionId, `Failed to connect to "${server.serverName}"`);
|
||||
const ownerUri = await azdata.connection.getUriForConnection(connectionId);
|
||||
const now = new Date();
|
||||
const databaseName = 'ADS_importBacpac_' + now.getTime().toString();
|
||||
|
||||
|
||||
@@ -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 } from './testConfig';
|
||||
import { getStandaloneServer, TestServerProfile } 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', () => {
|
||||
suite('Schema compare integration test suite @DacFx@', () => {
|
||||
suiteSetup(async function () {
|
||||
let attempts: number = 20;
|
||||
while (attempts > 0) {
|
||||
@@ -40,6 +40,7 @@ suite('Schema compare integration test suite', () => {
|
||||
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();
|
||||
@@ -82,16 +83,9 @@ suite('Schema compare integration test suite', () => {
|
||||
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();
|
||||
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 ownerUri = await getConnectionUri(server);
|
||||
const now = new Date();
|
||||
|
||||
const operationId = 'testOperationId_' + now.getTime().toString();
|
||||
@@ -161,16 +155,9 @@ suite('Schema compare integration test suite', () => {
|
||||
}
|
||||
});
|
||||
test('Schema compare dacpac to database comparison, script generation, and scmp @UNSTABLE@', async function () {
|
||||
this.timeout(5 * 60 * 1000);
|
||||
let server = await getStandaloneServer();
|
||||
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 ownerUri = await getConnectionUri(server);
|
||||
const now = new Date();
|
||||
const operationId = 'testOperationId_' + now.getTime().toString();
|
||||
const targetDB: string = 'ads_schemaCompare_targetDB_' + now.getTime().toString();
|
||||
@@ -228,6 +215,7 @@ suite('Schema compare integration test suite', () => {
|
||||
}
|
||||
});
|
||||
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();
|
||||
|
||||
@@ -284,6 +272,17 @@ suite('Schema compare integration test suite', () => {
|
||||
});
|
||||
});
|
||||
|
||||
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) {
|
||||
|
||||
@@ -35,9 +35,15 @@ 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 () => {
|
||||
@@ -180,6 +186,26 @@ 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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"downloadUrl": "https://github.com/Microsoft/sqltoolsservice/releases/download/v{#version#}/microsoft.sqltools.servicelayer-{#fileName#}",
|
||||
"version": "3.0.0-release.52",
|
||||
"version": "3.0.0-release.60",
|
||||
"downloadFileNames": {
|
||||
"Windows_86": "win-x86-netcoreapp3.1.zip",
|
||||
"Windows_64": "win-x64-netcoreapp3.1.zip",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "kusto",
|
||||
"version": "0.3.2",
|
||||
"version": "0.4.0",
|
||||
"publisher": "Microsoft",
|
||||
"aiKey": "AIF-444c3af9-8e69-4462-ab49-4191e6ad1916",
|
||||
"activationEvents": [
|
||||
|
||||
@@ -60,7 +60,7 @@ export class GuestSessionManager {
|
||||
connectionOptions['serverName'] = documentState.serverName;
|
||||
connectionOptions['databaseName'] = documentState.databaseName;
|
||||
connectionOptions['userName'] = 'liveshare';
|
||||
connectionOptions['password'] = 'liveshare';
|
||||
connectionOptions['password'] = 'liveshare'; // [SuppressMessage("Microsoft.Security", "CS001:SecretInline", Justification="Stub value for testing")]
|
||||
connectionOptions['authenticationType'] = 'liveshare';
|
||||
connectionOptions['savePassword'] = false;
|
||||
connectionOptions['saveProfile'] = false;
|
||||
|
||||
@@ -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';
|
||||
connectionOptions['password'] = 'liveshare'; // [SuppressMessage("Microsoft.Security", "CS001:SecretInline", Justification="Stub value for testing")]
|
||||
connectionOptions['authenticationType'] = 'liveshare';
|
||||
connectionOptions['savePassword'] = false;
|
||||
connectionOptions['saveProfile'] = false;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "machine-learning",
|
||||
"displayName": "%displayName%",
|
||||
"description": "%description%",
|
||||
"version": "0.5.0",
|
||||
"version": "0.6.0",
|
||||
"publisher": "Microsoft",
|
||||
"preview": true,
|
||||
"engines": {
|
||||
|
||||
@@ -8,13 +8,34 @@ import * as azurecore from 'azurecore';
|
||||
import { azureResource } from 'azureResource';
|
||||
|
||||
export class AzurecoreApiStub implements azurecore.IExtension {
|
||||
getFileShares(_account: azdata.Account, _subscription: azureResource.AzureResourceSubscription, _storageAccount: azureResource.AzureGraphResource, _ignoreErrors?: boolean): Promise<azurecore.GetFileSharesResult> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
getBlobContainers(_account: azdata.Account, _subscription: azureResource.AzureResourceSubscription, _storageAccount: azureResource.AzureGraphResource, _ignoreErrors?: boolean): Promise<azurecore.GetBlobContainersResult> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
getSqlManagedInstances(_account: azdata.Account, _subscriptions: azureResource.AzureResourceSubscription[], _ignoreErrors?: boolean): Promise<azurecore.GetSqlManagedInstancesResult> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
getSqlServers(_account: azdata.Account, _subscriptions: azureResource.AzureResourceSubscription[], _ignoreErrors?: boolean): Promise<azurecore.GetSqlServersResult> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
getSqlVMServers(_account: azdata.Account, _subscriptions: azureResource.AzureResourceSubscription[], _ignoreErrors?: boolean): Promise<azurecore.GetSqlVMServersResult> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
getStorageAccounts(_account: azdata.Account, _subscriptions: azureResource.AzureResourceSubscription[], _ignoreErrors?: boolean): Promise<azurecore.GetStorageAccountResult> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
makeHttpGetRequest(_account: azdata.Account, _subscription: azureResource.AzureResourceSubscription, _ignoreErrors: boolean, _url: string): Promise<any> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
runGraphQuery<T extends azureResource.AzureGraphResource>(_account: azdata.Account, _subscriptions: azureResource.AzureResourceSubscription[], _ignoreErrors: boolean, _query: string): Promise<azurecore.ResourceQueryResult<T>> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
getSubscriptions(_account?: azdata.Account | undefined, _ignoreErrors?: boolean | undefined, _selectedOnly?: boolean | undefined): Thenable<azurecore.GetSubscriptionsResult> {
|
||||
getSubscriptions(_account?: azdata.Account | undefined, _ignoreErrors?: boolean | undefined, _selectedOnly?: boolean | undefined): Promise<azurecore.GetSubscriptionsResult> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
getResourceGroups(_account?: azdata.Account | undefined, _subscription?: azureResource.AzureResourceSubscription | undefined, _ignoreErrors?: boolean | undefined): Thenable<azurecore.GetResourceGroupsResult> {
|
||||
getResourceGroups(_account?: azdata.Account | undefined, _subscription?: azureResource.AzureResourceSubscription | undefined, _ignoreErrors?: boolean | undefined): Promise<azurecore.GetResourceGroupsResult> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
getRegionDisplayName(_region?: string | undefined): string {
|
||||
|
||||
@@ -325,7 +325,7 @@
|
||||
"watch-web": "npx webpack-cli --config extension-browser.webpack.config --mode none --watch --info-verbosity verbose"
|
||||
},
|
||||
"dependencies": {
|
||||
"highlight.js": "9.15.10",
|
||||
"highlight.js": "10.4.1",
|
||||
"markdown-it": "^10.0.0",
|
||||
"markdown-it-front-matter": "^0.2.1",
|
||||
"vscode-extension-telemetry": "0.1.1",
|
||||
|
||||
@@ -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@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==
|
||||
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==
|
||||
|
||||
hmac-drbg@^1.0.0:
|
||||
version "1.0.1"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"downloadUrl": "https://github.com/Microsoft/sqltoolsservice/releases/download/v{#version#}/microsoft.sqltools.servicelayer-{#fileName#}",
|
||||
"version": "3.0.0-release.59",
|
||||
"version": "3.0.0-release.61",
|
||||
"downloadFileNames": {
|
||||
"Windows_86": "win-x86-netcoreapp3.1.zip",
|
||||
"Windows_64": "win-x64-netcoreapp3.1.zip",
|
||||
|
||||
@@ -9,8 +9,8 @@ import { BookTreeItem } from './bookTreeItem';
|
||||
import { getPinnedNotebooks, setPinnedBookPathsInConfig, IBookNotebook } from '../common/utils';
|
||||
|
||||
export interface IBookPinManager {
|
||||
pinNotebook(notebook: BookTreeItem): boolean;
|
||||
unpinNotebook(notebook: BookTreeItem): boolean;
|
||||
pinNotebook(notebook: BookTreeItem): Promise<boolean>;
|
||||
unpinNotebook(notebook: BookTreeItem): Promise<boolean>;
|
||||
}
|
||||
|
||||
enum PinBookOperation {
|
||||
@@ -39,20 +39,20 @@ export class BookPinManager implements IBookPinManager {
|
||||
return false;
|
||||
}
|
||||
|
||||
pinNotebook(notebook: BookTreeItem): boolean {
|
||||
return this.isNotebookPinned(notebook.book.contentPath) ? false : this.updatePinnedBooks(notebook, PinBookOperation.Pin);
|
||||
async pinNotebook(notebook: BookTreeItem): Promise<boolean> {
|
||||
return this.isNotebookPinned(notebook.book.contentPath) ? false : await this.updatePinnedBooks(notebook, PinBookOperation.Pin);
|
||||
}
|
||||
|
||||
unpinNotebook(notebook: BookTreeItem): boolean {
|
||||
return this.updatePinnedBooks(notebook, PinBookOperation.Unpin);
|
||||
async unpinNotebook(notebook: BookTreeItem): Promise<boolean> {
|
||||
return await this.updatePinnedBooks(notebook, PinBookOperation.Unpin);
|
||||
}
|
||||
|
||||
updatePinnedBooks(notebook: BookTreeItem, operation: PinBookOperation) {
|
||||
async updatePinnedBooks(notebook: BookTreeItem, operation: PinBookOperation): Promise<boolean> {
|
||||
let modifiedPinnedBooks = false;
|
||||
let bookPathToChange: string = notebook.book.contentPath;
|
||||
|
||||
let pinnedBooks: IBookNotebook[] = getPinnedNotebooks();
|
||||
let existingBookIndex = pinnedBooks.map(pinnedBookPath => path.normalize(pinnedBookPath?.notebookPath)).indexOf(bookPathToChange);
|
||||
let existingBookIndex = pinnedBooks.map(pinnedBookPath => path.normalize(pinnedBookPath?.notebookPath)).indexOf(path.normalize(bookPathToChange));
|
||||
|
||||
if (existingBookIndex !== -1 && operation === PinBookOperation.Unpin) {
|
||||
pinnedBooks.splice(existingBookIndex, 1);
|
||||
@@ -63,9 +63,8 @@ export class BookPinManager implements IBookPinManager {
|
||||
modifiedPinnedBooks = true;
|
||||
}
|
||||
|
||||
setPinnedBookPathsInConfig(pinnedBooks);
|
||||
await setPinnedBookPathsInConfig(pinnedBooks);
|
||||
this.setPinnedSectionContext();
|
||||
|
||||
return modifiedPinnedBooks;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,7 +117,7 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
|
||||
async pinNotebook(bookTreeItem: BookTreeItem): Promise<void> {
|
||||
let bookPathToUpdate = bookTreeItem.book?.contentPath;
|
||||
if (bookPathToUpdate) {
|
||||
let pinStatusChanged = this.bookPinManager.pinNotebook(bookTreeItem);
|
||||
let pinStatusChanged = await this.bookPinManager.pinNotebook(bookTreeItem);
|
||||
if (pinStatusChanged) {
|
||||
bookTreeItem.contextValue = 'pinnedNotebook';
|
||||
}
|
||||
@@ -127,7 +127,7 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
|
||||
async unpinNotebook(bookTreeItem: BookTreeItem): Promise<void> {
|
||||
let bookPathToUpdate = bookTreeItem.book?.contentPath;
|
||||
if (bookPathToUpdate) {
|
||||
let pinStatusChanged = this.bookPinManager.unpinNotebook(bookTreeItem);
|
||||
let pinStatusChanged = await this.bookPinManager.unpinNotebook(bookTreeItem);
|
||||
if (pinStatusChanged) {
|
||||
bookTreeItem.contextValue = 'savedNotebook';
|
||||
}
|
||||
|
||||
@@ -378,13 +378,14 @@ function hasWorkspaceFolders(): boolean {
|
||||
return workspaceFolders && workspaceFolders.length > 0;
|
||||
}
|
||||
|
||||
export function setPinnedBookPathsInConfig(pinnedNotebookPaths: IBookNotebook[]) {
|
||||
export async function setPinnedBookPathsInConfig(pinnedNotebookPaths: IBookNotebook[]): Promise<void> {
|
||||
let config: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration(notebookConfigKey);
|
||||
let storeInWorspace: boolean = hasWorkspaceFolders();
|
||||
|
||||
config.update(pinnedBooksConfigKey, pinnedNotebookPaths, storeInWorspace ? false : vscode.ConfigurationTarget.Global);
|
||||
await config.update(pinnedBooksConfigKey, pinnedNotebookPaths, storeInWorspace ? false : vscode.ConfigurationTarget.Global);
|
||||
}
|
||||
|
||||
|
||||
export interface IBookNotebook {
|
||||
bookPath?: string;
|
||||
notebookPath: string;
|
||||
|
||||
@@ -4,11 +4,13 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
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)
|
||||
registerOptionsSourceProvider: (provider: rd.IOptionsSourceProvider) => optionsSourcesService.registerOptionsSourceProvider(provider),
|
||||
registerValueProvider: (provider: rd.IValueProvider) => valueProviderService.registerValueProvider(provider)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -261,6 +261,11 @@ export interface DynamicEnablementInfo {
|
||||
value: string
|
||||
}
|
||||
|
||||
export interface ValueProviderInfo {
|
||||
providerId: string,
|
||||
triggerField: string
|
||||
}
|
||||
|
||||
export interface FieldInfoBase {
|
||||
labelWidth?: string;
|
||||
inputWidth?: string;
|
||||
@@ -307,9 +312,8 @@ 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 {
|
||||
|
||||
@@ -28,7 +28,9 @@ 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);
|
||||
|
||||
@@ -3,16 +3,24 @@
|
||||
* 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): void {
|
||||
if (this._optionsSourceStore.has(provider.optionsSourceId)) {
|
||||
throw new Error(loc.optionsSourceAlreadyDefined(provider.optionsSourceId));
|
||||
registerOptionsSourceProvider(provider: rd.IOptionsSourceProvider): vscode.Disposable {
|
||||
if (this._optionsSourceStore.has(provider.id)) {
|
||||
throw new Error(loc.optionsSourceAlreadyDefined(provider.id));
|
||||
}
|
||||
this._optionsSourceStore.set(provider.optionsSourceId, provider);
|
||||
this._optionsSourceStore.set(provider.id, provider);
|
||||
return {
|
||||
dispose: () => this.unregisterOptionsSourceProvider(provider.id)
|
||||
};
|
||||
}
|
||||
|
||||
private unregisterOptionsSourceProvider(providerId: string): void {
|
||||
this._optionsSourceStore.delete(providerId);
|
||||
}
|
||||
|
||||
getOptionsSource(optionsSourceProviderId: string): rd.IOptionsSourceProvider {
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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();
|
||||
@@ -3,8 +3,11 @@
|
||||
* 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 {
|
||||
@@ -103,3 +106,117 @@ 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(); }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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');
|
||||
});
|
||||
});
|
||||
@@ -4,17 +4,31 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
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 optionsSourceId: string,
|
||||
readonly id: 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
|
||||
*
|
||||
@@ -23,6 +37,7 @@ declare module 'resource-deployment' {
|
||||
*/
|
||||
|
||||
export interface IExtension {
|
||||
registerOptionsSourceProvider(provider: IOptionsSourceProvider): void
|
||||
registerOptionsSourceProvider(provider: IOptionsSourceProvider): vscode.Disposable,
|
||||
registerValueProvider(provider: IValueProvider): vscode.Disposable
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ 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';
|
||||
@@ -38,6 +39,7 @@ 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
|
||||
@@ -200,6 +202,7 @@ 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
|
||||
};
|
||||
}
|
||||
@@ -240,6 +243,7 @@ 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
|
||||
};
|
||||
}
|
||||
@@ -265,6 +269,7 @@ 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,
|
||||
};
|
||||
@@ -331,6 +336,7 @@ 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 }; }),
|
||||
{
|
||||
@@ -371,7 +377,8 @@ async function hookUpDynamicEnablement(context: WizardPageContext): Promise<void
|
||||
}
|
||||
const updateFields = async () => {
|
||||
const targetComponentValue = await targetComponent.getValue();
|
||||
fieldComponent.component.enabled = targetComponentValue === targetValue;
|
||||
const valuesMatch = targetComponentValue === targetValue;
|
||||
fieldComponent.component.enabled = valuesMatch;
|
||||
const isRequired = fieldComponent.component.enabled === false ? false : field.required;
|
||||
if (fieldComponent.labelComponent) {
|
||||
fieldComponent.labelComponent.requiredIndicator = isRequired;
|
||||
@@ -380,6 +387,41 @@ 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();
|
||||
@@ -623,6 +665,7 @@ 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
|
||||
});
|
||||
}
|
||||
@@ -659,6 +702,7 @@ 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
|
||||
});
|
||||
}
|
||||
@@ -755,6 +799,7 @@ 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;
|
||||
@@ -938,6 +983,7 @@ async function createRadioOptions(context: FieldContext, getRadioButtonInfo?: ((
|
||||
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,
|
||||
});
|
||||
@@ -1133,6 +1179,7 @@ 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
|
||||
});
|
||||
@@ -1394,3 +1441,18 @@ 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ 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 = {};
|
||||
@@ -58,16 +59,27 @@ export class NotebookWizardModel extends ResourceTypeModel {
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the notebook and returns true on successful generation
|
||||
* Generates the notebook and returns true if generation was done and so the wizard should be closed.
|
||||
**/
|
||||
public async onGenerateScript(): Promise<boolean> {
|
||||
const lastPage = this.wizard.lastPage! as NotebookWizardPage;
|
||||
if (lastPage.validatePage()) {
|
||||
const notebook = await this.prepareNotebookAndEnvironment();
|
||||
await this.openNotebook(notebook);
|
||||
return true;
|
||||
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
|
||||
} else {
|
||||
return false;
|
||||
return false; // validation failed so do not attempt to generate the notebook and do not close the wizard
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,7 +94,7 @@ export class NotebookWizardModel extends ResourceTypeModel {
|
||||
return await this.notebookService.openNotebookWithContent(notebookPath, JSON.stringify(notebook, undefined, 4));
|
||||
}
|
||||
|
||||
private async prepareNotebookAndEnvironment() {
|
||||
private async prepareNotebookAndEnvironment(): Promise<Notebook> {
|
||||
await setModelValues(this.inputComponents, this);
|
||||
const env: NodeJS.ProcessEnv = process.env;
|
||||
this.setEnvironmentVariables(env, (varName) => {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "schema-compare",
|
||||
"displayName": "%displayName%",
|
||||
"description": "%description%",
|
||||
"version": "1.8.0",
|
||||
"version": "1.9.0",
|
||||
"publisher": "Microsoft",
|
||||
"preview": false,
|
||||
"engines": {
|
||||
@@ -87,13 +87,15 @@
|
||||
},
|
||||
"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"
|
||||
"vscodetestcover": "^1.1.0",
|
||||
"sinon": "^9.0.2"
|
||||
},
|
||||
"__metadata": {
|
||||
"id": "37",
|
||||
|
||||
@@ -1,48 +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 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,11 +4,10 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
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(new ApiWrapper(), undefined, extensionContext).start(context); });
|
||||
vscode.commands.registerCommand('schemaCompare.start', async (context: any) => { await new SchemaCompareMainWindow(undefined, extensionContext).start(context); });
|
||||
}
|
||||
|
||||
export function deactivate(): void {
|
||||
|
||||
@@ -14,7 +14,6 @@ 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
|
||||
@@ -51,8 +50,8 @@ export class SchemaCompareMainWindow {
|
||||
private SchemaCompareActionMap: Map<Number, string>;
|
||||
private operationId: string;
|
||||
protected comparisonResult: mssql.SchemaCompareResult;
|
||||
private sourceNameComponent: azdata.TableComponent;
|
||||
private targetNameComponent: azdata.TableComponent;
|
||||
private sourceNameComponent: azdata.InputBoxComponent;
|
||||
private targetNameComponent: azdata.InputBoxComponent;
|
||||
private deploymentOptions: mssql.DeploymentOptions;
|
||||
private schemaCompareOptionDialog: SchemaCompareOptionsDialog;
|
||||
private tablelistenersToDispose: vscode.Disposable[] = [];
|
||||
@@ -69,7 +68,7 @@ export class SchemaCompareMainWindow {
|
||||
public sourceEndpointInfo: mssql.SchemaCompareEndpointInfo;
|
||||
public targetEndpointInfo: mssql.SchemaCompareEndpointInfo;
|
||||
|
||||
constructor(private apiWrapper: ApiWrapper, private schemaCompareService?: mssql.ISchemaCompareService, private extensionContext?: vscode.ExtensionContext) {
|
||||
constructor(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;
|
||||
@@ -87,7 +86,7 @@ export class SchemaCompareMainWindow {
|
||||
let profile = context ? <azdata.IConnectionProfile>context.connectionProfile : undefined;
|
||||
let sourceDacpac = context as string;
|
||||
if (profile) {
|
||||
let ownerUri = await this.apiWrapper.getUriForConnection((profile.id));
|
||||
let ownerUri = await azdata.connection.getUriForConnection((profile.id));
|
||||
this.sourceEndpointInfo = {
|
||||
endpointType: mssql.SchemaCompareEndpointType.Database,
|
||||
serverDisplayName: `${profile.serverName} ${profile.userName}`,
|
||||
@@ -157,26 +156,16 @@ export class SchemaCompareMainWindow {
|
||||
|
||||
this.sourceName = getEndpointName(this.sourceEndpointInfo);
|
||||
this.targetName = ' ';
|
||||
this.sourceNameComponent = view.modelBuilder.table().withProperties<azdata.TableComponentProperties>({
|
||||
data: [],
|
||||
columns: [
|
||||
{
|
||||
value: this.sourceName,
|
||||
headerCssClass: 'no-borders',
|
||||
toolTip: this.sourceName
|
||||
},
|
||||
]
|
||||
this.sourceNameComponent = view.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({
|
||||
value: this.sourceName,
|
||||
title: this.sourceName,
|
||||
enabled: false
|
||||
}).component();
|
||||
|
||||
this.targetNameComponent = view.modelBuilder.table().withProperties<azdata.TableComponentProperties>({
|
||||
data: [],
|
||||
columns: [
|
||||
{
|
||||
value: this.targetName,
|
||||
headerCssClass: 'no-borders',
|
||||
toolTip: this.targetName
|
||||
},
|
||||
]
|
||||
this.targetNameComponent = view.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({
|
||||
value: this.targetName,
|
||||
title: this.targetName,
|
||||
enabled: false
|
||||
}).component();
|
||||
|
||||
this.resetButtons(ResetButtonState.noSourceTarget);
|
||||
@@ -218,10 +207,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' } });
|
||||
this.sourceTargetFlexLayout.addItem(this.sourceNameComponent, { CSSStyles: { 'width': '40%', 'height': '25px', 'margin-top': '10px', 'margin-left': '15px', 'margin-right': '10px' } });
|
||||
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' } });
|
||||
this.sourceTargetFlexLayout.addItem(this.targetNameComponent, { CSSStyles: { 'width': '40%', 'height': '25px', 'margin-top': '10px', 'margin-left': '15px', 'margin-right': '10px' } });
|
||||
this.sourceTargetFlexLayout.addItem(this.selectTargetButton, { CSSStyles: { 'margin-top': '10px' } });
|
||||
|
||||
this.loader = view.modelBuilder.loadingComponent().component();
|
||||
@@ -259,21 +248,14 @@ export class SchemaCompareMainWindow {
|
||||
this.sourceName = getEndpointName(this.sourceEndpointInfo);
|
||||
this.targetName = getEndpointName(this.targetEndpointInfo);
|
||||
|
||||
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
|
||||
},
|
||||
]);
|
||||
|
||||
this.sourceNameComponent.updateProperties({
|
||||
value: this.sourceName,
|
||||
title: this.sourceName
|
||||
});
|
||||
this.targetNameComponent.updateProperties({
|
||||
value: this.targetName,
|
||||
title: this.targetName
|
||||
});
|
||||
if (!this.sourceName || !this.targetName || this.sourceName === ' ' || this.targetName === ' ') {
|
||||
this.resetButtons(ResetButtonState.noSourceTarget);
|
||||
} else {
|
||||
@@ -295,11 +277,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();
|
||||
this.apiWrapper.showErrorMessage(loc.compareErrorMessage(this.comparisonResult.errorMessage));
|
||||
vscode.window.showErrorMessage(loc.compareErrorMessage(this.comparisonResult?.errorMessage));
|
||||
return;
|
||||
}
|
||||
TelemetryReporter.createActionEvent(TelemetryViews.SchemaCompareMainWindow, 'SchemaComparisonFinished')
|
||||
@@ -407,69 +389,76 @@ export class SchemaCompareMainWindow {
|
||||
this.tablelistenersToDispose.push(this.differencesTable.onCellAction(async (rowState) => {
|
||||
let checkboxState = <azdata.ICheckboxCellActionEventArgs>rowState;
|
||||
if (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;
|
||||
}
|
||||
await this.applyIncludeExclude(checkboxState);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
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) {
|
||||
this.differencesTable.data[rowState.row][2] = rowState.checked;
|
||||
if (this.differencesTable.data[rowState.row]?.length > 2) {
|
||||
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) {
|
||||
@@ -647,7 +636,7 @@ export class SchemaCompareMainWindow {
|
||||
});
|
||||
}
|
||||
|
||||
private async cancelCompare() {
|
||||
public async cancelCompare() {
|
||||
|
||||
TelemetryReporter.createActionEvent(TelemetryViews.SchemaCompareMainWindow, 'SchemaCompareCancelStarted')
|
||||
.withAdditionalProperties({
|
||||
@@ -691,28 +680,32 @@ export class SchemaCompareMainWindow {
|
||||
}).component();
|
||||
|
||||
this.generateScriptButton.onDidClick(async (click) => {
|
||||
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))
|
||||
.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();
|
||||
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))
|
||||
.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) {
|
||||
this.optionsButton = view.modelBuilder.button().withProperties({
|
||||
label: loc.options,
|
||||
@@ -741,43 +734,47 @@ 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;
|
||||
this.applyButton.onDidClick(async (click) => {
|
||||
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();
|
||||
|
||||
vscode.window.showWarningMessage(loc.applyConfirmation, { modal: true }, yesString).then(async (result) => {
|
||||
if (result === yesString) {
|
||||
TelemetryReporter.createActionEvent(TelemetryViews.SchemaCompareMainWindow, 'SchemaCompareApplyStarted')
|
||||
// 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({
|
||||
'startTime': Date.now().toString(),
|
||||
'operationId': this.comparisonResult.operationId
|
||||
}).send();
|
||||
vscode.window.showErrorMessage(loc.applyErrorMessage(result.errorMessage));
|
||||
|
||||
// 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();
|
||||
// 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();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -856,23 +853,13 @@ export class SchemaCompareMainWindow {
|
||||
[this.sourceName, this.targetName] = [this.targetName, this.sourceName];
|
||||
|
||||
this.sourceNameComponent.updateProperties({
|
||||
columns: [
|
||||
{
|
||||
value: this.sourceName,
|
||||
headerCssClass: 'no-borders',
|
||||
toolTip: this.sourceName
|
||||
},
|
||||
]
|
||||
value: this.sourceName,
|
||||
title: this.sourceName
|
||||
});
|
||||
|
||||
this.targetNameComponent.updateProperties({
|
||||
columns: [
|
||||
{
|
||||
value: this.targetName,
|
||||
headerCssClass: 'no-borders',
|
||||
toolTip: this.targetName
|
||||
},
|
||||
]
|
||||
value: this.targetName,
|
||||
title: this.targetName
|
||||
});
|
||||
|
||||
// remember that source target have been toggled
|
||||
@@ -922,60 +909,64 @@ export class SchemaCompareMainWindow {
|
||||
}).component();
|
||||
|
||||
this.openScmpButton.onDidClick(async (click) => {
|
||||
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();
|
||||
await this.openScmp();
|
||||
});
|
||||
}
|
||||
|
||||
private async constructEndpointInfo(endpoint: mssql.SchemaCompareEndpointInfo, caller: string, apiWrapper: ApiWrapper): Promise<mssql.SchemaCompareEndpointInfo> {
|
||||
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> {
|
||||
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, apiWrapper);
|
||||
ownerUri = await verifyConnectionAndGetOwnerUri(endpoint, caller);
|
||||
}
|
||||
if (ownerUri) {
|
||||
endpointInfo = endpoint;
|
||||
@@ -1007,44 +998,48 @@ export class SchemaCompareMainWindow {
|
||||
}).component();
|
||||
|
||||
this.saveScmpButton.onDidClick(async (click) => {
|
||||
const rootPath = getRootPath();
|
||||
const filePath = await vscode.window.showSaveDialog(
|
||||
{
|
||||
defaultUri: vscode.Uri.file(rootPath),
|
||||
saveLabel: loc.save,
|
||||
filters: {
|
||||
'scmp Files': ['scmp'],
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
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);
|
||||
|
||||
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
|
||||
});
|
||||
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'],
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
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);
|
||||
|
||||
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
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts excluded diff entries into object ids which are needed to save them in an scmp
|
||||
*/
|
||||
@@ -1071,7 +1066,7 @@ export class SchemaCompareMainWindow {
|
||||
}
|
||||
|
||||
private async getService(): Promise<mssql.ISchemaCompareService> {
|
||||
if (isNullOrUndefined(this.schemaCompareService)) {
|
||||
if (this.schemaCompareService === null || this.schemaCompareService === undefined) {
|
||||
this.schemaCompareService = (vscode.extensions.getExtension(mssql.extension.name).exports as mssql.IExtension).schemaCompare;
|
||||
}
|
||||
return this.schemaCompareService;
|
||||
|
||||
@@ -4,13 +4,12 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
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 { SchemaCompareDialog } from './../dialogs/schemaCompareDialog';
|
||||
import * as sinon from 'sinon';
|
||||
import { SchemaCompareMainWindow } from '../schemaCompareMainWindow';
|
||||
import { SchemaCompareTestService, testStateScmp } from './testSchemaCompareService';
|
||||
import { createContext, TestContext } from './testContext';
|
||||
@@ -28,15 +27,20 @@ before(function (): void {
|
||||
testContext = createContext();
|
||||
});
|
||||
|
||||
describe('SchemaCompareMainWindow.start', function (): void {
|
||||
describe('SchemaCompareMainWindow.start @DacFx@', 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(testContext.apiWrapper.object, sc, mockExtensionContext.object);
|
||||
let result = new SchemaCompareMainWindowTest(sc, mockExtensionContext.object);
|
||||
await result.start(undefined);
|
||||
|
||||
should(result.getComparisonResult() === undefined);
|
||||
@@ -52,7 +56,7 @@ describe('SchemaCompareMainWindow.start', function (): void {
|
||||
it('Should start with the source as undefined', async function (): Promise<void> {
|
||||
let sc = new SchemaCompareTestService();
|
||||
|
||||
let result = new SchemaCompareMainWindowTest(testContext.apiWrapper.object, sc, mockExtensionContext.object);
|
||||
let result = new SchemaCompareMainWindowTest(sc, mockExtensionContext.object);
|
||||
await result.start(undefined);
|
||||
|
||||
should.equal(result.sourceEndpointInfo, undefined);
|
||||
@@ -62,8 +66,8 @@ describe('SchemaCompareMainWindow.start', function (): void {
|
||||
it('Should start with the source as database', async function (): Promise<void> {
|
||||
let sc = new SchemaCompareTestService();
|
||||
|
||||
let result = new SchemaCompareMainWindowTest(testContext.apiWrapper.object, sc, mockExtensionContext.object);
|
||||
await result.start({connectionProfile: mockIConnectionProfile});
|
||||
let result = new SchemaCompareMainWindowTest(sc, mockExtensionContext.object);
|
||||
await result.start({ connectionProfile: mockIConnectionProfile });
|
||||
|
||||
should.notEqual(result.sourceEndpointInfo, undefined);
|
||||
should.equal(result.sourceEndpointInfo.endpointType, mssql.SchemaCompareEndpointType.Database);
|
||||
@@ -75,7 +79,7 @@ describe('SchemaCompareMainWindow.start', function (): void {
|
||||
it('Should start with the source as dacpac.', async function (): Promise<void> {
|
||||
let sc = new SchemaCompareTestService();
|
||||
|
||||
let result = new SchemaCompareMainWindowTest(testContext.apiWrapper.object, sc, mockExtensionContext.object);
|
||||
let result = new SchemaCompareMainWindowTest(sc, mockExtensionContext.object);
|
||||
const dacpacPath = mockFilePath;
|
||||
await result.start(dacpacPath);
|
||||
|
||||
@@ -85,7 +89,287 @@ describe('SchemaCompareMainWindow.start', 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>();
|
||||
@@ -93,16 +377,22 @@ describe('SchemaCompareMainWindow.execute', function (): void {
|
||||
testContext = createContext();
|
||||
});
|
||||
|
||||
beforeEach(function (): void {
|
||||
testContext.apiWrapper.reset();
|
||||
this.afterEach(() => {
|
||||
sinon.restore();
|
||||
});
|
||||
|
||||
this.beforeEach(() => {
|
||||
sinon.restore();
|
||||
});
|
||||
|
||||
it('Should fail for failing Schema Compare service', async function (): Promise<void> {
|
||||
let sc = new SchemaCompareTestService(testStateScmp.FAILURE);
|
||||
|
||||
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);
|
||||
showErrorMessageStub = sinon.stub(vscode.window, 'showErrorMessage').callsFake((message) => {
|
||||
throw new Error(message);
|
||||
});
|
||||
|
||||
let result = new SchemaCompareMainWindowTest(sc, mockExtensionContext.object);
|
||||
await result.start(undefined);
|
||||
|
||||
should(result.getComparisonResult() === undefined);
|
||||
@@ -116,8 +406,7 @@ describe('SchemaCompareMainWindow.execute', function (): void {
|
||||
it('Should exit for failing Schema Compare service', async function (): Promise<void> {
|
||||
let sc = new SchemaCompareTestService(testStateScmp.FAILURE);
|
||||
|
||||
testContext.apiWrapper.setup(x => x.showErrorMessage(TypeMoq.It.isAny())).returns(() => Promise.resolve(''));
|
||||
let result = new SchemaCompareMainWindowTest(testContext.apiWrapper.object, sc, mockExtensionContext.object);
|
||||
let result = new SchemaCompareMainWindowTest(sc, mockExtensionContext.object);
|
||||
|
||||
await result.start(undefined);
|
||||
|
||||
@@ -127,13 +416,12 @@ 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(testContext.apiWrapper.object, sc, mockExtensionContext.object);
|
||||
let result = new SchemaCompareMainWindowTest(sc, mockExtensionContext.object);
|
||||
|
||||
await result.start(undefined);
|
||||
|
||||
@@ -145,7 +433,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,
|
||||
@@ -156,13 +444,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(testContext.apiWrapper.object, sc, mockExtensionContext.object);
|
||||
let result = new SchemaCompareMainWindowTest(sc, mockExtensionContext.object);
|
||||
|
||||
await result.start(undefined);
|
||||
|
||||
@@ -174,7 +462,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,
|
||||
@@ -185,7 +473,7 @@ describe('SchemaCompareMainWindow.execute', function (): void {
|
||||
selectTargetButtonState: true,
|
||||
generateScriptButtonState: true,
|
||||
applyButtonState: true
|
||||
} );
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
@@ -201,18 +489,18 @@ describe('SchemaCompareMainWindow.updateSourceAndTarget', function (): void {
|
||||
let sc = new SchemaCompareTestService();
|
||||
let endpointInfo: mssql.SchemaCompareEndpointInfo;
|
||||
|
||||
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);
|
||||
|
||||
result.sourceEndpointInfo = {...endpointInfo};
|
||||
result.targetEndpointInfo = {...endpointInfo};
|
||||
result.sourceEndpointInfo = { ...endpointInfo };
|
||||
result.targetEndpointInfo = { ...endpointInfo };
|
||||
|
||||
result.updateSourceAndTarget();
|
||||
|
||||
result.verifyButtonsState( {
|
||||
result.verifyButtonsState({
|
||||
compareButtonState: false,
|
||||
optionsButtonState: false,
|
||||
switchButtonState: false,
|
||||
@@ -223,25 +511,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(testContext.apiWrapper.object, sc, mockExtensionContext.object);
|
||||
let result = new SchemaCompareMainWindowTest(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,
|
||||
@@ -252,14 +540,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(testContext.apiWrapper.object, sc, mockExtensionContext.object);
|
||||
let result = new SchemaCompareMainWindowTest(sc, mockExtensionContext.object);
|
||||
|
||||
await result.start(undefined);
|
||||
|
||||
@@ -270,7 +558,7 @@ describe('SchemaCompareMainWindow.updateSourceAndTarget', function (): void {
|
||||
|
||||
result.updateSourceAndTarget();
|
||||
|
||||
result.verifyButtonsState( {
|
||||
result.verifyButtonsState({
|
||||
compareButtonState: true,
|
||||
optionsButtonState: true,
|
||||
switchButtonState: true,
|
||||
@@ -281,7 +569,7 @@ describe('SchemaCompareMainWindow.updateSourceAndTarget', function (): void {
|
||||
selectTargetButtonState: true,
|
||||
generateScriptButtonState: false,
|
||||
applyButtonState: false
|
||||
} );
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -25,14 +25,14 @@ before(function (): void {
|
||||
testContext = createContext();
|
||||
});
|
||||
|
||||
describe('SchemaCompareDialog.openDialog', function (): void {
|
||||
describe('SchemaCompareDialog.openDialog @DacFx@', 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(testContext.apiWrapper.object, undefined, mockExtensionContext.object);
|
||||
let schemaCompareResult = new SchemaCompareMainWindow(undefined, mockExtensionContext.object);
|
||||
let dialog = new SchemaCompareDialog(schemaCompareResult);
|
||||
await dialog.openDialog();
|
||||
|
||||
@@ -42,7 +42,7 @@ describe('SchemaCompareDialog.openDialog', function (): void {
|
||||
});
|
||||
|
||||
it('Simulate ok button- with both endpoints set to dacpac', async function (): Promise<void> {
|
||||
let schemaCompareResult = new SchemaCompareMainWindowTest(testContext.apiWrapper.object, undefined, mockExtensionContext.object);
|
||||
let schemaCompareResult = new SchemaCompareMainWindowTest(undefined, mockExtensionContext.object);
|
||||
await schemaCompareResult.start(undefined);
|
||||
schemaCompareResult.sourceEndpointInfo = setDacpacEndpointInfo(mocksource);
|
||||
schemaCompareResult.targetEndpointInfo = setDacpacEndpointInfo(mocktarget);
|
||||
@@ -53,7 +53,7 @@ describe('SchemaCompareDialog.openDialog', 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', function (): void {
|
||||
selectTargetButtonState: true,
|
||||
generateScriptButtonState: false,
|
||||
applyButtonState: false
|
||||
} );
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,11 +5,8 @@
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -17,7 +14,6 @@ export function createContext(): TestContext {
|
||||
let extensionPath = path.join(__dirname, '..', '..');
|
||||
|
||||
return {
|
||||
apiWrapper: TypeMoq.Mock.ofType(ApiWrapper),
|
||||
context: {
|
||||
subscriptions: [],
|
||||
workspaceState: {
|
||||
|
||||
@@ -7,7 +7,6 @@ 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;
|
||||
@@ -24,10 +23,9 @@ export interface ButtonState {
|
||||
export class SchemaCompareMainWindowTest extends SchemaCompareMainWindow {
|
||||
|
||||
constructor(
|
||||
apiWrapper: ApiWrapper,
|
||||
schemaCompareService: mssql.ISchemaCompareService,
|
||||
extensionContext: vscode.ExtensionContext) {
|
||||
super(apiWrapper, schemaCompareService, extensionContext);
|
||||
super(schemaCompareService, extensionContext);
|
||||
}
|
||||
|
||||
// only for test
|
||||
|
||||
@@ -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): Thenable<mssql.SchemaCompareResult> {
|
||||
schemaCompare(operationId: string, sourceEndpointInfo: mssql.SchemaCompareEndpointInfo, targetEndpointInfo: mssql.SchemaCompareEndpointInfo, taskExecutionMode: azdata.TaskExecutionMode, deploymentOptions: mssql.DeploymentOptions): Thenable<mssql.SchemaCompareResult> {
|
||||
let result: mssql.SchemaCompareResult;
|
||||
if (this.testState === testStateScmp.FAILURE) {
|
||||
result = {
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
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';
|
||||
@@ -15,10 +16,15 @@ 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', function (): void {
|
||||
describe('utils: Tests to verify getEndpointName @DacFx@', function (): void {
|
||||
afterEach(() => {
|
||||
sinon.restore();
|
||||
});
|
||||
|
||||
it('Should generate correct endpoint information', () => {
|
||||
let endpointInfo: mssql.SchemaCompareEndpointInfo;
|
||||
|
||||
@@ -52,7 +58,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', testContext.apiWrapper.object);
|
||||
ownerUri = await verifyConnectionAndGetOwnerUri(mockDacpacEndpoint, 'test');
|
||||
|
||||
should(ownerUri).equal(undefined);
|
||||
});
|
||||
@@ -62,7 +68,7 @@ describe('utils: Basic tests to verify verifyConnectionAndGetOwnerUri', function
|
||||
let testDatabaseEndpoint: mssql.SchemaCompareEndpointInfo = { ...mockDatabaseEndpoint };
|
||||
testDatabaseEndpoint.connectionDetails = undefined;
|
||||
|
||||
ownerUri = await verifyConnectionAndGetOwnerUri(testDatabaseEndpoint, 'test', testContext.apiWrapper.object);
|
||||
ownerUri = await verifyConnectionAndGetOwnerUri(testDatabaseEndpoint, 'test');
|
||||
|
||||
should(ownerUri).equal(undefined);
|
||||
});
|
||||
@@ -73,6 +79,10 @@ 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 };
|
||||
@@ -80,12 +90,14 @@ describe('utils: In-depth tests to verify verifyConnectionAndGetOwnerUri', funct
|
||||
testDatabaseEndpoint.connectionDetails = { ...mockConnectionInfo };
|
||||
const getConnectionString = loc.getConnectionString('test');
|
||||
|
||||
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); });
|
||||
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);
|
||||
});
|
||||
|
||||
await shouldThrowSpecificError(async () => await verifyConnectionAndGetOwnerUri(testDatabaseEndpoint, 'test', testContext.apiWrapper.object), getConnectionString);
|
||||
await shouldThrowSpecificError(async () => await verifyConnectionAndGetOwnerUri(testDatabaseEndpoint, 'test'), getConnectionString);
|
||||
});
|
||||
|
||||
it('Should throw an error for login failure', async function (): Promise<void> {
|
||||
@@ -94,12 +106,15 @@ describe('utils: In-depth tests to verify verifyConnectionAndGetOwnerUri', funct
|
||||
let testDatabaseEndpoint: mssql.SchemaCompareEndpointInfo = { ...mockDatabaseEndpoint };
|
||||
testDatabaseEndpoint.connectionDetails = { ...mockConnectionInfo };
|
||||
|
||||
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); });
|
||||
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);
|
||||
});
|
||||
|
||||
await shouldThrowSpecificError(async () => await verifyConnectionAndGetOwnerUri(testDatabaseEndpoint, 'test', testContext.apiWrapper.object), connection.errorMessage);
|
||||
await shouldThrowSpecificError(async () => await verifyConnectionAndGetOwnerUri(testDatabaseEndpoint, 'test'), connection.errorMessage);
|
||||
});
|
||||
|
||||
it('Should throw an error for login failure with openConnectionDialog but no ownerUri', async function (): Promise<void> {
|
||||
@@ -108,13 +123,18 @@ describe('utils: In-depth tests to verify verifyConnectionAndGetOwnerUri', funct
|
||||
let testDatabaseEndpoint: mssql.SchemaCompareEndpointInfo = { ...mockDatabaseEndpoint };
|
||||
testDatabaseEndpoint.connectionDetails = { ...mockConnectionInfo };
|
||||
|
||||
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); });
|
||||
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);
|
||||
});
|
||||
|
||||
await shouldThrowSpecificError(async () => await verifyConnectionAndGetOwnerUri(testDatabaseEndpoint, 'test', testContext.apiWrapper.object), connection.errorMessage);
|
||||
await shouldThrowSpecificError(async () => await verifyConnectionAndGetOwnerUri(testDatabaseEndpoint, 'test'), connection.errorMessage);
|
||||
});
|
||||
|
||||
it('Should not throw an error and set ownerUri appropriately', async function (): Promise<void> {
|
||||
@@ -124,10 +144,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 };
|
||||
|
||||
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); });
|
||||
sinon.stub(azdata.connection, 'connect').returns(<any>Promise.resolve(connection));
|
||||
sinon.stub(azdata.connection, 'getUriForConnection').returns(<any>Promise.resolve(expectedOwnerUri));
|
||||
|
||||
ownerUri = await verifyConnectionAndGetOwnerUri(testDatabaseEndpoint, 'test', testContext.apiWrapper.object);
|
||||
ownerUri = await verifyConnectionAndGetOwnerUri(testDatabaseEndpoint, 'test');
|
||||
|
||||
should(ownerUri).equal(expectedOwnerUri);
|
||||
});
|
||||
|
||||
@@ -8,7 +8,6 @@ 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 {
|
||||
@@ -32,7 +31,7 @@ export function getPackageInfo(packageJson: any): IPackageInfo {
|
||||
* @param msg The error message to map
|
||||
*/
|
||||
export function getTelemetryErrorType(msg: string): string {
|
||||
if (msg.indexOf('Object reference not set to an instance of an object') !== -1) {
|
||||
if (msg && msg.indexOf('Object reference not set to an instance of an object') !== -1) {
|
||||
return 'ObjectReferenceNotSet';
|
||||
}
|
||||
else {
|
||||
@@ -85,18 +84,18 @@ function connectionInfoToConnectionProfile(details: azdata.ConnectionInfo): azda
|
||||
};
|
||||
}
|
||||
|
||||
export async function verifyConnectionAndGetOwnerUri(endpoint: mssql.SchemaCompareEndpointInfo, caller: string, apiWrapper: ApiWrapper): Promise<string | undefined> {
|
||||
export async function verifyConnectionAndGetOwnerUri(endpoint: mssql.SchemaCompareEndpointInfo, caller: string): Promise<string | undefined> {
|
||||
let ownerUri = undefined;
|
||||
|
||||
if (endpoint.endpointType === mssql.SchemaCompareEndpointType.Database && endpoint.connectionDetails) {
|
||||
let connectionProfile = await connectionInfoToConnectionProfile(endpoint.connectionDetails);
|
||||
let connection = await apiWrapper.connect(connectionProfile, false, false);
|
||||
let connection = await azdata.connection.connect(connectionProfile, false, false);
|
||||
|
||||
if (connection) {
|
||||
ownerUri = await apiWrapper.getUriForConnection(connection.connectionId);
|
||||
ownerUri = await azdata.connection.getUriForConnection(connection.connectionId);
|
||||
|
||||
if (!ownerUri) {
|
||||
let connectionList = await apiWrapper.getConnections(true);
|
||||
let connectionList = await azdata.connection.getConnections(true);
|
||||
|
||||
let userConnection;
|
||||
userConnection = connectionList.find(connection =>
|
||||
@@ -109,18 +108,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 apiWrapper.showWarningMessage(getConnectionString, { modal: true }, loc.YesButtonText);
|
||||
let result = await vscode.window.showWarningMessage(getConnectionString, { modal: true }, loc.YesButtonText);
|
||||
if (result === loc.YesButtonText) {
|
||||
userConnection = await apiWrapper.openConnectionDialog(undefined, connectionProfile);
|
||||
userConnection = await azdata.connection.openConnectionDialog(undefined, connectionProfile);
|
||||
}
|
||||
}
|
||||
|
||||
if (userConnection !== undefined) {
|
||||
ownerUri = await apiWrapper.getUriForConnection(userConnection.connectionId);
|
||||
ownerUri = await azdata.connection.getUriForConnection(userConnection.connectionId);
|
||||
}
|
||||
}
|
||||
if (!ownerUri && connection.errorMessage) {
|
||||
apiWrapper.showErrorMessage(connection.errorMessage);
|
||||
vscode.window.showErrorMessage(connection.errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,6 +182,42 @@
|
||||
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"
|
||||
@@ -192,6 +228,18 @@
|
||||
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"
|
||||
@@ -395,6 +443,11 @@ 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"
|
||||
@@ -501,6 +554,11 @@ 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"
|
||||
@@ -576,6 +634,16 @@ 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"
|
||||
@@ -675,6 +743,17 @@ 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"
|
||||
@@ -692,6 +771,13 @@ 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"
|
||||
@@ -785,6 +871,19 @@ 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"
|
||||
@@ -838,6 +937,11 @@ 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"
|
||||
|
||||
47
extensions/sql-database-projects/images/sqlEdgeProject.svg
Normal file
47
extensions/sql-database-projects/images/sqlEdgeProject.svg
Normal file
@@ -0,0 +1,47 @@
|
||||
<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>
|
||||
|
After Width: | Height: | Size: 7.3 KiB |
@@ -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.5.1",
|
||||
"version": "0.6.0",
|
||||
"publisher": "Microsoft",
|
||||
"preview": true,
|
||||
"engines": {
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
CREATE EXTERNAL DATA SOURCE [@@OBJECT_NAME@@] WITH
|
||||
(
|
||||
LOCATION = '@@LOCATION@@'
|
||||
)
|
||||
@@ -0,0 +1,5 @@
|
||||
CREATE EXTERNAL STREAM [@@OBJECT_NAME@@] WITH
|
||||
(
|
||||
DATA_SOURCE = [@@DATA_SOURCE_NAME@@],
|
||||
LOCATION = N'@@LOCATION@@'@@OPTIONS@@
|
||||
)
|
||||
@@ -5,6 +5,5 @@
|
||||
|
||||
EXEC sys.sp_create_streaming_job @NAME = '@@OBJECT_NAME@@', @STATEMENT = 'INSERT INTO SqlOutputStream SELECT
|
||||
timeCreated,
|
||||
streamColumn1 as column1,
|
||||
streamColumn2 as column2
|
||||
streamColumn1 as Id
|
||||
FROM EdgeHubInputStream'
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
CREATE EXTERNAL FILE FORMAT [@@OBJECT_NAME@@] WITH
|
||||
(
|
||||
FORMAT_TYPE = JSON,
|
||||
DATA_COMPRESSION = 'org.apache.hadoop.io.compress.GzipCodec'
|
||||
)
|
||||
@@ -23,9 +23,13 @@ export const MicrosoftDatatoolsSchemaSqlSql = 'Microsoft.Data.Tools.Schema.Sql.S
|
||||
export const databaseSchemaProvider = 'DatabaseSchemaProvider';
|
||||
|
||||
// Project Provider
|
||||
export const sqlDatabaseProjectTypeId = 'sqldbproj';
|
||||
export const projectTypeDisplayName = localize('projectTypeDisplayName', "SQL Database");
|
||||
export const projectTypeDescription = localize('projectTypeDescription', "Design, edit, and publish schemas for SQL databases");
|
||||
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");
|
||||
|
||||
// commands
|
||||
export const revealFileInOsCommand = 'revealFileInOS';
|
||||
@@ -118,7 +122,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");
|
||||
@@ -126,9 +130,13 @@ 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', "Enter project location");
|
||||
export const projectLocationPlaceholderText = localize('projectLocationPlaceholderText', "Select location to create project");
|
||||
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
|
||||
@@ -196,6 +204,9 @@ 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");
|
||||
|
||||
@@ -14,6 +14,7 @@ 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;
|
||||
@@ -33,6 +34,7 @@ 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');
|
||||
|
||||
@@ -11,12 +11,15 @@ export namespace cssStyles {
|
||||
export const fontWeightBold = { 'font-weight': 'bold' };
|
||||
export const titleFontSize = 13;
|
||||
|
||||
export const labelWidth = '205px';
|
||||
export const textboxWidth = '190px';
|
||||
export const publishDialogLabelWidth = '205px';
|
||||
export const publishDialogTextboxWidth = '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';
|
||||
|
||||
@@ -56,40 +56,35 @@ export class ProjectsController {
|
||||
* @param folderUri
|
||||
* @param projectGuid
|
||||
*/
|
||||
public async createNewProject(newProjName: string, folderUri: vscode.Uri, makeOwnFolder: boolean, projectGuid?: string): Promise<string> {
|
||||
if (projectGuid && !UUID.isUUID(projectGuid)) {
|
||||
throw new Error(`Specified GUID is invalid: '${projectGuid}'`);
|
||||
public async createNewProject(creationParams: NewProjectParams): Promise<string> {
|
||||
if (creationParams.projectGuid && !UUID.isUUID(creationParams.projectGuid)) {
|
||||
throw new Error(`Specified GUID is invalid: '${creationParams.projectGuid}'`);
|
||||
}
|
||||
|
||||
const macroDict: Record<string, string> = {
|
||||
'PROJECT_NAME': newProjName,
|
||||
'PROJECT_GUID': projectGuid ?? UUID.generateUuid().toUpperCase()
|
||||
'PROJECT_NAME': creationParams.newProjName,
|
||||
'PROJECT_GUID': creationParams.projectGuid ?? UUID.generateUuid().toUpperCase()
|
||||
};
|
||||
|
||||
let newProjFileContents = this.macroExpansion(templates.newSqlProjectTemplate, macroDict);
|
||||
let newProjFileContents = templates.macroExpansion(templates.newSqlProjectTemplate, macroDict);
|
||||
|
||||
let newProjFileName = newProjName;
|
||||
let newProjFileName = creationParams.newProjName;
|
||||
|
||||
if (!newProjFileName.toLowerCase().endsWith(constants.sqlprojExtension)) {
|
||||
newProjFileName += constants.sqlprojExtension;
|
||||
}
|
||||
|
||||
const newProjFilePath = makeOwnFolder ? path.join(folderUri.fsPath, path.parse(newProjFileName).name, newProjFileName) : path.join(folderUri.fsPath, newProjFileName);
|
||||
const newProjFilePath = path.join(creationParams.folderUri.fsPath, path.parse(newProjFileName).name, newProjFileName);
|
||||
|
||||
let fileExists = false;
|
||||
try {
|
||||
await fs.access(newProjFilePath);
|
||||
fileExists = true;
|
||||
}
|
||||
catch { } // file doesn't already exist
|
||||
|
||||
if (fileExists) {
|
||||
if (await utils.exists(newProjFilePath)) {
|
||||
throw new Error(constants.projectAlreadyExists(newProjFileName, path.parse(newProjFilePath).dir));
|
||||
}
|
||||
|
||||
await fs.mkdir(path.dirname(newProjFilePath), { recursive: true });
|
||||
await fs.writeFile(newProjFilePath, newProjFileContents);
|
||||
|
||||
await this.addTemplateFiles(newProjFilePath, creationParams.projectTypeId);
|
||||
|
||||
return newProjFilePath;
|
||||
}
|
||||
|
||||
@@ -256,7 +251,7 @@ export class ProjectsController {
|
||||
}
|
||||
}
|
||||
|
||||
const itemType = templates.projectScriptTypeMap()[itemTypeName.toLocaleLowerCase()];
|
||||
const itemType = templates.get(itemTypeName);
|
||||
const absolutePathToParent = path.join(project.projectFolderPath, relativePath);
|
||||
let itemObjectName = await this.promptForNewObjectName(itemType, project, absolutePathToParent, constants.sqlFileExtension);
|
||||
|
||||
@@ -266,18 +261,10 @@ export class ProjectsController {
|
||||
return; // user cancelled
|
||||
}
|
||||
|
||||
const newFileText = this.macroExpansion(itemType.templateScript, { 'OBJECT_NAME': itemObjectName });
|
||||
const newFileText = templates.macroExpansion(itemType.templateScript, { 'OBJECT_NAME': itemObjectName });
|
||||
const relativeFilePath = path.join(relativePath, itemObjectName + constants.sqlFileExtension);
|
||||
|
||||
try {
|
||||
// check if file already exists
|
||||
const absoluteFilePath = path.join(project.projectFolderPath, relativeFilePath);
|
||||
const fileExists = await utils.exists(absoluteFilePath);
|
||||
|
||||
if (fileExists) {
|
||||
throw new Error(constants.fileAlreadyExists(path.parse(absoluteFilePath).name));
|
||||
}
|
||||
|
||||
const newEntry = await project.addScriptItem(relativeFilePath, newFileText, itemType.type);
|
||||
|
||||
await vscode.commands.executeCommand(constants.vscodeOpenCommand, newEntry.fsUri);
|
||||
@@ -460,6 +447,12 @@ export class ProjectsController {
|
||||
return addDatabaseReferenceDialog;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a database reference to a project, after selections have been made in the dialog
|
||||
* @param project project to which to add the database reference
|
||||
* @param settings settings for the database reference
|
||||
* @param context a treeItem in a project's hierarchy, to be used to obtain a Project
|
||||
*/
|
||||
public async addDatabaseReferenceCallback(project: Project, settings: ISystemDatabaseReferenceSettings | IDacpacReferenceSettings | IProjectReferenceSettings, context: dataworkspace.WorkspaceTreeItem): Promise<void> {
|
||||
try {
|
||||
if ((<IProjectReferenceSettings>settings).projectName !== undefined) {
|
||||
@@ -494,6 +487,11 @@ export class ProjectsController {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the contents of an external streaming job's query against the last-built dacpac.
|
||||
* If no dacpac exists at the output path, one will be built first.
|
||||
* @param node a treeItem in a project's hierarchy, to be used to obtain a Project
|
||||
*/
|
||||
public async validateExternalStreamingJob(node: dataworkspace.WorkspaceTreeItem): Promise<mssql.ValidateStreamingJobResult> {
|
||||
const project: Project = this.getProjectFromContext(node);
|
||||
|
||||
@@ -547,6 +545,29 @@ export class ProjectsController {
|
||||
}
|
||||
}
|
||||
|
||||
private async addTemplateFiles(newProjFilePath: string, projectTypeId: string): Promise<void> {
|
||||
if (projectTypeId === constants.emptySqlDatabaseProjectTypeId || newProjFilePath === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (projectTypeId === constants.edgeSqlDatabaseProjectTypeId) {
|
||||
const project = await Project.openProject(newProjFilePath);
|
||||
|
||||
await this.createFileFromTemplate(project, templates.get(templates.table), 'DataTable.sql', { 'OBJECT_NAME': 'DataTable' });
|
||||
await this.createFileFromTemplate(project, templates.get(templates.dataSource), 'EdgeHubInputDataSource.sql', { 'OBJECT_NAME': 'EdgeHubInputDataSource', 'LOCATION': 'edgehub://' });
|
||||
await this.createFileFromTemplate(project, templates.get(templates.dataSource), 'SqlOutputDataSource.sql', { 'OBJECT_NAME': 'SqlOutputDataSource', 'LOCATION': 'sqlserver://tcp:.,1433' });
|
||||
await this.createFileFromTemplate(project, templates.get(templates.fileFormat), 'StreamFileFormat.sql', { 'OBJECT_NAME': 'StreamFileFormat' });
|
||||
await this.createFileFromTemplate(project, templates.get(templates.externalStream), 'EdgeHubInputStream.sql', { 'OBJECT_NAME': 'EdgeHubInputStream', 'DATA_SOURCE_NAME': 'EdgeHubInputDataSource', 'LOCATION': 'input', 'OPTIONS': ',\n\tFILE_FORMAT = StreamFileFormat' });
|
||||
await this.createFileFromTemplate(project, templates.get(templates.externalStream), 'SqlOutputStream.sql', { 'OBJECT_NAME': 'SqlOutputStream', 'DATA_SOURCE_NAME': 'SqlOutputDataSource', 'LOCATION': 'TSQLStreaming.dbo.DataTable', 'OPTIONS': '' });
|
||||
await this.createFileFromTemplate(project, templates.get(templates.externalStreamingJob), 'EdgeStreamingJob.sql', { 'OBJECT_NAME': 'EdgeStreamingJob' });
|
||||
}
|
||||
}
|
||||
|
||||
private async createFileFromTemplate(project: Project, itemType: templates.ProjectScriptType, relativePath: string, expansionMacros: Record<string, string>): Promise<void> {
|
||||
const newFileText = templates.macroExpansion(itemType.templateScript, expansionMacros);
|
||||
await project.addScriptItem(relativePath, newFileText, itemType.type);
|
||||
}
|
||||
|
||||
private getProjectFromContext(context: Project | BaseProjectTreeItem | dataworkspace.WorkspaceTreeItem): Project {
|
||||
if ('element' in context) {
|
||||
return context.element.root.project;
|
||||
@@ -570,21 +591,7 @@ export class ProjectsController {
|
||||
return (ext.exports as mssql.IExtension).dacFx;
|
||||
}
|
||||
|
||||
private macroExpansion(template: string, macroDict: Record<string, string>): string {
|
||||
const macroIndicator = '@@';
|
||||
let output = template;
|
||||
|
||||
for (const macro in macroDict) {
|
||||
// check if value contains the macroIndicator, which could break expansion for successive macros
|
||||
if (macroDict[macro].includes(macroIndicator)) {
|
||||
throw new Error(`Macro value ${macroDict[macro]} is invalid because it contains ${macroIndicator}`);
|
||||
}
|
||||
|
||||
output = output.replace(new RegExp(macroIndicator + macro + macroIndicator, 'g'), macroDict[macro]);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
private async promptForNewObjectName(itemType: templates.ProjectScriptType, _project: Project, folderPath: string, fileExtension?: string): Promise<string | undefined> {
|
||||
const suggestedName = itemType.friendlyName.replace(/\s+/g, '');
|
||||
@@ -630,21 +637,29 @@ export class ProjectsController {
|
||||
try {
|
||||
const workspaceApi = utils.getDataWorkspaceExtensionApi();
|
||||
|
||||
const newProjFolderUri = model.filePath;
|
||||
const validateWorkspace = await workspaceApi.validateWorkspace();
|
||||
if (validateWorkspace) {
|
||||
const newProjFolderUri = model.filePath;
|
||||
|
||||
const newProjFilePath = await this.createNewProject(model.projName, vscode.Uri.file(newProjFolderUri), true);
|
||||
model.filePath = path.dirname(newProjFilePath);
|
||||
this.setFilePath(model);
|
||||
const newProjFilePath = await this.createNewProject({
|
||||
newProjName: model.projName,
|
||||
folderUri: vscode.Uri.file(newProjFolderUri),
|
||||
projectTypeId: constants.emptySqlDatabaseProjectTypeId
|
||||
});
|
||||
|
||||
const project = await Project.openProject(newProjFilePath);
|
||||
await this.createProjectFromDatabaseApiCall(model); // Call ExtractAPI in DacFx Service
|
||||
let fileFolderList: string[] = model.extractTarget === mssql.ExtractTarget.file ? [model.filePath] : await this.generateList(model.filePath); // Create a list of all the files and directories to be added to project
|
||||
model.filePath = path.dirname(newProjFilePath);
|
||||
this.setFilePath(model);
|
||||
|
||||
await project.addToProject(fileFolderList); // Add generated file structure to the project
|
||||
const project = await Project.openProject(newProjFilePath);
|
||||
await this.createProjectFromDatabaseApiCall(model); // Call ExtractAPI in DacFx Service
|
||||
let fileFolderList: string[] = model.extractTarget === mssql.ExtractTarget.file ? [model.filePath] : await this.generateList(model.filePath); // Create a list of all the files and directories to be added to project
|
||||
|
||||
// add project to workspace
|
||||
workspaceApi.showProjectsView();
|
||||
await workspaceApi.addProjectsToWorkspace([vscode.Uri.file(newProjFilePath)]);
|
||||
await project.addToProject(fileFolderList); // Add generated file structure to the project
|
||||
|
||||
// add project to workspace
|
||||
workspaceApi.showProjectsView();
|
||||
await workspaceApi.addProjectsToWorkspace([vscode.Uri.file(newProjFilePath)]);
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
vscode.window.showErrorMessage(utils.getErrorMessage(err));
|
||||
@@ -718,3 +733,10 @@ export class ProjectsController {
|
||||
|
||||
//#endregion
|
||||
}
|
||||
|
||||
export interface NewProjectParams {
|
||||
newProjName: string;
|
||||
folderUri: vscode.Uri;
|
||||
projectTypeId: string;
|
||||
projectGuid?: string;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import * as vscode from 'vscode';
|
||||
import * as constants from '../common/constants';
|
||||
import * as newProjectTool from '../tools/newProjectTool';
|
||||
import * as mssql from '../../../mssql';
|
||||
import * as path from 'path';
|
||||
|
||||
import { IconPathHelper } from '../common/iconHelper';
|
||||
import { cssStyles } from '../common/uiConstants';
|
||||
@@ -24,6 +25,7 @@ export class CreateProjectFromDatabaseDialog {
|
||||
public projectNameTextBox: azdata.InputBoxComponent | undefined;
|
||||
public projectLocationTextBox: azdata.InputBoxComponent | undefined;
|
||||
public folderStructureDropDown: azdata.DropDownComponent | undefined;
|
||||
public workspaceInputBox: azdata.InputBoxComponent | undefined;
|
||||
private formBuilder: azdata.FormBuilder | undefined;
|
||||
private connectionId: string | undefined;
|
||||
private toDispose: vscode.Disposable[] = [];
|
||||
@@ -81,6 +83,10 @@ export class CreateProjectFromDatabaseDialog {
|
||||
const createProjectSettingsFormSection = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column' }).component();
|
||||
createProjectSettingsFormSection.addItems([folderStructureRow]);
|
||||
|
||||
const workspaceContainerRow = this.createWorkspaceContainerRow(view);
|
||||
const createworkspaceContainerFormSection = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column' }).component();
|
||||
createworkspaceContainerFormSection.addItems([workspaceContainerRow]);
|
||||
|
||||
this.formBuilder = <azdata.FormBuilder>view.modelBuilder.formContainer()
|
||||
.withFormItems([
|
||||
{
|
||||
@@ -106,13 +112,22 @@ export class CreateProjectFromDatabaseDialog {
|
||||
component: createProjectSettingsFormSection,
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: constants.workspace,
|
||||
components: [
|
||||
{
|
||||
component: createworkspaceContainerFormSection,
|
||||
}
|
||||
]
|
||||
}
|
||||
], {
|
||||
horizontal: false,
|
||||
titleFontSize: cssStyles.titleFontSize
|
||||
})
|
||||
.withLayout({
|
||||
width: '100%'
|
||||
width: '100%',
|
||||
padding: '10px 10px 0 20px'
|
||||
});
|
||||
|
||||
let formModel = this.formBuilder.component();
|
||||
@@ -129,12 +144,11 @@ export class CreateProjectFromDatabaseDialog {
|
||||
const serverLabel = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
|
||||
value: constants.server,
|
||||
requiredIndicator: true,
|
||||
width: cssStyles.labelWidth,
|
||||
CSSStyles: cssStyles.fontWeightBold
|
||||
width: cssStyles.createProjectFromDatabaseLabelWidth
|
||||
}).component();
|
||||
|
||||
const connectionRow = view.modelBuilder.flexContainer().withItems([serverLabel, sourceConnectionTextBox], { flex: '0 0 auto', CSSStyles: { 'margin-right': '10px', 'margin-bottom': '-10px', 'margin-top': '-20px' } }).withLayout({ flexFlow: 'row', alignItems: 'center' }).component();
|
||||
connectionRow.insertItem(selectConnectionButton, 2, { CSSStyles: { 'margin-right': '0px', 'margin-bottom': '-10px', 'margin-top': '-20px' } });
|
||||
const connectionRow = view.modelBuilder.flexContainer().withItems([serverLabel, sourceConnectionTextBox], { flex: '0 0 auto', CSSStyles: { 'margin-right': '10px', 'margin-bottom': '-5px', 'margin-top': '-10px' } }).withLayout({ flexFlow: 'row', alignItems: 'center' }).component();
|
||||
connectionRow.insertItem(selectConnectionButton, 2, { CSSStyles: { 'margin-right': '0px', 'margin-bottom': '-5px', 'margin-top': '-10px' } });
|
||||
|
||||
return connectionRow;
|
||||
}
|
||||
@@ -143,21 +157,21 @@ export class CreateProjectFromDatabaseDialog {
|
||||
this.sourceDatabaseDropDown = view.modelBuilder.dropDown().withProperties({
|
||||
ariaLabel: constants.databaseNameLabel,
|
||||
required: true,
|
||||
width: cssStyles.textboxWidth,
|
||||
width: cssStyles.createProjectFromDatabaseTextboxWidth,
|
||||
editable: true,
|
||||
fireOnTextChange: true
|
||||
}).component();
|
||||
|
||||
this.sourceDatabaseDropDown.onValueChanged(() => {
|
||||
this.setProjectName();
|
||||
this.updateWorkspaceInputbox(this.projectLocationTextBox!.value!, this.projectNameTextBox!.value!);
|
||||
this.tryEnableCreateButton();
|
||||
});
|
||||
|
||||
const databaseLabel = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
|
||||
value: constants.databaseNameLabel,
|
||||
requiredIndicator: true,
|
||||
width: cssStyles.labelWidth,
|
||||
CSSStyles: cssStyles.fontWeightBold
|
||||
width: cssStyles.createProjectFromDatabaseLabelWidth
|
||||
}).component();
|
||||
|
||||
const databaseRow = view.modelBuilder.flexContainer().withItems([databaseLabel, <azdata.DropDownComponent>this.sourceDatabaseDropDown], { flex: '0 0 auto', CSSStyles: { 'margin-right': '10px', 'margin-bottom': '-10px' } }).withLayout({ flexFlow: 'row', alignItems: 'center' }).component();
|
||||
@@ -173,7 +187,7 @@ export class CreateProjectFromDatabaseDialog {
|
||||
this.sourceConnectionTextBox = view.modelBuilder.inputBox().withProperties({
|
||||
value: '',
|
||||
placeHolder: constants.selectConnection,
|
||||
width: cssStyles.textboxWidth,
|
||||
width: cssStyles.createProjectFromDatabaseTextboxWidth,
|
||||
enabled: false
|
||||
}).component();
|
||||
|
||||
@@ -229,24 +243,26 @@ export class CreateProjectFromDatabaseDialog {
|
||||
private createProjectNameRow(view: azdata.ModelView): azdata.FlexContainer {
|
||||
this.projectNameTextBox = view.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({
|
||||
ariaLabel: constants.projectNamePlaceholderText,
|
||||
placeHolder: constants.projectNamePlaceholderText,
|
||||
required: true,
|
||||
width: cssStyles.textboxWidth,
|
||||
width: cssStyles.createProjectFromDatabaseTextboxWidth,
|
||||
validationErrorMessage: constants.projectNameRequired
|
||||
}).component();
|
||||
|
||||
this.projectNameTextBox.onTextChanged(() => {
|
||||
this.projectNameTextBox!.value = this.projectNameTextBox!.value?.trim();
|
||||
this.projectNameTextBox!.updateProperty('title', this.projectNameTextBox!.value);
|
||||
this.updateWorkspaceInputbox(this.projectLocationTextBox!.value!, this.projectNameTextBox!.value!);
|
||||
this.tryEnableCreateButton();
|
||||
});
|
||||
|
||||
const projectNameLabel = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
|
||||
value: constants.projectNameLabel,
|
||||
requiredIndicator: true,
|
||||
width: cssStyles.labelWidth,
|
||||
CSSStyles: cssStyles.fontWeightBold
|
||||
width: cssStyles.createProjectFromDatabaseLabelWidth
|
||||
}).component();
|
||||
|
||||
const projectNameRow = view.modelBuilder.flexContainer().withItems([projectNameLabel, this.projectNameTextBox], { flex: '0 0 auto', CSSStyles: { 'margin-right': '10px', 'margin-bottom': '-10px', 'margin-top': '-20px' } }).withLayout({ flexFlow: 'row', alignItems: 'center' }).component();
|
||||
const projectNameRow = view.modelBuilder.flexContainer().withItems([projectNameLabel, this.projectNameTextBox], { flex: '0 0 auto', CSSStyles: { 'margin-right': '10px', 'margin-bottom': '-5px', 'margin-top': '-10px' } }).withLayout({ flexFlow: 'row', alignItems: 'center' }).component();
|
||||
|
||||
return projectNameRow;
|
||||
}
|
||||
@@ -258,20 +274,20 @@ export class CreateProjectFromDatabaseDialog {
|
||||
value: '',
|
||||
ariaLabel: constants.projectLocationLabel,
|
||||
placeHolder: constants.projectLocationPlaceholderText,
|
||||
width: cssStyles.textboxWidth,
|
||||
width: cssStyles.createProjectFromDatabaseTextboxWidth,
|
||||
validationErrorMessage: constants.projectLocationRequired
|
||||
}).component();
|
||||
|
||||
this.projectLocationTextBox.onTextChanged(() => {
|
||||
this.projectLocationTextBox!.updateProperty('title', this.projectLocationTextBox!.value);
|
||||
this.updateWorkspaceInputbox(this.projectLocationTextBox!.value!, this.projectNameTextBox!.value!);
|
||||
this.tryEnableCreateButton();
|
||||
});
|
||||
|
||||
const projectLocationLabel = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
|
||||
value: constants.projectLocationLabel,
|
||||
requiredIndicator: true,
|
||||
width: cssStyles.labelWidth,
|
||||
CSSStyles: cssStyles.fontWeightBold
|
||||
width: cssStyles.createProjectFromDatabaseLabelWidth
|
||||
}).component();
|
||||
|
||||
const projectLocationRow = view.modelBuilder.flexContainer().withItems([projectLocationLabel, this.projectLocationTextBox], { flex: '0 0 auto', CSSStyles: { 'margin-right': '10px', 'margin-bottom': '-10px' } }).withLayout({ flexFlow: 'row', alignItems: 'center' }).component();
|
||||
@@ -302,6 +318,7 @@ export class CreateProjectFromDatabaseDialog {
|
||||
|
||||
this.projectLocationTextBox!.value = folderUris[0].fsPath;
|
||||
this.projectLocationTextBox!.updateProperty('title', folderUris[0].fsPath);
|
||||
this.updateWorkspaceInputbox(this.projectLocationTextBox!.value!, this.projectNameTextBox!.value!);
|
||||
});
|
||||
|
||||
return browseFolderButton;
|
||||
@@ -313,7 +330,7 @@ export class CreateProjectFromDatabaseDialog {
|
||||
value: constants.schemaObjectType,
|
||||
ariaLabel: constants.folderStructureLabel,
|
||||
required: true,
|
||||
width: cssStyles.textboxWidth
|
||||
width: cssStyles.createProjectFromDatabaseTextboxWidth
|
||||
}).component();
|
||||
|
||||
this.folderStructureDropDown.onValueChanged(() => {
|
||||
@@ -323,15 +340,50 @@ export class CreateProjectFromDatabaseDialog {
|
||||
const folderStructureLabel = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
|
||||
value: constants.folderStructureLabel,
|
||||
requiredIndicator: true,
|
||||
width: cssStyles.labelWidth,
|
||||
CSSStyles: cssStyles.fontWeightBold
|
||||
width: cssStyles.createProjectFromDatabaseLabelWidth
|
||||
}).component();
|
||||
|
||||
const folderStructureRow = view.modelBuilder.flexContainer().withItems([folderStructureLabel, <azdata.DropDownComponent>this.folderStructureDropDown], { flex: '0 0 auto', CSSStyles: { 'margin-right': '10px', 'margin-top': '-20px' } }).withLayout({ flexFlow: 'row', alignItems: 'center' }).component();
|
||||
const folderStructureRow = view.modelBuilder.flexContainer().withItems([folderStructureLabel, <azdata.DropDownComponent>this.folderStructureDropDown], { flex: '0 0 auto', CSSStyles: { 'margin-right': '10px', 'margin-top': '-10px' } }).withLayout({ flexFlow: 'row', alignItems: 'center' }).component();
|
||||
|
||||
return folderStructureRow;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates container with information on which workspace the project will be added to and where the workspace will be
|
||||
* created if no workspace is currently open
|
||||
* @param view
|
||||
*/
|
||||
private createWorkspaceContainerRow(view: azdata.ModelView): azdata.FlexContainer {
|
||||
this.workspaceInputBox = view.modelBuilder.inputBox().withProperties({
|
||||
ariaLabel: constants.workspaceLocationTitle,
|
||||
enabled: false,
|
||||
value: vscode.workspace.workspaceFile?.fsPath ?? '',
|
||||
title: vscode.workspace.workspaceFile?.fsPath ?? '' // hovertext for if file path is too long to be seen in textbox
|
||||
}).component();
|
||||
|
||||
const workspaceLabel = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
|
||||
value: vscode.workspace.workspaceFile ? constants.addProjectToCurrentWorkspace : constants.newWorkspaceWillBeCreated,
|
||||
CSSStyles: { 'margin-top': '-10px', 'margin-bottom': '5px' }
|
||||
}).component();
|
||||
|
||||
const workspaceContainerRow = view.modelBuilder.flexContainer().withItems([workspaceLabel, this.workspaceInputBox], { flex: '0 0 auto', CSSStyles: { 'margin-right': '10px', 'margin-top': '0px' } }).withLayout({ flexFlow: 'column' }).component();
|
||||
|
||||
return workspaceContainerRow;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the workspace inputbox based on the passed in location and name if there isn't a workspace currently open
|
||||
* @param location
|
||||
* @param name
|
||||
*/
|
||||
public updateWorkspaceInputbox(location: string, name: string): void {
|
||||
if (!vscode.workspace.workspaceFile) {
|
||||
const fileLocation = location && name ? path.join(location, `${name}.code-workspace`) : '';
|
||||
this.workspaceInputBox!.value = fileLocation;
|
||||
this.workspaceInputBox!.title = fileLocation;
|
||||
}
|
||||
}
|
||||
|
||||
// only enable Create button if all fields are filled
|
||||
public tryEnableCreateButton(): void {
|
||||
if (this.sourceConnectionTextBox!.value && this.sourceDatabaseDropDown!.value
|
||||
|
||||
@@ -289,7 +289,7 @@ export class PublishDatabaseDialog {
|
||||
value: '',
|
||||
ariaLabel: constants.targetConnectionLabel,
|
||||
placeHolder: constants.selectConnection,
|
||||
width: cssStyles.textboxWidth,
|
||||
width: cssStyles.publishDialogTextboxWidth,
|
||||
enabled: false
|
||||
}).component();
|
||||
|
||||
@@ -353,12 +353,12 @@ export class PublishDatabaseDialog {
|
||||
this.loadProfileTextBox = view.modelBuilder.inputBox().withProperties({
|
||||
placeHolder: constants.loadProfilePlaceholderText,
|
||||
ariaLabel: constants.profile,
|
||||
width: cssStyles.textboxWidth
|
||||
width: cssStyles.publishDialogTextboxWidth
|
||||
}).component();
|
||||
|
||||
const profileLabel = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
|
||||
value: constants.profile,
|
||||
width: cssStyles.labelWidth
|
||||
width: cssStyles.publishDialogLabelWidth
|
||||
}).component();
|
||||
|
||||
const profileRow = view.modelBuilder.flexContainer().withItems([profileLabel, this.loadProfileTextBox], { flex: '0 0 auto', CSSStyles: { 'margin-right': '10px' } }).withLayout({ flexFlow: 'row', alignItems: 'center' }).component();
|
||||
@@ -374,7 +374,7 @@ export class PublishDatabaseDialog {
|
||||
const serverLabel = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
|
||||
value: constants.server,
|
||||
requiredIndicator: true,
|
||||
width: cssStyles.labelWidth
|
||||
width: cssStyles.publishDialogLabelWidth
|
||||
}).component();
|
||||
|
||||
const connectionRow = view.modelBuilder.flexContainer().withItems([serverLabel, this.targetConnectionTextBox], { flex: '0 0 auto', CSSStyles: { 'margin-right': '10px' } }).withLayout({ flexFlow: 'row', alignItems: 'center' }).component();
|
||||
@@ -389,7 +389,7 @@ export class PublishDatabaseDialog {
|
||||
value: this.getDefaultDatabaseName(),
|
||||
ariaLabel: constants.databaseNameLabel,
|
||||
required: true,
|
||||
width: cssStyles.textboxWidth,
|
||||
width: cssStyles.publishDialogTextboxWidth,
|
||||
editable: true,
|
||||
fireOnTextChange: true
|
||||
}).component();
|
||||
@@ -401,7 +401,7 @@ export class PublishDatabaseDialog {
|
||||
const databaseLabel = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
|
||||
value: constants.databaseNameLabel,
|
||||
requiredIndicator: true,
|
||||
width: cssStyles.labelWidth
|
||||
width: cssStyles.publishDialogLabelWidth
|
||||
}).component();
|
||||
|
||||
const databaseRow = view.modelBuilder.flexContainer().withItems([databaseLabel, <azdata.DropDownComponent>this.targetDatabaseDropDown], { flex: '0 0 auto', CSSStyles: { 'margin-right': '10px' } }).withLayout({ flexFlow: 'row', alignItems: 'center' }).component();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user