mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-18 18:46:47 -05:00
Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1b5c54dd8c | ||
|
|
4082170522 | ||
|
|
5ecf1c6e6f | ||
|
|
6de11c8107 | ||
|
|
76d7b0a9fe | ||
|
|
ce4c3e9586 | ||
|
|
5190bf376c | ||
|
|
77b9a708df | ||
|
|
a4ee871b88 | ||
|
|
3f4e19fc08 | ||
|
|
571fca6de5 | ||
|
|
5a2fdc4034 | ||
|
|
cc6d84e7f6 | ||
|
|
99e11d2e22 | ||
|
|
9a85123e21 | ||
|
|
56669db6b6 | ||
|
|
8782eeb32f | ||
|
|
7f3d5bac0a | ||
|
|
7a1e0a7d2e | ||
|
|
681ecbd946 | ||
|
|
e7798a8e32 | ||
|
|
b158180ef4 | ||
|
|
7ad9da7fda | ||
|
|
94e2016a16 | ||
|
|
21bb577da8 | ||
|
|
5e8325ba28 | ||
|
|
25b7ccade3 | ||
|
|
57940c581c | ||
|
|
82f9e4e24b | ||
|
|
3e22fcfd2d | ||
|
|
0bc81e1078 | ||
|
|
7b6328dccf | ||
|
|
05124273ea | ||
|
|
b1d4444522 | ||
|
|
4ee2d369cf | ||
|
|
fb28b69bb0 | ||
|
|
f2709c7100 | ||
|
|
3476f5ae38 | ||
|
|
b937fdee7a | ||
|
|
dd9ac2e362 | ||
|
|
403ff6cfec | ||
|
|
4a6226974e | ||
|
|
6a2c47f511 | ||
|
|
3d9a316f4b |
@@ -85,16 +85,36 @@
|
|||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"source": [
|
"source": [
|
||||||
"# Required Values\n",
|
"# Required Values\n",
|
||||||
|
"env_var = \"AZDATA_NB_VAR_CONTROLLER_ENDPOINT\" in os.environ\n",
|
||||||
|
"if env_var:\n",
|
||||||
|
" controller_endpoint = os.environ[\"AZDATA_NB_VAR_CONTROLLER_ENDPOINT\"]\n",
|
||||||
|
"else:\n",
|
||||||
|
" sys.exit(f'environment variable: AZDATA_NB_VAR_CONTROLLER_ENDPOINT was not defined. Exiting\\n')\n",
|
||||||
|
"\n",
|
||||||
|
"env_var = \"AZDATA_NB_VAR_CONTROLLER_USERNAME\" in os.environ\n",
|
||||||
|
"if env_var:\n",
|
||||||
|
" controller_username = os.environ[\"AZDATA_NB_VAR_CONTROLLER_USERNAME\"]\n",
|
||||||
|
"else:\n",
|
||||||
|
" sys.exit(f'environment variable: AZDATA_NB_VAR_CONTROLLER_USERNAME was not defined. Exiting\\n')\n",
|
||||||
|
"\n",
|
||||||
|
"env_var = \"AZDATA_NB_VAR_CONTROLLER_PASSWORD\" in os.environ\n",
|
||||||
|
"if env_var:\n",
|
||||||
|
" controller_password = os.environ[\"AZDATA_NB_VAR_CONTROLLER_PASSWORD\"]\n",
|
||||||
|
"else:\n",
|
||||||
|
" sys.exit(f'environment variable: AZDATA_NB_VAR_CONTROLLER_PASSWORD was not defined. Exiting\\n')\n",
|
||||||
|
"\n",
|
||||||
"env_var = \"AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_NAME\" in os.environ\n",
|
"env_var = \"AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_NAME\" in os.environ\n",
|
||||||
"if env_var:\n",
|
"if env_var:\n",
|
||||||
" server_group_name = os.environ[\"AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_NAME\"]\n",
|
" server_group_name = os.environ[\"AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_NAME\"]\n",
|
||||||
"else:\n",
|
"else:\n",
|
||||||
" sys.exit(f'environment variable: AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_NAME was not defined. Exiting\\n')\n",
|
" sys.exit(f'environment variable: AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_NAME was not defined. Exiting\\n')\n",
|
||||||
|
"\n",
|
||||||
"env_var = \"AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_PASSWORD\" in os.environ\n",
|
"env_var = \"AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_PASSWORD\" in os.environ\n",
|
||||||
"if env_var:\n",
|
"if env_var:\n",
|
||||||
" postgres_password = os.environ[\"AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_PASSWORD\"]\n",
|
" postgres_password = os.environ[\"AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_PASSWORD\"]\n",
|
||||||
"else:\n",
|
"else:\n",
|
||||||
" sys.exit(f'environment variable: AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_PASSWORD was not defined. Exiting\\n') \n",
|
" sys.exit(f'environment variable: AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_PASSWORD was not defined. Exiting\\n') \n",
|
||||||
|
"\n",
|
||||||
"env_var = \"AZDATA_NB_VAR_POSTGRES_STORAGE_CLASS_DATA\" in os.environ\n",
|
"env_var = \"AZDATA_NB_VAR_POSTGRES_STORAGE_CLASS_DATA\" in os.environ\n",
|
||||||
"if env_var:\n",
|
"if env_var:\n",
|
||||||
" postgres_storage_class_data = os.environ[\"AZDATA_NB_VAR_POSTGRES_STORAGE_CLASS_DATA\"]\n",
|
" postgres_storage_class_data = os.environ[\"AZDATA_NB_VAR_POSTGRES_STORAGE_CLASS_DATA\"]\n",
|
||||||
@@ -154,6 +174,21 @@
|
|||||||
"azdata_cell_guid": "90b0e162-2987-463f-9ce6-12dda1267189"
|
"azdata_cell_guid": "90b0e162-2987-463f-9ce6-12dda1267189"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"source": [
|
||||||
|
"# Login to the data controller.\n",
|
||||||
|
"#\n",
|
||||||
|
"os.environ[\"AZDATA_PASSWORD\"] = controller_password\n",
|
||||||
|
"cmd = f'azdata login -e {controller_endpoint} -u {controller_username}'\n",
|
||||||
|
"out=run_command()"
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"azdata_cell_guid": "71366399-5963-4e24-b2f2-6bb5bffba4ec"
|
||||||
|
},
|
||||||
|
"outputs": [],
|
||||||
|
"execution_count": null
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"source": [
|
"source": [
|
||||||
@@ -177,4 +212,4 @@
|
|||||||
"execution_count": null
|
"execution_count": null
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -85,21 +85,42 @@
|
|||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"source": [
|
"source": [
|
||||||
"# Required Values\n",
|
"# Required Values\n",
|
||||||
|
"env_var = \"AZDATA_NB_VAR_CONTROLLER_ENDPOINT\" in os.environ\n",
|
||||||
|
"if env_var:\n",
|
||||||
|
" controller_endpoint = os.environ[\"AZDATA_NB_VAR_CONTROLLER_ENDPOINT\"]\n",
|
||||||
|
"else:\n",
|
||||||
|
" sys.exit(f'environment variable: AZDATA_NB_VAR_CONTROLLER_ENDPOINT was not defined. Exiting\\n')\n",
|
||||||
|
"\n",
|
||||||
|
"env_var = \"AZDATA_NB_VAR_CONTROLLER_USERNAME\" in os.environ\n",
|
||||||
|
"if env_var:\n",
|
||||||
|
" controller_username = os.environ[\"AZDATA_NB_VAR_CONTROLLER_USERNAME\"]\n",
|
||||||
|
"else:\n",
|
||||||
|
" sys.exit(f'environment variable: AZDATA_NB_VAR_CONTROLLER_USERNAME was not defined. Exiting\\n')\n",
|
||||||
|
"\n",
|
||||||
|
"env_var = \"AZDATA_NB_VAR_CONTROLLER_PASSWORD\" in os.environ\n",
|
||||||
|
"if env_var:\n",
|
||||||
|
" controller_password = os.environ[\"AZDATA_NB_VAR_CONTROLLER_PASSWORD\"]\n",
|
||||||
|
"else:\n",
|
||||||
|
" sys.exit(f'environment variable: AZDATA_NB_VAR_CONTROLLER_PASSWORD was not defined. Exiting\\n')\n",
|
||||||
|
"\n",
|
||||||
"env_var = \"AZDATA_NB_VAR_SQL_INSTANCE_NAME\" in os.environ\n",
|
"env_var = \"AZDATA_NB_VAR_SQL_INSTANCE_NAME\" in os.environ\n",
|
||||||
"if env_var:\n",
|
"if env_var:\n",
|
||||||
" mssql_instance_name = os.environ[\"AZDATA_NB_VAR_SQL_INSTANCE_NAME\"]\n",
|
" mssql_instance_name = os.environ[\"AZDATA_NB_VAR_SQL_INSTANCE_NAME\"]\n",
|
||||||
"else:\n",
|
"else:\n",
|
||||||
" sys.exit(f'environment variable: AZDATA_NB_VAR_SQL_INSTANCE_NAME was not defined. Exiting\\n')\n",
|
" sys.exit(f'environment variable: AZDATA_NB_VAR_SQL_INSTANCE_NAME was not defined. Exiting\\n')\n",
|
||||||
|
"\n",
|
||||||
"env_var = \"AZDATA_NB_VAR_SQL_PASSWORD\" in os.environ\n",
|
"env_var = \"AZDATA_NB_VAR_SQL_PASSWORD\" in os.environ\n",
|
||||||
"if env_var:\n",
|
"if env_var:\n",
|
||||||
" mssql_password = os.environ[\"AZDATA_NB_VAR_SQL_PASSWORD\"]\n",
|
" mssql_password = os.environ[\"AZDATA_NB_VAR_SQL_PASSWORD\"]\n",
|
||||||
"else:\n",
|
"else:\n",
|
||||||
" sys.exit(f'environment variable: AZDATA_NB_VAR_SQL_PASSWORD was not defined. Exiting\\n') \n",
|
" sys.exit(f'environment variable: AZDATA_NB_VAR_SQL_PASSWORD was not defined. Exiting\\n')\n",
|
||||||
|
"\n",
|
||||||
"env_var = \"AZDATA_NB_VAR_SQL_STORAGE_CLASS_DATA\" in os.environ\n",
|
"env_var = \"AZDATA_NB_VAR_SQL_STORAGE_CLASS_DATA\" in os.environ\n",
|
||||||
"if env_var:\n",
|
"if env_var:\n",
|
||||||
" mssql_storage_class_data = os.environ[\"AZDATA_NB_VAR_SQL_STORAGE_CLASS_DATA\"]\n",
|
" mssql_storage_class_data = os.environ[\"AZDATA_NB_VAR_SQL_STORAGE_CLASS_DATA\"]\n",
|
||||||
"else:\n",
|
"else:\n",
|
||||||
" sys.exit(f'environment variable: AZDATA_NB_VAR_SQL_STORAGE_CLASS_DATA was not defined. Exiting\\n') \n",
|
" sys.exit(f'environment variable: AZDATA_NB_VAR_SQL_STORAGE_CLASS_DATA was not defined. Exiting\\n')\n",
|
||||||
|
"\n",
|
||||||
"env_var = \"AZDATA_NB_VAR_SQL_STORAGE_CLASS_LOGS\" in os.environ\n",
|
"env_var = \"AZDATA_NB_VAR_SQL_STORAGE_CLASS_LOGS\" in os.environ\n",
|
||||||
"if env_var:\n",
|
"if env_var:\n",
|
||||||
" mssql_storage_class_logs = os.environ[\"AZDATA_NB_VAR_SQL_STORAGE_CLASS_LOGS\"]\n",
|
" mssql_storage_class_logs = os.environ[\"AZDATA_NB_VAR_SQL_STORAGE_CLASS_LOGS\"]\n",
|
||||||
@@ -123,6 +144,21 @@
|
|||||||
"azdata_cell_guid": "90b0e162-2987-463f-9ce6-12dda1267189"
|
"azdata_cell_guid": "90b0e162-2987-463f-9ce6-12dda1267189"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"source": [
|
||||||
|
"# Login to the data controller.\n",
|
||||||
|
"#\n",
|
||||||
|
"os.environ[\"AZDATA_PASSWORD\"] = controller_password\n",
|
||||||
|
"cmd = f'azdata login -e {controller_endpoint} -u {controller_username}'\n",
|
||||||
|
"out=run_command()"
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"azdata_cell_guid": "1437c536-17e8-4a7f-80c1-aa43ad02686c"
|
||||||
|
},
|
||||||
|
"outputs": [],
|
||||||
|
"execution_count": null
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"source": [
|
"source": [
|
||||||
@@ -139,4 +175,4 @@
|
|||||||
"execution_count": null
|
"execution_count": null
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -547,20 +547,7 @@
|
|||||||
],
|
],
|
||||||
"when": true
|
"when": true
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
"agreement": {
|
|
||||||
"template": "%arc.control.plane.arc.data.controller.agreement%",
|
|
||||||
"links": [
|
|
||||||
{
|
|
||||||
"text": "%microsoft.agreement.privacy.statement%",
|
|
||||||
"url": "https://go.microsoft.com/fwlink/?LinkId=853010"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"text": "%arc.agreement.azdata.eula%",
|
|
||||||
"url": "https://aka.ms/eula-azdata-en"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "arc.sql",
|
"name": "arc.sql",
|
||||||
@@ -571,18 +558,6 @@
|
|||||||
"light": "./images/miaa.svg",
|
"light": "./images/miaa.svg",
|
||||||
"dark": "./images/miaa.svg"
|
"dark": "./images/miaa.svg"
|
||||||
},
|
},
|
||||||
"options": [
|
|
||||||
{
|
|
||||||
"name": "resourceType",
|
|
||||||
"displayName": "%resource.type.picker.display.name%",
|
|
||||||
"values": [
|
|
||||||
{
|
|
||||||
"name": "sql.managed.instance",
|
|
||||||
"displayName": "%sql.managed.instance.display.name%"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"providers": [
|
"providers": [
|
||||||
{
|
{
|
||||||
"dialog": {
|
"dialog": {
|
||||||
@@ -599,6 +574,25 @@
|
|||||||
{
|
{
|
||||||
"title": "%arc.sql.settings.section.title%",
|
"title": "%arc.sql.settings.section.title%",
|
||||||
"fields": [
|
"fields": [
|
||||||
|
{
|
||||||
|
"label": "%arc.controller%",
|
||||||
|
"variableName": "AZDATA_NB_VAR_ARC_CONTROLLER",
|
||||||
|
"type": "options",
|
||||||
|
"editable": false,
|
||||||
|
"required": true,
|
||||||
|
"options": {
|
||||||
|
"source": {
|
||||||
|
"type": "ArcControllersOptionsSource",
|
||||||
|
"variableNames": {
|
||||||
|
"endpoint": "AZDATA_NB_VAR_CONTROLLER_ENDPOINT",
|
||||||
|
"username": "AZDATA_NB_VAR_CONTROLLER_USERNAME",
|
||||||
|
"password": "AZDATA_NB_VAR_CONTROLLER_PASSWORD"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"optionsType": "dropdown"
|
||||||
|
},
|
||||||
|
"labelWidth": "100%"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"label": "%arc.sql.instance.name%",
|
"label": "%arc.sql.instance.name%",
|
||||||
"variableName": "AZDATA_NB_VAR_SQL_INSTANCE_NAME",
|
"variableName": "AZDATA_NB_VAR_SQL_INSTANCE_NAME",
|
||||||
@@ -653,7 +647,7 @@
|
|||||||
"version": "20.1.0"
|
"version": "20.1.0"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"when": "resourceType=sql.managed.instance"
|
"when": "true"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"agreement": {
|
"agreement": {
|
||||||
@@ -666,10 +660,6 @@
|
|||||||
{
|
{
|
||||||
"text": "%arc.agreement.sql.terms.conditions%",
|
"text": "%arc.agreement.sql.terms.conditions%",
|
||||||
"url": "https://go.microsoft.com/fwlink/?linkid=2045708"
|
"url": "https://go.microsoft.com/fwlink/?linkid=2045708"
|
||||||
},
|
|
||||||
{
|
|
||||||
"text": "%arc.agreement.azdata.eula%",
|
|
||||||
"url": "https://aka.ms/eula-azdata-en"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -683,18 +673,6 @@
|
|||||||
"light": "./images/postgres.svg",
|
"light": "./images/postgres.svg",
|
||||||
"dark": "./images/postgres.svg"
|
"dark": "./images/postgres.svg"
|
||||||
},
|
},
|
||||||
"options": [
|
|
||||||
{
|
|
||||||
"name": "resourceType",
|
|
||||||
"displayName": "%resource.type.picker.display.name%",
|
|
||||||
"values": [
|
|
||||||
{
|
|
||||||
"name": "postgres",
|
|
||||||
"displayName": "%postgres.server.group.display.name%"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"providers": [
|
"providers": [
|
||||||
{
|
{
|
||||||
"dialog": {
|
"dialog": {
|
||||||
@@ -711,6 +689,25 @@
|
|||||||
{
|
{
|
||||||
"title": "%arc.postgres.settings.section.title%",
|
"title": "%arc.postgres.settings.section.title%",
|
||||||
"fields": [
|
"fields": [
|
||||||
|
{
|
||||||
|
"label": "%arc.controller%",
|
||||||
|
"variableName": "AZDATA_NB_VAR_ARC_CONTROLLER",
|
||||||
|
"type": "options",
|
||||||
|
"editable": false,
|
||||||
|
"required": true,
|
||||||
|
"options": {
|
||||||
|
"source": {
|
||||||
|
"type": "ArcControllersOptionsSource",
|
||||||
|
"variableNames": {
|
||||||
|
"endpoint": "AZDATA_NB_VAR_CONTROLLER_ENDPOINT",
|
||||||
|
"username": "AZDATA_NB_VAR_CONTROLLER_USERNAME",
|
||||||
|
"password": "AZDATA_NB_VAR_CONTROLLER_PASSWORD"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"optionsType": "dropdown"
|
||||||
|
},
|
||||||
|
"labelWidth": "100%"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"label": "%arc.postgres.server.group.name%",
|
"label": "%arc.postgres.server.group.name%",
|
||||||
"variableName": "AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_NAME",
|
"variableName": "AZDATA_NB_VAR_POSTGRES_SERVER_GROUP_NAME",
|
||||||
@@ -810,7 +807,7 @@
|
|||||||
"version": "20.1.0"
|
"version": "20.1.0"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"when": "resourceType=postgres"
|
"when": "true"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"agreement": {
|
"agreement": {
|
||||||
@@ -823,10 +820,6 @@
|
|||||||
{
|
{
|
||||||
"text": "%arc.agreement.postgres.terms.conditions%",
|
"text": "%arc.agreement.postgres.terms.conditions%",
|
||||||
"url": "https://go.microsoft.com/fwlink/?linkid=2045708"
|
"url": "https://go.microsoft.com/fwlink/?linkid=2045708"
|
||||||
},
|
|
||||||
{
|
|
||||||
"text": "%arc.agreement.azdata.eula%",
|
|
||||||
"url": "https://aka.ms/eula-azdata-en"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,7 +65,6 @@
|
|||||||
"arc.control.plane.summary.location": "Location",
|
"arc.control.plane.summary.location": "Location",
|
||||||
"arc.control.plane.arc.data.controller.agreement": "I accept {0} and {1}.",
|
"arc.control.plane.arc.data.controller.agreement": "I accept {0} and {1}.",
|
||||||
"microsoft.agreement.privacy.statement":"Microsoft Privacy Statement",
|
"microsoft.agreement.privacy.statement":"Microsoft Privacy Statement",
|
||||||
"arc.agreement.azdata.eula":"azdata license terms",
|
|
||||||
"deploy.arc.control.plane.action":"Script to notebook",
|
"deploy.arc.control.plane.action":"Script to notebook",
|
||||||
|
|
||||||
|
|
||||||
@@ -73,9 +72,9 @@
|
|||||||
"resource.type.arc.postgres.display.name": "PostgreSQL server groups - Azure Arc (preview)",
|
"resource.type.arc.postgres.display.name": "PostgreSQL server groups - Azure Arc (preview)",
|
||||||
"resource.type.arc.sql.description": "Managed SQL Instance service for app developers in a customer-managed environment",
|
"resource.type.arc.sql.description": "Managed SQL Instance service for app developers in a customer-managed environment",
|
||||||
"resource.type.arc.postgres.description": "Deploy PostgreSQL server groups into an Azure Arc environment",
|
"resource.type.arc.postgres.description": "Deploy PostgreSQL server groups into an Azure Arc environment",
|
||||||
"resource.type.picker.display.name": "Resource Type",
|
"arc.controller": "Target Azure Arc Controller",
|
||||||
"sql.managed.instance.display.name": "Azure SQL managed instance - Azure Arc",
|
|
||||||
"postgres.server.group.display.name": "PostgreSQL server groups - Azure Arc",
|
|
||||||
"arc.sql.new.dialog.title": "Deploy Azure SQL managed instance - Azure Arc (preview)",
|
"arc.sql.new.dialog.title": "Deploy Azure SQL managed instance - Azure Arc (preview)",
|
||||||
"arc.sql.settings.section.title": "SQL Connection information",
|
"arc.sql.settings.section.title": "SQL Connection information",
|
||||||
"arc.azure.section.title": "Azure information",
|
"arc.azure.section.title": "Azure information",
|
||||||
@@ -108,7 +107,7 @@
|
|||||||
"arc.postgres.server.group.cores.limit": "Max CPU cores (per node) to allow",
|
"arc.postgres.server.group.cores.limit": "Max CPU cores (per node) to allow",
|
||||||
"arc.postgres.server.group.memory.request": "Min memory MB (per node) to reserve",
|
"arc.postgres.server.group.memory.request": "Min memory MB (per node) to reserve",
|
||||||
"arc.postgres.server.group.memory.limit": "Max memory MB (per node) to allow",
|
"arc.postgres.server.group.memory.limit": "Max memory MB (per node) to allow",
|
||||||
"arc.agreement": "I accept {0}, {1} and {2}.",
|
"arc.agreement": "I accept {0} and {1}.",
|
||||||
"arc.agreement.sql.terms.conditions":"Azure SQL managed instance - Azure Arc terms and conditions",
|
"arc.agreement.sql.terms.conditions":"Azure SQL managed instance - Azure Arc terms and conditions",
|
||||||
"arc.agreement.postgres.terms.conditions":"PostgreSQL server groups - Azure Arc terms and conditions",
|
"arc.agreement.postgres.terms.conditions":"PostgreSQL server groups - Azure Arc terms and conditions",
|
||||||
"arc.deploy.action":"Deploy"
|
"arc.deploy.action":"Deploy"
|
||||||
|
|||||||
@@ -5,9 +5,10 @@
|
|||||||
|
|
||||||
import * as arc from 'arc';
|
import * as arc from 'arc';
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
|
import { UserCancelledError } from './common/utils';
|
||||||
import { IconPathHelper, refreshActionId } from './constants';
|
import { IconPathHelper, refreshActionId } from './constants';
|
||||||
import * as loc from './localizedConstants';
|
import * as loc from './localizedConstants';
|
||||||
import { ConnectToControllerDialog } from './ui/dialogs/connectControllerDialog';
|
import { ConnectToControllerDialog, PasswordToControllerDialog } from './ui/dialogs/connectControllerDialog';
|
||||||
import { AzureArcTreeDataProvider } from './ui/tree/azureArcTreeDataProvider';
|
import { AzureArcTreeDataProvider } from './ui/tree/azureArcTreeDataProvider';
|
||||||
import { ControllerTreeNode } from './ui/tree/controllerTreeNode';
|
import { ControllerTreeNode } from './ui/tree/controllerTreeNode';
|
||||||
import { TreeNode } from './ui/tree/treeNode';
|
import { TreeNode } from './ui/tree/treeNode';
|
||||||
@@ -57,11 +58,24 @@ export async function activate(context: vscode.ExtensionContext): Promise<arc.IE
|
|||||||
await checkArcDeploymentExtension();
|
await checkArcDeploymentExtension();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
getRegisteredDataControllers: async () => {
|
getRegisteredDataControllers: async () => (await treeDataProvider.getChildren())
|
||||||
return (await treeDataProvider.getChildren())
|
.filter(node => node instanceof ControllerTreeNode)
|
||||||
.filter(node => node instanceof ControllerTreeNode)
|
.map(node => ({
|
||||||
.map(node => (node as ControllerTreeNode).model.info);
|
label: (node as ControllerTreeNode).model.label,
|
||||||
|
info: (node as ControllerTreeNode).model.info
|
||||||
|
})),
|
||||||
|
getControllerPassword: async (controllerInfo: arc.ControllerInfo) => {
|
||||||
|
return await treeDataProvider.getPassword(controllerInfo);
|
||||||
|
},
|
||||||
|
reacquireControllerPassword: async (controllerInfo: arc.ControllerInfo) => {
|
||||||
|
let model;
|
||||||
|
const dialog = new PasswordToControllerDialog(treeDataProvider);
|
||||||
|
dialog.showDialog(controllerInfo);
|
||||||
|
model = await dialog.waitForClose();
|
||||||
|
if (!model) {
|
||||||
|
throw new UserCancelledError();
|
||||||
|
}
|
||||||
|
return model.password;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ export const indirect = localize('arc.indirect', "Indirect");
|
|||||||
export const loading = localize('arc.loading', "Loading...");
|
export const loading = localize('arc.loading', "Loading...");
|
||||||
export const refreshToEnterCredentials = localize('arc.refreshToEnterCredentials', "Refresh node to enter credentials");
|
export const refreshToEnterCredentials = localize('arc.refreshToEnterCredentials', "Refresh node to enter credentials");
|
||||||
export const connectToController = localize('arc.connectToController', "Connect to Existing Controller");
|
export const connectToController = localize('arc.connectToController', "Connect to Existing Controller");
|
||||||
|
export const passwordToController = localize('arc.passwordToController', "Provide Password to Controller");
|
||||||
export const controllerUrl = localize('arc.controllerUrl', "Controller URL");
|
export const controllerUrl = localize('arc.controllerUrl', "Controller URL");
|
||||||
export const controllerName = localize('arc.controllerName', "Name");
|
export const controllerName = localize('arc.controllerName', "Name");
|
||||||
export const defaultControllerName = localize('arc.defaultControllerName', "arc-dc");
|
export const defaultControllerName = localize('arc.defaultControllerName', "arc-dc");
|
||||||
@@ -81,6 +82,7 @@ export const password = localize('arc.password', "Password");
|
|||||||
export const rememberPassword = localize('arc.rememberPassword', "Remember Password");
|
export const rememberPassword = localize('arc.rememberPassword', "Remember Password");
|
||||||
export const connect = localize('arc.connect', "Connect");
|
export const connect = localize('arc.connect', "Connect");
|
||||||
export const cancel = localize('arc.cancel', "Cancel");
|
export const cancel = localize('arc.cancel', "Cancel");
|
||||||
|
export const ok = localize('arc.ok', "Ok");
|
||||||
export const notConfigured = localize('arc.notConfigured', "Not Configured");
|
export const notConfigured = localize('arc.notConfigured', "Not Configured");
|
||||||
|
|
||||||
// Database States - see https://docs.microsoft.com/sql/relational-databases/databases/database-states
|
// Database States - see https://docs.microsoft.com/sql/relational-databases/databases/database-states
|
||||||
@@ -156,3 +158,6 @@ export function invalidResourceDeletionName(name: string): string { return local
|
|||||||
export function couldNotFindAzureResource(name: string): string { return localize('arc.couldNotFindAzureResource', "Could not find Azure resource for {0}", name); }
|
export function couldNotFindAzureResource(name: string): string { return localize('arc.couldNotFindAzureResource', "Could not find Azure resource for {0}", name); }
|
||||||
export function passwordResetFailed(error: any): string { return localize('arc.passwordResetFailed', "Failed to reset password. {0}", getErrorMessage(error)); }
|
export function passwordResetFailed(error: any): string { return localize('arc.passwordResetFailed', "Failed to reset password. {0}", getErrorMessage(error)); }
|
||||||
export function errorConnectingToController(error: any): string { return localize('arc.errorConnectingToController', "Error connecting to controller. {0}", getErrorMessage(error)); }
|
export function errorConnectingToController(error: any): string { return localize('arc.errorConnectingToController', "Error connecting to controller. {0}", getErrorMessage(error)); }
|
||||||
|
export function passwordAcquisitionFailed(error: any): string { return localize('arc.passwordAcquisitionFailed', "Failed to acquire password. {0}", getErrorMessage(error)); }
|
||||||
|
export const invalidPassword = localize('arc.invalidPassword', "The password did not work, try again.");
|
||||||
|
export function errorVerifyingPassword(error: any): string { return localize('arc.errorVerifyingPassword', "Error encountered while verifying password. {0}", getErrorMessage(error)); }
|
||||||
|
|||||||
8
extensions/arc/src/typings/arc.d.ts
vendored
8
extensions/arc/src/typings/arc.d.ts
vendored
@@ -35,7 +35,13 @@ declare module 'arc' {
|
|||||||
resources: ResourceInfo[]
|
resources: ResourceInfo[]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export interface DataController {
|
||||||
|
label: string,
|
||||||
|
info: ControllerInfo
|
||||||
|
}
|
||||||
export interface IExtension {
|
export interface IExtension {
|
||||||
getRegisteredDataControllers(): Promise<ControllerInfo[]>;
|
getRegisteredDataControllers(): Promise<DataController[]>;
|
||||||
|
getControllerPassword(controllerInfo: ControllerInfo): Promise<string>;
|
||||||
|
reacquireControllerPassword(controllerInfo: ControllerInfo, password: string, retryCount?: number): Promise<string>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
import { ControllerInfo } from 'arc';
|
import { ControllerInfo } from 'arc';
|
||||||
import * as azdata from 'azdata';
|
import * as azdata from 'azdata';
|
||||||
|
import * as azdataExt from 'azdata-ext';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import { Deferred } from '../../common/promise';
|
import { Deferred } from '../../common/promise';
|
||||||
@@ -12,94 +13,136 @@ import * as loc from '../../localizedConstants';
|
|||||||
import { ControllerModel } from '../../models/controllerModel';
|
import { ControllerModel } from '../../models/controllerModel';
|
||||||
import { InitializingComponent } from '../components/initializingComponent';
|
import { InitializingComponent } from '../components/initializingComponent';
|
||||||
import { AzureArcTreeDataProvider } from '../tree/azureArcTreeDataProvider';
|
import { AzureArcTreeDataProvider } from '../tree/azureArcTreeDataProvider';
|
||||||
|
import { getErrorMessage } from '../../common/utils';
|
||||||
|
|
||||||
export type ConnectToControllerDialogModel = { controllerModel: ControllerModel, password: string };
|
export type ConnectToControllerDialogModel = { controllerModel: ControllerModel, password: string };
|
||||||
export class ConnectToControllerDialog extends InitializingComponent {
|
abstract class ControllerDialogBase extends InitializingComponent {
|
||||||
private modelBuilder!: azdata.ModelBuilder;
|
protected modelBuilder!: azdata.ModelBuilder;
|
||||||
|
protected dialog: azdata.window.Dialog;
|
||||||
|
|
||||||
private urlInputBox!: azdata.InputBoxComponent;
|
protected urlInputBox!: azdata.InputBoxComponent;
|
||||||
private nameInputBox!: azdata.InputBoxComponent;
|
protected nameInputBox!: azdata.InputBoxComponent;
|
||||||
private usernameInputBox!: azdata.InputBoxComponent;
|
protected usernameInputBox!: azdata.InputBoxComponent;
|
||||||
private passwordInputBox!: azdata.InputBoxComponent;
|
protected passwordInputBox!: azdata.InputBoxComponent;
|
||||||
private rememberPwCheckBox!: azdata.CheckBoxComponent;
|
|
||||||
|
|
||||||
private _completionPromise = new Deferred<ConnectToControllerDialogModel | undefined>();
|
protected getComponents(): (azdata.FormComponent<azdata.Component> & { layout?: azdata.FormItemLayout | undefined; })[] {
|
||||||
|
return [
|
||||||
private _id!: string;
|
{
|
||||||
|
component: this.urlInputBox,
|
||||||
constructor(private _treeDataProvider: AzureArcTreeDataProvider) {
|
title: loc.controllerUrl,
|
||||||
super();
|
required: true
|
||||||
|
}, {
|
||||||
|
component: this.nameInputBox,
|
||||||
|
title: loc.controllerName,
|
||||||
|
required: false
|
||||||
|
}, {
|
||||||
|
component: this.usernameInputBox,
|
||||||
|
title: loc.username,
|
||||||
|
required: true
|
||||||
|
}, {
|
||||||
|
component: this.passwordInputBox,
|
||||||
|
title: loc.password,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public showDialog(controllerInfo?: ControllerInfo, password?: string): azdata.window.Dialog {
|
protected abstract fieldToFocusOn(): azdata.Component;
|
||||||
this._id = controllerInfo?.id ?? uuid();
|
protected readonlyFields(): azdata.InputBoxComponent[] { return []; }
|
||||||
const dialog = azdata.window.createModelViewDialog(loc.connectToController);
|
|
||||||
dialog.cancelButton.onClick(() => this.handleCancel());
|
|
||||||
dialog.registerContent(async view => {
|
|
||||||
this.modelBuilder = view.modelBuilder;
|
|
||||||
|
|
||||||
this.urlInputBox = this.modelBuilder.inputBox()
|
protected initializeFields(controllerInfo: ControllerInfo | undefined, password: string | undefined) {
|
||||||
.withProperties<azdata.InputBoxProperties>({
|
this.urlInputBox = this.modelBuilder.inputBox()
|
||||||
value: controllerInfo?.url,
|
.withProperties<azdata.InputBoxProperties>({
|
||||||
// If we have a model then we're editing an existing connection so don't let them modify the URL
|
value: controllerInfo?.url,
|
||||||
readOnly: !!controllerInfo
|
// If we have a model then we're editing an existing connection so don't let them modify the URL
|
||||||
}).component();
|
readOnly: !!controllerInfo
|
||||||
this.nameInputBox = this.modelBuilder.inputBox()
|
}).component();
|
||||||
.withProperties<azdata.InputBoxProperties>({
|
this.nameInputBox = this.modelBuilder.inputBox()
|
||||||
value: controllerInfo?.name
|
.withProperties<azdata.InputBoxProperties>({
|
||||||
}).component();
|
value: controllerInfo?.name
|
||||||
this.usernameInputBox = this.modelBuilder.inputBox()
|
}).component();
|
||||||
.withProperties<azdata.InputBoxProperties>({
|
this.usernameInputBox = this.modelBuilder.inputBox()
|
||||||
value: controllerInfo?.username
|
.withProperties<azdata.InputBoxProperties>({
|
||||||
}).component();
|
value: controllerInfo?.username
|
||||||
this.passwordInputBox = this.modelBuilder.inputBox()
|
}).component();
|
||||||
.withProperties<azdata.InputBoxProperties>({
|
this.passwordInputBox = this.modelBuilder.inputBox()
|
||||||
inputType: 'password',
|
.withProperties<azdata.InputBoxProperties>({
|
||||||
value: password
|
inputType: 'password',
|
||||||
})
|
value: password
|
||||||
.component();
|
}).component();
|
||||||
this.rememberPwCheckBox = this.modelBuilder.checkBox()
|
}
|
||||||
.withProperties<azdata.CheckBoxProperties>({
|
|
||||||
label: loc.rememberPassword,
|
protected completionPromise = new Deferred<ConnectToControllerDialogModel | undefined>();
|
||||||
checked: controllerInfo?.rememberPassword
|
protected id!: string;
|
||||||
}).component();
|
|
||||||
|
constructor(protected treeDataProvider: AzureArcTreeDataProvider, title: string) {
|
||||||
|
super();
|
||||||
|
this.dialog = azdata.window.createModelViewDialog(title);
|
||||||
|
}
|
||||||
|
|
||||||
|
public showDialog(controllerInfo?: ControllerInfo, password: string | undefined = undefined): azdata.window.Dialog {
|
||||||
|
this.id = controllerInfo?.id ?? uuid();
|
||||||
|
this.dialog.cancelButton.onClick(() => this.handleCancel());
|
||||||
|
this.dialog.registerContent(async (view) => {
|
||||||
|
this.modelBuilder = view.modelBuilder;
|
||||||
|
this.initializeFields(controllerInfo, password);
|
||||||
|
|
||||||
let formModel = this.modelBuilder.formContainer()
|
let formModel = this.modelBuilder.formContainer()
|
||||||
.withFormItems([{
|
.withFormItems([{
|
||||||
components: [
|
components: this.getComponents(),
|
||||||
{
|
|
||||||
component: this.urlInputBox,
|
|
||||||
title: loc.controllerUrl,
|
|
||||||
required: true
|
|
||||||
}, {
|
|
||||||
component: this.nameInputBox,
|
|
||||||
title: loc.controllerName,
|
|
||||||
required: false
|
|
||||||
}, {
|
|
||||||
component: this.usernameInputBox,
|
|
||||||
title: loc.username,
|
|
||||||
required: true
|
|
||||||
}, {
|
|
||||||
component: this.passwordInputBox,
|
|
||||||
title: loc.password,
|
|
||||||
required: true
|
|
||||||
}, {
|
|
||||||
component: this.rememberPwCheckBox,
|
|
||||||
title: ''
|
|
||||||
}
|
|
||||||
],
|
|
||||||
title: ''
|
title: ''
|
||||||
}]).withLayout({ width: '100%' }).component();
|
}]).withLayout({ width: '100%' }).component();
|
||||||
await view.initializeModel(formModel);
|
await view.initializeModel(formModel);
|
||||||
this.urlInputBox.focus();
|
await this.fieldToFocusOn().focus();
|
||||||
|
this.readonlyFields().forEach(f => f.readOnly = true);
|
||||||
this.initialized = true;
|
this.initialized = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
dialog.registerCloseValidator(async () => await this.validate());
|
this.dialog.registerCloseValidator(async () => await this.validate());
|
||||||
dialog.okButton.label = loc.connect;
|
this.dialog.okButton.label = loc.connect;
|
||||||
dialog.cancelButton.label = loc.cancel;
|
this.dialog.cancelButton.label = loc.cancel;
|
||||||
azdata.window.openDialog(dialog);
|
azdata.window.openDialog(this.dialog);
|
||||||
return dialog;
|
return this.dialog;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract async validate(): Promise<boolean>;
|
||||||
|
|
||||||
|
private handleCancel(): void {
|
||||||
|
this.completionPromise.resolve(undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
public waitForClose(): Promise<ConnectToControllerDialogModel | undefined> {
|
||||||
|
return this.completionPromise.promise;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ConnectToControllerDialog extends ControllerDialogBase {
|
||||||
|
protected rememberPwCheckBox!: azdata.CheckBoxComponent;
|
||||||
|
|
||||||
|
protected fieldToFocusOn() {
|
||||||
|
return this.urlInputBox;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getComponents() {
|
||||||
|
return [
|
||||||
|
...super.getComponents(),
|
||||||
|
{
|
||||||
|
component: this.rememberPwCheckBox,
|
||||||
|
title: ''
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected initializeFields(controllerInfo: ControllerInfo | undefined, password: string | undefined) {
|
||||||
|
super.initializeFields(controllerInfo, password);
|
||||||
|
this.rememberPwCheckBox = this.modelBuilder.checkBox()
|
||||||
|
.withProperties<azdata.CheckBoxProperties>({
|
||||||
|
label: loc.rememberPassword,
|
||||||
|
checked: controllerInfo?.rememberPassword
|
||||||
|
}).component();
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(treeDataProvider: AzureArcTreeDataProvider) {
|
||||||
|
super(treeDataProvider, loc.connectToController);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async validate(): Promise<boolean> {
|
public async validate(): Promise<boolean> {
|
||||||
@@ -120,32 +163,86 @@ export class ConnectToControllerDialog extends InitializingComponent {
|
|||||||
url = `${url}:30080`;
|
url = `${url}:30080`;
|
||||||
}
|
}
|
||||||
const controllerInfo: ControllerInfo = {
|
const controllerInfo: ControllerInfo = {
|
||||||
id: this._id,
|
id: this.id,
|
||||||
url: url,
|
url: url,
|
||||||
name: this.nameInputBox.value ?? '',
|
name: this.nameInputBox.value ?? '',
|
||||||
username: this.usernameInputBox.value,
|
username: this.usernameInputBox.value,
|
||||||
rememberPassword: this.rememberPwCheckBox.checked ?? false,
|
rememberPassword: this.rememberPwCheckBox.checked ?? false,
|
||||||
resources: []
|
resources: []
|
||||||
};
|
};
|
||||||
const controllerModel = new ControllerModel(this._treeDataProvider, controllerInfo, this.passwordInputBox.value);
|
const controllerModel = new ControllerModel(this.treeDataProvider, controllerInfo, this.passwordInputBox.value);
|
||||||
try {
|
try {
|
||||||
// Validate that we can connect to the controller, this also populates the controllerRegistration from the connection response.
|
// Validate that we can connect to the controller, this also populates the controllerRegistration from the connection response.
|
||||||
await controllerModel.refresh(false);
|
await controllerModel.refresh(false);
|
||||||
// default info.name to the name of the controller instance if the user did not specify their own and to a pre-canned default if for some weird reason controller endpoint returned instanceName is also not a valid value
|
// default info.name to the name of the controller instance if the user did not specify their own and to a pre-canned default if for some weird reason controller endpoint returned instanceName is also not a valid value
|
||||||
controllerModel.info.name = controllerModel.info.name || controllerModel.controllerConfig?.metadata.name || loc.defaultControllerName;
|
controllerModel.info.name = controllerModel.info.name || controllerModel.controllerConfig?.metadata.name || loc.defaultControllerName;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
vscode.window.showErrorMessage(loc.connectToControllerFailed(this.urlInputBox.value, err));
|
this.dialog.message = {
|
||||||
|
text: loc.connectToControllerFailed(this.urlInputBox.value, err),
|
||||||
|
level: azdata.window.MessageLevel.Error
|
||||||
|
};
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
this._completionPromise.resolve({ controllerModel: controllerModel, password: this.passwordInputBox.value });
|
this.completionPromise.resolve({ controllerModel: controllerModel, password: this.passwordInputBox.value });
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class PasswordToControllerDialog extends ControllerDialogBase {
|
||||||
|
|
||||||
|
constructor(treeDataProvider: AzureArcTreeDataProvider) {
|
||||||
|
super(treeDataProvider, loc.passwordToController);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fieldToFocusOn() {
|
||||||
|
return this.passwordInputBox;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected readonlyFields() {
|
||||||
|
return [
|
||||||
|
this.urlInputBox,
|
||||||
|
this.nameInputBox,
|
||||||
|
this.usernameInputBox
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public async validate(): Promise<boolean> {
|
||||||
|
if (!this.passwordInputBox.value) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const azdataApi = <azdataExt.IExtension>vscode.extensions.getExtension(azdataExt.extension.name)?.exports;
|
||||||
|
try {
|
||||||
|
await azdataApi.azdata.login(this.urlInputBox.value!, this.usernameInputBox.value!, this.passwordInputBox.value);
|
||||||
|
} catch (e) {
|
||||||
|
if (getErrorMessage(e).match(/Wrong username or password/i)) {
|
||||||
|
this.dialog.message = {
|
||||||
|
text: loc.invalidPassword,
|
||||||
|
level: azdata.window.MessageLevel.Error
|
||||||
|
};
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
this.dialog.message = {
|
||||||
|
text: loc.errorVerifyingPassword(e),
|
||||||
|
level: azdata.window.MessageLevel.Error
|
||||||
|
};
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const controllerInfo: ControllerInfo = {
|
||||||
|
id: this.id,
|
||||||
|
url: this.urlInputBox.value!,
|
||||||
|
name: this.nameInputBox.value!,
|
||||||
|
username: this.usernameInputBox.value!,
|
||||||
|
rememberPassword: false,
|
||||||
|
resources: []
|
||||||
|
};
|
||||||
|
const controllerModel = new ControllerModel(this.treeDataProvider, controllerInfo, this.passwordInputBox.value);
|
||||||
|
this.completionPromise.resolve({ controllerModel: controllerModel, password: this.passwordInputBox.value });
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleCancel(): void {
|
public showDialog(controllerInfo?: ControllerInfo): azdata.window.Dialog {
|
||||||
this._completionPromise.resolve(undefined);
|
const dialog = super.showDialog(controllerInfo);
|
||||||
}
|
dialog.okButton.label = loc.ok;
|
||||||
|
return dialog;
|
||||||
public waitForClose(): Promise<ConnectToControllerDialogModel | undefined> {
|
|
||||||
return this._completionPromise.promise;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import * as azdataExt from 'azdata-ext';
|
import * as azdataExt from 'azdata-ext';
|
||||||
|
import * as fs from 'fs';
|
||||||
import * as os from 'os';
|
import * as os from 'os';
|
||||||
import { SemVer } from 'semver';
|
import { SemVer } from 'semver';
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
@@ -13,7 +14,6 @@ import Logger from './common/logger';
|
|||||||
import { getErrorMessage, searchForCmd } from './common/utils';
|
import { getErrorMessage, searchForCmd } from './common/utils';
|
||||||
import { azdataAcceptEulaKey, azdataConfigSection, azdataFound, azdataHostname, azdataInstallKey, azdataReleaseJson, azdataUpdateKey, azdataUri, debugConfigKey, eulaAccepted, eulaUrl, microsoftPrivacyStatementUrl } from './constants';
|
import { azdataAcceptEulaKey, azdataConfigSection, azdataFound, azdataHostname, azdataInstallKey, azdataReleaseJson, azdataUpdateKey, azdataUri, debugConfigKey, eulaAccepted, eulaUrl, microsoftPrivacyStatementUrl } from './constants';
|
||||||
import * as loc from './localizedConstants';
|
import * as loc from './localizedConstants';
|
||||||
import * as fs from 'fs';
|
|
||||||
|
|
||||||
const enum AzdataDeployOption {
|
const enum AzdataDeployOption {
|
||||||
dontPrompt = 'dontPrompt',
|
dontPrompt = 'dontPrompt',
|
||||||
@@ -24,9 +24,6 @@ const enum AzdataDeployOption {
|
|||||||
* Interface for an object to interact with the azdata tool installed on the box.
|
* Interface for an object to interact with the azdata tool installed on the box.
|
||||||
*/
|
*/
|
||||||
export interface IAzdataTool extends azdataExt.IAzdataApi {
|
export interface IAzdataTool extends azdataExt.IAzdataApi {
|
||||||
path: string,
|
|
||||||
cachedVersion: SemVer
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executes azdata with the specified arguments (e.g. --version) and returns the result
|
* Executes azdata with the specified arguments (e.g. --version) and returns the result
|
||||||
* @param args The args to pass to azdata
|
* @param args The args to pass to azdata
|
||||||
@@ -39,9 +36,26 @@ export interface IAzdataTool extends azdataExt.IAzdataApi {
|
|||||||
* An object to interact with the azdata tool installed on the box.
|
* An object to interact with the azdata tool installed on the box.
|
||||||
*/
|
*/
|
||||||
export class AzdataTool implements IAzdataTool {
|
export class AzdataTool implements IAzdataTool {
|
||||||
public cachedVersion: SemVer;
|
|
||||||
constructor(public path: string, version: string) {
|
private _semVersion: SemVer;
|
||||||
this.cachedVersion = new SemVer(version);
|
constructor(private _path: string, version: string) {
|
||||||
|
this._semVersion = new SemVer(version);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The semVersion corresponding to this installation of azdata. version() method should have been run
|
||||||
|
* before fetching this value to ensure that correct value is returned. This is almost always correct unless
|
||||||
|
* Azdata has gotten reinstalled in the background after this IAzdataApi object was constructed.
|
||||||
|
*/
|
||||||
|
public getSemVersion() {
|
||||||
|
return this._semVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* gets the path where azdata tool is installed
|
||||||
|
*/
|
||||||
|
public getPath() {
|
||||||
|
return this._path;
|
||||||
}
|
}
|
||||||
|
|
||||||
public arc = {
|
public arc = {
|
||||||
@@ -110,8 +124,8 @@ export class AzdataTool implements IAzdataTool {
|
|||||||
* It also updates the cachedVersion property based on the return value from the tool.
|
* It also updates the cachedVersion property based on the return value from the tool.
|
||||||
*/
|
*/
|
||||||
public async version(): Promise<azdataExt.AzdataOutput<string>> {
|
public async version(): Promise<azdataExt.AzdataOutput<string>> {
|
||||||
const output = await executeAzdataCommand(`"${this.path}"`, ['--version']);
|
const output = await executeAzdataCommand(`"${this._path}"`, ['--version']);
|
||||||
this.cachedVersion = new SemVer(parseVersion(output.stdout));
|
this._semVersion = new SemVer(parseVersion(output.stdout));
|
||||||
return {
|
return {
|
||||||
logs: [],
|
logs: [],
|
||||||
stdout: output.stdout.split(os.EOL),
|
stdout: output.stdout.split(os.EOL),
|
||||||
@@ -122,7 +136,7 @@ export class AzdataTool implements IAzdataTool {
|
|||||||
|
|
||||||
public async executeCommand<R>(args: string[], additionalEnvVars?: { [key: string]: string }): Promise<azdataExt.AzdataOutput<R>> {
|
public async executeCommand<R>(args: string[], additionalEnvVars?: { [key: string]: string }): Promise<azdataExt.AzdataOutput<R>> {
|
||||||
try {
|
try {
|
||||||
const output = JSON.parse((await executeAzdataCommand(`"${this.path}"`, args.concat(['--output', 'json']), additionalEnvVars)).stdout);
|
const output = JSON.parse((await executeAzdataCommand(`"${this._path}"`, args.concat(['--output', 'json']), additionalEnvVars)).stdout);
|
||||||
return {
|
return {
|
||||||
logs: <string[]>output.log,
|
logs: <string[]>output.log,
|
||||||
stdout: <string[]>output.stdout,
|
stdout: <string[]>output.stdout,
|
||||||
@@ -136,12 +150,12 @@ export class AzdataTool implements IAzdataTool {
|
|||||||
// to get the correct stderr out. The actual value we get is something like
|
// to get the correct stderr out. The actual value we get is something like
|
||||||
// ERROR: { stderr: '...' }
|
// ERROR: { stderr: '...' }
|
||||||
// so we also need to trim off the start that isn't a valid JSON blob
|
// so we also need to trim off the start that isn't a valid JSON blob
|
||||||
err.stderr = JSON.parse(err.stderr.substring(err.stderr.indexOf('{'))).stderr;
|
err.stderr = JSON.parse(err.stderr.substring(err.stderr.indexOf('{'), err.stderr.indexOf('}') + 1)).stderr;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// it means this was probably some other generic error (such as command not being found)
|
// it means this was probably some other generic error (such as command not being found)
|
||||||
// check if azdata still exists if it does then rethrow the original error if not then emit a new specific error.
|
// check if azdata still exists if it does then rethrow the original error if not then emit a new specific error.
|
||||||
try {
|
try {
|
||||||
await fs.promises.access(this.path);
|
await fs.promises.access(this._path);
|
||||||
//this.path exists
|
//this.path exists
|
||||||
throw err; // rethrow the error
|
throw err; // rethrow the error
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -176,7 +190,7 @@ export async function findAzdata(): Promise<IAzdataTool> {
|
|||||||
try {
|
try {
|
||||||
const azdata = await findSpecificAzdata();
|
const azdata = await findSpecificAzdata();
|
||||||
await vscode.commands.executeCommand('setContext', azdataFound, true); // save a context key that azdata was found so that command for installing azdata is no longer available in commandPalette and that for updating it is.
|
await vscode.commands.executeCommand('setContext', azdataFound, true); // save a context key that azdata was found so that command for installing azdata is no longer available in commandPalette and that for updating it is.
|
||||||
Logger.log(loc.foundExistingAzdata(azdata.path, azdata.cachedVersion.raw));
|
Logger.log(loc.foundExistingAzdata(azdata.getPath(), azdata.getSemVersion().raw));
|
||||||
return azdata;
|
return azdata;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
Logger.log(loc.couldNotFindAzdata(err));
|
Logger.log(loc.couldNotFindAzdata(err));
|
||||||
@@ -262,12 +276,12 @@ export async function checkAndInstallAzdata(userRequested: boolean = false): Pro
|
|||||||
*/
|
*/
|
||||||
export async function checkAndUpdateAzdata(currentAzdata?: IAzdataTool, userRequested: boolean = false): Promise<boolean> {
|
export async function checkAndUpdateAzdata(currentAzdata?: IAzdataTool, userRequested: boolean = false): Promise<boolean> {
|
||||||
if (currentAzdata !== undefined) {
|
if (currentAzdata !== undefined) {
|
||||||
const newVersion = await discoverLatestAvailableAzdataVersion();
|
const newSemVersion = await discoverLatestAvailableAzdataVersion();
|
||||||
if (newVersion.compare(currentAzdata.cachedVersion) === 1) {
|
if (newSemVersion.compare(currentAzdata.getSemVersion()) === 1) {
|
||||||
Logger.log(loc.foundAzdataVersionToUpdateTo(newVersion.raw, currentAzdata.cachedVersion.raw));
|
Logger.log(loc.foundAzdataVersionToUpdateTo(newSemVersion.raw, currentAzdata.getSemVersion().raw));
|
||||||
return await promptToUpdateAzdata(newVersion.raw, userRequested);
|
return await promptToUpdateAzdata(newSemVersion.raw, userRequested);
|
||||||
} else {
|
} else {
|
||||||
Logger.log(loc.currentlyInstalledVersionIsLatest(currentAzdata.cachedVersion.raw));
|
Logger.log(loc.currentlyInstalledVersionIsLatest(currentAzdata.getSemVersion().raw));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Logger.log(loc.updateCheckSkipped);
|
Logger.log(loc.updateCheckSkipped);
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import * as loc from './localizedConstants';
|
|||||||
|
|
||||||
let localAzdata: IAzdataTool | undefined = undefined;
|
let localAzdata: IAzdataTool | undefined = undefined;
|
||||||
let eulaAccepted: boolean = false;
|
let eulaAccepted: boolean = false;
|
||||||
|
|
||||||
export async function activate(context: vscode.ExtensionContext): Promise<azdataExt.IExtension> {
|
export async function activate(context: vscode.ExtensionContext): Promise<azdataExt.IExtension> {
|
||||||
vscode.commands.registerCommand('azdata.acceptEula', async () => {
|
vscode.commands.registerCommand('azdata.acceptEula', async () => {
|
||||||
eulaAccepted = await promptForEula(context.globalState, true /* userRequested */);
|
eulaAccepted = await promptForEula(context.globalState, true /* userRequested */);
|
||||||
@@ -59,26 +58,27 @@ export async function activate(context: vscode.ExtensionContext): Promise<azdata
|
|||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
isEulaAccepted: () => !!context.globalState.get<boolean>(constants.eulaAccepted),
|
||||||
azdata: {
|
azdata: {
|
||||||
arc: {
|
arc: {
|
||||||
dc: {
|
dc: {
|
||||||
create: async (namespace: string, name: string, connectivityMode: string, resourceGroup: string, location: string, subscription: string, profileName?: string, storageClass?: string) => {
|
create: async (namespace: string, name: string, connectivityMode: string, resourceGroup: string, location: string, subscription: string, profileName?: string, storageClass?: string) => {
|
||||||
await throwIfNoAzdataOrEulaNotAccepted();
|
throwIfNoAzdataOrEulaNotAccepted();
|
||||||
return localAzdata!.arc.dc.create(namespace, name, connectivityMode, resourceGroup, location, subscription, profileName, storageClass);
|
return localAzdata!.arc.dc.create(namespace, name, connectivityMode, resourceGroup, location, subscription, profileName, storageClass);
|
||||||
},
|
},
|
||||||
endpoint: {
|
endpoint: {
|
||||||
list: async () => {
|
list: async () => {
|
||||||
await throwIfNoAzdataOrEulaNotAccepted();
|
throwIfNoAzdataOrEulaNotAccepted();
|
||||||
return localAzdata!.arc.dc.endpoint.list();
|
return localAzdata!.arc.dc.endpoint.list();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
config: {
|
config: {
|
||||||
list: async () => {
|
list: async () => {
|
||||||
await throwIfNoAzdataOrEulaNotAccepted();
|
throwIfNoAzdataOrEulaNotAccepted();
|
||||||
return localAzdata!.arc.dc.config.list();
|
return localAzdata!.arc.dc.config.list();
|
||||||
},
|
},
|
||||||
show: async () => {
|
show: async () => {
|
||||||
await throwIfNoAzdataOrEulaNotAccepted();
|
throwIfNoAzdataOrEulaNotAccepted();
|
||||||
return localAzdata!.arc.dc.config.show();
|
return localAzdata!.arc.dc.config.show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -86,11 +86,11 @@ export async function activate(context: vscode.ExtensionContext): Promise<azdata
|
|||||||
postgres: {
|
postgres: {
|
||||||
server: {
|
server: {
|
||||||
list: async () => {
|
list: async () => {
|
||||||
await throwIfNoAzdataOrEulaNotAccepted();
|
throwIfNoAzdataOrEulaNotAccepted();
|
||||||
return localAzdata!.arc.postgres.server.list();
|
return localAzdata!.arc.postgres.server.list();
|
||||||
},
|
},
|
||||||
show: async (name: string) => {
|
show: async (name: string) => {
|
||||||
await throwIfNoAzdataOrEulaNotAccepted();
|
throwIfNoAzdataOrEulaNotAccepted();
|
||||||
return localAzdata!.arc.postgres.server.show(name);
|
return localAzdata!.arc.postgres.server.show(name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -98,41 +98,53 @@ export async function activate(context: vscode.ExtensionContext): Promise<azdata
|
|||||||
sql: {
|
sql: {
|
||||||
mi: {
|
mi: {
|
||||||
delete: async (name: string) => {
|
delete: async (name: string) => {
|
||||||
await throwIfNoAzdataOrEulaNotAccepted();
|
throwIfNoAzdataOrEulaNotAccepted();
|
||||||
return localAzdata!.arc.sql.mi.delete(name);
|
return localAzdata!.arc.sql.mi.delete(name);
|
||||||
},
|
},
|
||||||
list: async () => {
|
list: async () => {
|
||||||
await throwIfNoAzdataOrEulaNotAccepted();
|
throwIfNoAzdataOrEulaNotAccepted();
|
||||||
return localAzdata!.arc.sql.mi.list();
|
return localAzdata!.arc.sql.mi.list();
|
||||||
},
|
},
|
||||||
show: async (name: string) => {
|
show: async (name: string) => {
|
||||||
await throwIfNoAzdataOrEulaNotAccepted();
|
throwIfNoAzdataOrEulaNotAccepted();
|
||||||
return localAzdata!.arc.sql.mi.show(name);
|
return localAzdata!.arc.sql.mi.show(name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
getPath: () => {
|
||||||
|
throwIfNoAzdata();
|
||||||
|
return localAzdata!.getPath();
|
||||||
|
},
|
||||||
login: async (endpoint: string, username: string, password: string) => {
|
login: async (endpoint: string, username: string, password: string) => {
|
||||||
await throwIfNoAzdataOrEulaNotAccepted();
|
throwIfNoAzdataOrEulaNotAccepted();
|
||||||
return localAzdata!.login(endpoint, username, password);
|
return localAzdata!.login(endpoint, username, password);
|
||||||
},
|
},
|
||||||
|
getSemVersion: () => {
|
||||||
|
throwIfNoAzdata();
|
||||||
|
return localAzdata!.getSemVersion();
|
||||||
|
},
|
||||||
version: async () => {
|
version: async () => {
|
||||||
await throwIfNoAzdataOrEulaNotAccepted();
|
throwIfNoAzdata();
|
||||||
return localAzdata!.version();
|
return localAzdata!.version();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function throwIfNoAzdataOrEulaNotAccepted(): Promise<void> {
|
function throwIfNoAzdataOrEulaNotAccepted(): void {
|
||||||
if (!localAzdata) {
|
throwIfNoAzdata();
|
||||||
Logger.log(loc.noAzdata);
|
|
||||||
throw new Error(loc.noAzdata);
|
|
||||||
}
|
|
||||||
if (!eulaAccepted) {
|
if (!eulaAccepted) {
|
||||||
Logger.log(loc.eulaNotAccepted);
|
Logger.log(loc.eulaNotAccepted);
|
||||||
throw new Error(loc.eulaNotAccepted);
|
throw new Error(loc.eulaNotAccepted);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function throwIfNoAzdata() {
|
||||||
|
if (!localAzdata) {
|
||||||
|
Logger.log(loc.noAzdata);
|
||||||
|
throw new Error(loc.noAzdata);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function deactivate(): void { }
|
export function deactivate(): void { }
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export const decline = localize('azdata.decline', "Decline");
|
|||||||
export const doNotAskAgain = localize('azdata.doNotAskAgain', "Don't Ask Again");
|
export const doNotAskAgain = localize('azdata.doNotAskAgain', "Don't Ask Again");
|
||||||
export const askLater = localize('azdata.askLater', "Ask Later");
|
export const askLater = localize('azdata.askLater', "Ask Later");
|
||||||
export const downloadingTo = (name: string, location: string): string => localize('azdata.downloadingTo', "Downloading {0} to {1}", name, location);
|
export const downloadingTo = (name: string, location: string): string => localize('azdata.downloadingTo', "Downloading {0} to {1}", name, location);
|
||||||
export const executingCommand = (command: string, args: string[]): string => localize('azdata.executingCommand', "Executing command \"{0} {1}\"", command, args?.join(' '));
|
export const executingCommand = (command: string, args: string[]): string => localize('azdata.executingCommand', "Executing command: '{0} {1}'", command, args?.join(' '));
|
||||||
export const stdoutOutput = (stdout: string): string => localize('azdata.stdoutOutput', "stdout: {0}", stdout);
|
export const stdoutOutput = (stdout: string): string => localize('azdata.stdoutOutput', "stdout: {0}", stdout);
|
||||||
export const stderrOutput = (stderr: string): string => localize('azdata.stderrOutput', "stderr: {0}", stderr);
|
export const stderrOutput = (stderr: string): string => localize('azdata.stderrOutput', "stderr: {0}", stderr);
|
||||||
export const checkingLatestAzdataVersion = localize('azdata.checkingLatestAzdataVersion', "Checking for latest available version of azdata");
|
export const checkingLatestAzdataVersion = localize('azdata.checkingLatestAzdataVersion', "Checking for latest available version of azdata");
|
||||||
@@ -56,11 +56,11 @@ export const userRequestedInstall = localize('azdata.userRequestedInstall', "Use
|
|||||||
export const userRequestedUpdate = localize('azdata.userRequestedUpdate', "User requested to update Azure Data CLI using 'Azure Data CLI: Check for Update' command");
|
export const userRequestedUpdate = localize('azdata.userRequestedUpdate', "User requested to update Azure Data CLI using 'Azure Data CLI: Check for Update' command");
|
||||||
export const userRequestedAcceptEula = localize('azdata.acceptEula', "User requested to be prompted for accepting EULA by invoking 'Azure Data CLI: Accept EULA' command");
|
export const userRequestedAcceptEula = localize('azdata.acceptEula', "User requested to be prompted for accepting EULA by invoking 'Azure Data CLI: Accept EULA' command");
|
||||||
export const updateCheckSkipped = localize('azdata.updateCheckSkipped', "No check for new Azure Data CLI version availability performed as Azure Data CLI was not found to be installed");
|
export const updateCheckSkipped = localize('azdata.updateCheckSkipped', "No check for new Azure Data CLI version availability performed as Azure Data CLI was not found to be installed");
|
||||||
export const eulaNotAccepted = localize('azdata.eulaNotAccepted', "Microsoft Privacy statement and Azure Data CLI license terms have not been accepted, [accept the EULA](command:azdata.acceptEula) to use the features that require it.");
|
export const eulaNotAccepted = localize('azdata.eulaNotAccepted', "Microsoft Privacy statement and Azure Data CLI license terms have not been accepted. Execute the command: [Azure Data CLI: Accept EULA](command:azdata.acceptEula) to accept EULA to enable the features that requires Azure Data CLI.");
|
||||||
export const installManually = (expectedVersion: string, instructionsUrl: string) => localize('azdata.installManually', "Azure Data CLI is not installed. Version: {0} needs to be installed or some features may not work. Please install it manually using these [instructions]({1}). Restart ADS when installation is done.", expectedVersion, instructionsUrl);
|
export const installManually = (expectedVersion: string, instructionsUrl: string) => localize('azdata.installManually', "Azure Data CLI is not installed. Version: {0} needs to be installed or some features may not work. Please install it manually using these [instructions]({1}). Restart ADS when installation is done.", expectedVersion, instructionsUrl);
|
||||||
export const installCorrectVersionManually = (currentVersion: string, expectedVersion: string, instructionsUrl: string) => localize('azdata.installCorrectVersionManually', "Azure Data CLI version: {0} is installed, version: {1} needs to be installed or some features may not work. Please uninstall the current version and then install the correct version manually using these [instructions]({2}). Restart ADS when installation is done.", currentVersion, expectedVersion, instructionsUrl);
|
export const installCorrectVersionManually = (currentVersion: string, expectedVersion: string, instructionsUrl: string) => localize('azdata.installCorrectVersionManually', "Azure Data CLI version: {0} is installed, version: {1} needs to be installed or some features may not work. Please uninstall the current version and then install the correct version manually using these [instructions]({2}). Restart ADS when installation is done.", currentVersion, expectedVersion, instructionsUrl);
|
||||||
export const promptForEula = (privacyStatementUrl: string, eulaUrl: string) => localize('azdata.promptForEula', "It is required to accept the [Microsoft Privacy Statement]({0}) and the [Azure Data CLI license terms]({1}) to use this extension. Declining this will result in some features not working.", privacyStatementUrl, eulaUrl);
|
export const promptForEula = (privacyStatementUrl: string, eulaUrl: string) => localize('azdata.promptForEula', "It is required to accept the [Microsoft Privacy Statement]({0}) and the [Azure Data CLI license terms]({1}) to use this extension. Declining this will result in some features not working.", privacyStatementUrl, eulaUrl);
|
||||||
export const promptForEulaLog = (privacyStatementUrl: string, eulaUrl: string) => promptLog(promptForEula(privacyStatementUrl, eulaUrl));
|
export const promptForEulaLog = (privacyStatementUrl: string, eulaUrl: string) => promptLog(promptForEula(privacyStatementUrl, eulaUrl));
|
||||||
export const userResponseToEulaPrompt = (response: string | undefined) => localize('azdata.promptForEulaResponse', "User response to Eula prompt: {0}", response);
|
export const userResponseToEulaPrompt = (response: string | undefined) => localize('azdata.promptForEulaResponse', "User response to EULA prompt: {0}", response);
|
||||||
export const eulaAcceptedStateOnStartup = (eulaAccepted: boolean) => localize('azdata.eulaAcceptedStateOnStartup', "eulaAccepted state on startup: {0}", eulaAccepted);
|
export const eulaAcceptedStateOnStartup = (eulaAccepted: boolean) => localize('azdata.eulaAcceptedStateOnStartup', "'EULA Accepted' state on startup: {0}", eulaAccepted);
|
||||||
export const eulaAcceptedStateUpdated = (eulaAccepted: boolean) => localize('azdata.eulaAcceptedStateUpdated', "Updated 'eulaAccepted' state to: {0}", eulaAccepted);
|
export const eulaAcceptedStateUpdated = (eulaAccepted: boolean) => localize('azdata.eulaAcceptedStateUpdated', "Updated 'EULA Accepted' state to: {0}", eulaAccepted);
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { SemVer } from 'semver';
|
|
||||||
import * as should from 'should';
|
import * as should from 'should';
|
||||||
import * as sinon from 'sinon';
|
import * as sinon from 'sinon';
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
@@ -12,10 +11,10 @@ import * as azdata from '../azdata';
|
|||||||
import * as childProcess from '../common/childProcess';
|
import * as childProcess from '../common/childProcess';
|
||||||
import { HttpClient } from '../common/httpClient';
|
import { HttpClient } from '../common/httpClient';
|
||||||
import * as utils from '../common/utils';
|
import * as utils from '../common/utils';
|
||||||
import * as loc from '../localizedConstants';
|
|
||||||
import * as constants from '../constants';
|
import * as constants from '../constants';
|
||||||
|
import * as loc from '../localizedConstants';
|
||||||
|
|
||||||
const oldAzdataMock = <azdata.AzdataTool>{ path: '/path/to/azdata', cachedVersion: new SemVer('0.0.0') };
|
const oldAzdataMock = new azdata.AzdataTool('/path/to/azdata', '0.0.0');
|
||||||
const releaseJson = {
|
const releaseJson = {
|
||||||
win32: {
|
win32: {
|
||||||
'version': '9999.999.999',
|
'version': '9999.999.999',
|
||||||
|
|||||||
14
extensions/azdata/src/typings/azdata-ext.d.ts
vendored
14
extensions/azdata/src/typings/azdata-ext.d.ts
vendored
@@ -4,6 +4,8 @@
|
|||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
declare module 'azdata-ext' {
|
declare module 'azdata-ext' {
|
||||||
|
import { SemVer } from 'semver';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Covers defining what the azdata extension exports to other extensions
|
* Covers defining what the azdata extension exports to other extensions
|
||||||
*
|
*
|
||||||
@@ -80,7 +82,7 @@ declare module 'azdata-ext' {
|
|||||||
location: string, // "eastus2euap",
|
location: string, // "eastus2euap",
|
||||||
resourceGroup: string, // "my-rg",
|
resourceGroup: string, // "my-rg",
|
||||||
subscription: string, // "a5082b29-8c6e-4bc5-8ddd-8ef39dfebc39"
|
subscription: string, // "a5082b29-8c6e-4bc5-8ddd-8ef39dfebc39"
|
||||||
},
|
},
|
||||||
controller: {
|
controller: {
|
||||||
'enableBilling': string, // "True"
|
'enableBilling': string, // "True"
|
||||||
'logs.rotation.days': string, // "7"
|
'logs.rotation.days': string, // "7"
|
||||||
@@ -224,12 +226,20 @@ declare module 'azdata-ext' {
|
|||||||
show(name: string): Promise<AzdataOutput<SqlMiShowResult>>
|
show(name: string): Promise<AzdataOutput<SqlMiShowResult>>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
getPath(): string,
|
||||||
login(endpoint: string, username: string, password: string): Promise<AzdataOutput<any>>,
|
login(endpoint: string, username: string, password: string): Promise<AzdataOutput<any>>,
|
||||||
|
/**
|
||||||
|
* The semVersion corresponding to this installation of azdata. version() method should have been run
|
||||||
|
* before fetching this value to ensure that correct value is returned. This is almost always correct unless
|
||||||
|
* Azdata has gotten reinstalled in the background after this IAzdataApi object was constructed.
|
||||||
|
*/
|
||||||
|
getSemVersion(): SemVer,
|
||||||
version(): Promise<AzdataOutput<string>>
|
version(): Promise<AzdataOutput<string>>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IExtension {
|
export interface IExtension {
|
||||||
azdata: IAzdataApi;
|
azdata: IAzdataApi;
|
||||||
|
isEulaAccepted(): boolean;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,16 +106,19 @@ export class SummaryPage extends ImportPage {
|
|||||||
|
|
||||||
private async handleImport(): Promise<boolean> {
|
private async handleImport(): Promise<boolean> {
|
||||||
let changeColumnResults = [];
|
let changeColumnResults = [];
|
||||||
this.model.proseColumns.forEach((val, i, arr) => {
|
let i = 0;
|
||||||
|
|
||||||
|
for (let val of this.model.proseColumns) {
|
||||||
let columnChangeParams = {
|
let columnChangeParams = {
|
||||||
index: i,
|
index: i++,
|
||||||
newName: val.columnName,
|
newName: val.columnName,
|
||||||
newDataType: val.dataType,
|
newDataType: val.dataType,
|
||||||
newNullable: val.nullable,
|
newNullable: val.nullable,
|
||||||
newInPrimaryKey: val.primaryKey
|
newInPrimaryKey: val.primaryKey
|
||||||
};
|
};
|
||||||
changeColumnResults.push(this.provider.sendChangeColumnSettingsRequest(columnChangeParams));
|
const changeColumnResult = await this.provider.sendChangeColumnSettingsRequest(columnChangeParams);
|
||||||
});
|
changeColumnResults.push(changeColumnResult);
|
||||||
|
}
|
||||||
|
|
||||||
let result: InsertDataResponse;
|
let result: InsertDataResponse;
|
||||||
let err;
|
let err;
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ suite('Dacpac integration test suite', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const bacpac1: string = path.join(__dirname, '..', '..', 'testData', 'Database1.bacpac');
|
const bacpac1: string = path.join(__dirname, '..', '..', 'testData', 'Database1.bacpac');
|
||||||
test('Import and export bacpac', async function () {
|
test('Import and export bacpac @UNSTABLE@', async function () {
|
||||||
const server = await getStandaloneServer();
|
const server = await getStandaloneServer();
|
||||||
await utils.connectToServer(server);
|
await utils.connectToServer(server);
|
||||||
|
|
||||||
|
|||||||
@@ -186,13 +186,13 @@ export class CurrentModelsTable extends ModelViewBase implements IDataComponent<
|
|||||||
|
|
||||||
public async onLoading(): Promise<void> {
|
public async onLoading(): Promise<void> {
|
||||||
if (this._loader) {
|
if (this._loader) {
|
||||||
await this._loader.updateProperties({ loading: true });
|
this._loader.loading = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async onLoaded(): Promise<void> {
|
public async onLoaded(): Promise<void> {
|
||||||
if (this._loader) {
|
if (this._loader) {
|
||||||
await this._loader.updateProperties({ loading: false });
|
this._loader.loading = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -99,6 +99,11 @@ export class ModelImportLocationPage extends ModelViewBase implements IPageView,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.importTable && this._labelComponent) {
|
if (this.importTable && this._labelComponent) {
|
||||||
|
|
||||||
|
// Add table name to the models imported.
|
||||||
|
// Since Table name is picked last as per new flow this hasn't been set yet.
|
||||||
|
this.modelsViewData?.forEach(x => x.targetImportTable = this.importTable);
|
||||||
|
|
||||||
if (!this.validateImportTableName()) {
|
if (!this.validateImportTableName()) {
|
||||||
this._labelComponent.value = constants.selectModelsTableMessage;
|
this._labelComponent.value = constants.selectModelsTableMessage;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"downloadUrl": "https://github.com/Microsoft/sqltoolsservice/releases/download/v{#version#}/microsoft.sqltools.servicelayer-{#fileName#}",
|
"downloadUrl": "https://github.com/Microsoft/sqltoolsservice/releases/download/v{#version#}/microsoft.sqltools.servicelayer-{#fileName#}",
|
||||||
"version": "3.0.0-release.29",
|
"version": "3.0.0-release.35",
|
||||||
"downloadFileNames": {
|
"downloadFileNames": {
|
||||||
"Windows_86": "win-x86-netcoreapp3.1.zip",
|
"Windows_86": "win-x86-netcoreapp3.1.zip",
|
||||||
"Windows_64": "win-x64-netcoreapp3.1.zip",
|
"Windows_64": "win-x64-netcoreapp3.1.zip",
|
||||||
|
|||||||
@@ -26,6 +26,9 @@
|
|||||||
"# Create Jupyter Book\n",
|
"# Create Jupyter Book\n",
|
||||||
" <span style=\"color:red\">**Note**:</span> **Windows users** using **Python 3.8** might be impacted by this known [issue](https://jupyterbook.org/advanced/advanced.html#working-on-windows) on the latest **jupyter book** version.\n",
|
" <span style=\"color:red\">**Note**:</span> **Windows users** using **Python 3.8** might be impacted by this known [issue](https://jupyterbook.org/advanced/advanced.html#working-on-windows) on the latest **jupyter book** version.\n",
|
||||||
"\n",
|
"\n",
|
||||||
|
" <span style=\"color:red\">**Note**:</span> In Jupyter Book versions above 0.7.0, verify that the names of the files do not start with a **number**. Due to the new design of jupyter book, the prefix number will be removed from the file name in the Table of Contents. Please refer to this [section](https://jupyterbook.org/customize/toc.html#automatically-generate-your-toc-yml-file) of the documentation for more information.\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
"This is a notebook to create a [Jupyter Book](https://jupyterbook.org/intro.html), which is an organized collections of Jupyter notebooks.\n",
|
"This is a notebook to create a [Jupyter Book](https://jupyterbook.org/intro.html), which is an organized collections of Jupyter notebooks.\n",
|
||||||
"\n",
|
"\n",
|
||||||
"The best way to use this notebook is by clicking Run all in the toolbar above. This will run all the cells in a step-by-step order so that you can create your Jupyter Book.\n",
|
"The best way to use this notebook is by clicking Run all in the toolbar above. This will run all the cells in a step-by-step order so that you can create your Jupyter Book.\n",
|
||||||
@@ -39,24 +42,38 @@
|
|||||||
"azdata_cell_guid": "97541c75-b1c9-4e4c-9f0a-f93df4a550ef"
|
"azdata_cell_guid": "97541c75-b1c9-4e4c-9f0a-f93df4a550ef"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"source": [
|
||||||
|
"import sys\n",
|
||||||
|
"\n",
|
||||||
|
"def getJupyterBookVersion():\n",
|
||||||
|
" cmd = f'{sys.executable} -m pip show jupyter-book'\n",
|
||||||
|
" cmdOutput = !{cmd}\n",
|
||||||
|
" if len(cmdOutput) <= 1:\n",
|
||||||
|
" !pip install jupyter-book\n",
|
||||||
|
" cmd = f'{sys.executable} -m pip show jupyter-book'\n",
|
||||||
|
" cmdOutput = !{cmd}\n",
|
||||||
|
" for x in cmdOutput:\n",
|
||||||
|
" if 'version' in x.lower():\n",
|
||||||
|
" version_str = tuple(x.split(': ')[1].replace('.', ''))\n",
|
||||||
|
" version = tuple(int(x) for x in version_str)\n",
|
||||||
|
" return version, version_str"
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"azdata_cell_guid": "4bc12e83-cd03-425b-ad82-3ea9e0393b2c",
|
||||||
|
"tags": [
|
||||||
|
"hide_input"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"outputs": [],
|
||||||
|
"execution_count": null
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"source": [
|
"source": [
|
||||||
"import sys\r\n",
|
"import sys\r\n",
|
||||||
"\r\n",
|
"\r\n",
|
||||||
"def getJupyterBookVersion():\r\n",
|
|
||||||
" cmd = f'{sys.executable} -m pip show jupyter-book'\r\n",
|
|
||||||
" cmdOutput = !{cmd}\r\n",
|
|
||||||
" if len(cmdOutput) <= 1:\r\n",
|
|
||||||
" !pip install jupyter-book\r\n",
|
|
||||||
" cmd = f'{sys.executable} -m pip show jupyter-book'\r\n",
|
|
||||||
" cmdOutput = !{cmd}\r\n",
|
|
||||||
" for x in cmdOutput:\r\n",
|
|
||||||
" if 'version' in x.lower():\r\n",
|
|
||||||
" version_str = tuple(x.split(': ')[1].replace('.', ''))\r\n",
|
|
||||||
" version = tuple(int(x) for x in version_str)\r\n",
|
|
||||||
" return version, version_str\r\n",
|
|
||||||
"\r\n",
|
|
||||||
"try:\r\n",
|
"try:\r\n",
|
||||||
" version, version_str = getJupyterBookVersion()\r\n",
|
" version, version_str = getJupyterBookVersion()\r\n",
|
||||||
" if version:\r\n",
|
" if version:\r\n",
|
||||||
@@ -64,7 +81,7 @@
|
|||||||
" print(f'Jupyter-book version {display_version} is already installed!')\r\n",
|
" print(f'Jupyter-book version {display_version} is already installed!')\r\n",
|
||||||
" use_current = input(f'Would you like to create a book using the current installed version [{display_version}] of jupyter-book? [y/n]').lower()\r\n",
|
" use_current = input(f'Would you like to create a book using the current installed version [{display_version}] of jupyter-book? [y/n]').lower()\r\n",
|
||||||
"\r\n",
|
"\r\n",
|
||||||
" while use_current not in ['yes', 'y', 'no', 'n']:\r\n",
|
" while use_current not in ['','yes', 'y', 'no', 'n']:\r\n",
|
||||||
" use_current = input(f'Would you like to create a book using the current installed version [{display_version}] of jupyter-book? [y/n]').lower()\r\n",
|
" use_current = input(f'Would you like to create a book using the current installed version [{display_version}] of jupyter-book? [y/n]').lower()\r\n",
|
||||||
" if use_current in ['no', 'n']:\r\n",
|
" if use_current in ['no', 'n']:\r\n",
|
||||||
" install_version = input(f'Please enter the version you would like to use (eg. 0.7.4)')\r\n",
|
" install_version = input(f'Please enter the version you would like to use (eg. 0.7.4)')\r\n",
|
||||||
@@ -138,7 +155,7 @@
|
|||||||
" if platform in ['linux', 'darwin']:\r\n",
|
" if platform in ['linux', 'darwin']:\r\n",
|
||||||
" subprocess.check_call(['cp', '-r',content_folder, book_name])\r\n",
|
" subprocess.check_call(['cp', '-r',content_folder, book_name])\r\n",
|
||||||
" else:\r\n",
|
" else:\r\n",
|
||||||
" subprocess.check_call(['xcopy', content_folder, book_name])\r\n",
|
" subprocess.check_call(['xcopy', content_folder, book_name, '/E', '/H'])\r\n",
|
||||||
" !jupyter-book toc \"$book_name\"\r\n",
|
" !jupyter-book toc \"$book_name\"\r\n",
|
||||||
"except Exception as e:\r\n",
|
"except Exception as e:\r\n",
|
||||||
" raise SystemExit(str(e))"
|
" raise SystemExit(str(e))"
|
||||||
@@ -328,6 +345,8 @@
|
|||||||
" if k == 'file':\n",
|
" if k == 'file':\n",
|
||||||
" new_data.append({k : data[k]})\n",
|
" new_data.append({k : data[k]})\n",
|
||||||
" elif k == 'sections':\n",
|
" elif k == 'sections':\n",
|
||||||
|
" new_data[-1].update({'numbered' : 'false'})\n",
|
||||||
|
" new_data[-1].update({'expand_sections' : 'true'})\n",
|
||||||
" new_data[-1].update({k : data[k]})\n",
|
" new_data[-1].update({k : data[k]})\n",
|
||||||
" data = new_data\n",
|
" data = new_data\n",
|
||||||
" for k in data:\n",
|
" for k in data:\n",
|
||||||
@@ -407,7 +426,9 @@
|
|||||||
],
|
],
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"azdata_cell_guid": "bd2fe173-66ce-48b3-8dc3-c4d7560953c8",
|
"azdata_cell_guid": "bd2fe173-66ce-48b3-8dc3-c4d7560953c8",
|
||||||
"tags": []
|
"tags": [
|
||||||
|
"hide_input"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
"execution_count": null
|
"execution_count": null
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ export class BookTreeItem extends vscode.TreeItem {
|
|||||||
this.tooltip = `${this._uri}`;
|
this.tooltip = `${this._uri}`;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.tooltip = this.book.type === BookTreeItemType.Book ? this.book.root : this.book.contentPath;
|
this.tooltip = this.book.type === BookTreeItemType.Book ? (this.book.version === BookVersion.v1 ? path.join(this.book.root, content) : this.book.root) : this.book.contentPath;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import { Deferred } from '../common/promise';
|
|||||||
import { IBookTrustManager, BookTrustManager } from './bookTrustManager';
|
import { IBookTrustManager, BookTrustManager } from './bookTrustManager';
|
||||||
import * as loc from '../common/localizedConstants';
|
import * as loc from '../common/localizedConstants';
|
||||||
import * as glob from 'fast-glob';
|
import * as glob from 'fast-glob';
|
||||||
import { isNullOrUndefined } from 'util';
|
|
||||||
import { IJupyterBookSectionV2, IJupyterBookSectionV1 } from '../contracts/content';
|
import { IJupyterBookSectionV2, IJupyterBookSectionV1 } from '../contracts/content';
|
||||||
import { debounce, getPinnedNotebooks } from '../common/utils';
|
import { debounce, getPinnedNotebooks } from '../common/utils';
|
||||||
import { IBookPinManager, BookPinManager } from './bookPinManager';
|
import { IBookPinManager, BookPinManager } from './bookPinManager';
|
||||||
@@ -313,7 +312,7 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
|
|||||||
if (shouldReveal || this._bookViewer?.visible) {
|
if (shouldReveal || this._bookViewer?.visible) {
|
||||||
bookItem = notebookPath ? await this.findAndExpandParentNode(notebookPath) : undefined;
|
bookItem = notebookPath ? await this.findAndExpandParentNode(notebookPath) : undefined;
|
||||||
// Select + focus item in viewlet if books viewlet is already open, or if we pass in variable
|
// Select + focus item in viewlet if books viewlet is already open, or if we pass in variable
|
||||||
if (bookItem) {
|
if (bookItem?.contextValue !== 'pinnedNotebook') {
|
||||||
// Note: 3 is the maximum number of levels that the vscode APIs let you expand to
|
// Note: 3 is the maximum number of levels that the vscode APIs let you expand to
|
||||||
await this._bookViewer.reveal(bookItem, { select: true, focus: true, expand: true });
|
await this._bookViewer.reveal(bookItem, { select: true, focus: true, expand: true });
|
||||||
}
|
}
|
||||||
@@ -492,7 +491,7 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
|
|||||||
let notebookConfig = vscode.workspace.getConfiguration(constants.notebookConfigKey);
|
let notebookConfig = vscode.workspace.getConfiguration(constants.notebookConfigKey);
|
||||||
let maxDepth = notebookConfig[constants.maxBookSearchDepth];
|
let maxDepth = notebookConfig[constants.maxBookSearchDepth];
|
||||||
// Use default value if user enters an invalid value
|
// Use default value if user enters an invalid value
|
||||||
if (isNullOrUndefined(maxDepth) || maxDepth < 0) {
|
if (maxDepth === null || maxDepth === undefined || maxDepth < 0) {
|
||||||
maxDepth = 10;
|
maxDepth = 10;
|
||||||
} else if (maxDepth === 0) { // No limit of search depth if user enters 0
|
} else if (maxDepth === 0) { // No limit of search depth if user enters 0
|
||||||
maxDepth = undefined;
|
maxDepth = undefined;
|
||||||
|
|||||||
24
extensions/resource-deployment/images/azure-sql.svg
Normal file
24
extensions/resource-deployment/images/azure-sql.svg
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<svg id="a96792b7-ce28-4ca3-9767-4e065ef4820f" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18" width="18" height="18">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="ef16bf9d-a8b6-4181-b6cd-66fc5203f956" x1="2.59" y1="10.16" x2="15.41" y2="10.16" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0" stop-color="#005ba1" />
|
||||||
|
<stop offset="0.07" stop-color="#0060a9" />
|
||||||
|
<stop offset="0.36" stop-color="#0071c8" />
|
||||||
|
<stop offset="0.52" stop-color="#0078d4" />
|
||||||
|
<stop offset="0.64" stop-color="#0074cd" />
|
||||||
|
<stop offset="0.82" stop-color="#006abb" />
|
||||||
|
<stop offset="1" stop-color="#005ba1" />
|
||||||
|
</linearGradient>
|
||||||
|
<radialGradient id="bf3846c3-4d74-4743-ab9a-f334c248bd92" cx="9.36" cy="10.57" r="7.07" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0" stop-color="#f2f2f2" />
|
||||||
|
<stop offset="0.58" stop-color="#eee" />
|
||||||
|
<stop offset="1" stop-color="#e6e6e6" />
|
||||||
|
</radialGradient>
|
||||||
|
</defs>
|
||||||
|
<title>Icon-databases-130</title>
|
||||||
|
<path d="M9,5.14c-3.54,0-6.41-1-6.41-2.32V15.18c0,1.27,2.82,2.3,6.32,2.32H9c3.54,0,6.41-1,6.41-2.32V2.82C15.41,4.11,12.54,5.14,9,5.14Z" fill="url(#ef16bf9d-a8b6-4181-b6cd-66fc5203f956)" />
|
||||||
|
<path d="M15.41,2.82c0,1.29-2.87,2.32-6.41,2.32s-6.41-1-6.41-2.32S5.46.5,9,.5s6.41,1,6.41,2.32" fill="#e8e8e8" />
|
||||||
|
<path d="M13.92,2.63c0,.82-2.21,1.48-4.92,1.48S4.08,3.45,4.08,2.63,6.29,1.16,9,1.16s4.92.66,4.92,1.47" fill="#50e6ff" />
|
||||||
|
<path d="M9,3a11.55,11.55,0,0,0-3.89.57A11.42,11.42,0,0,0,9,4.11a11.15,11.15,0,0,0,3.89-.58A11.84,11.84,0,0,0,9,3Z" fill="#198ab3" />
|
||||||
|
<path d="M12.9,11.4V8H12v4.13h2.46V11.4ZM5.76,9.73a1.83,1.83,0,0,1-.51-.31.44.44,0,0,1-.12-.32.34.34,0,0,1,.15-.3.68.68,0,0,1,.42-.12,1.62,1.62,0,0,1,1,.29V8.11a2.58,2.58,0,0,0-1-.16,1.64,1.64,0,0,0-1.09.34,1.08,1.08,0,0,0-.42.89c0,.51.32.91,1,1.21a2.88,2.88,0,0,1,.62.36.42.42,0,0,1,.15.32.38.38,0,0,1-.16.31.81.81,0,0,1-.45.11,1.66,1.66,0,0,1-1.09-.42V12a2.17,2.17,0,0,0,1.07.24,1.88,1.88,0,0,0,1.18-.33A1.08,1.08,0,0,0,6.84,11a1.05,1.05,0,0,0-.25-.7A2.42,2.42,0,0,0,5.76,9.73ZM11,11.32a2.34,2.34,0,0,0,.33-1.26A2.32,2.32,0,0,0,11,9a1.81,1.81,0,0,0-.7-.75,2,2,0,0,0-1-.26,2.11,2.11,0,0,0-1.08.27A1.86,1.86,0,0,0,7.49,9a2.46,2.46,0,0,0-.26,1.14,2.26,2.26,0,0,0,.24,1,1.76,1.76,0,0,0,.69.74,2.06,2.06,0,0,0,1,.3l.86,1h1.21L10,12.08A1.79,1.79,0,0,0,11,11.32ZM10,11.07a.94.94,0,0,1-.76.35.92.92,0,0,1-.76-.36,1.52,1.52,0,0,1-.29-1,1.53,1.53,0,0,1,.29-1,1,1,0,0,1,.78-.37.87.87,0,0,1,.75.37,1.62,1.62,0,0,1,.27,1A1.46,1.46,0,0,1,10,11.07Z" fill="url(#bf3846c3-4d74-4743-ab9a-f334c248bd92)" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.5 KiB |
@@ -258,7 +258,7 @@
|
|||||||
"name": "azure-cli"
|
"name": "azure-cli"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "azdata",
|
"name": "azdata-old",
|
||||||
"version": "20.0.0"
|
"version": "20.0.0"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -275,7 +275,7 @@
|
|||||||
"version": "1.13.0"
|
"version": "1.13.0"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "azdata",
|
"name": "azdata-old",
|
||||||
"version": "20.0.0"
|
"version": "20.0.0"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -292,7 +292,7 @@
|
|||||||
"version": "1.13.0"
|
"version": "1.13.0"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "azdata",
|
"name": "azdata-old",
|
||||||
"version": "20.0.0"
|
"version": "20.0.0"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -309,7 +309,7 @@
|
|||||||
"version": "1.13.0"
|
"version": "1.13.0"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "azdata",
|
"name": "azdata-old",
|
||||||
"version": "20.0.0"
|
"version": "20.0.0"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -326,7 +326,7 @@
|
|||||||
"version": "1.13.0"
|
"version": "1.13.0"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "azdata",
|
"name": "azdata-old",
|
||||||
"version": "20.0.0"
|
"version": "20.0.0"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -391,24 +391,45 @@
|
|||||||
"when": "version=sql2019"
|
"when": "version=sql2019"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "azure-sql",
|
||||||
|
"displayIndex": 4,
|
||||||
|
"displayName": "%azure-sql-displayName%",
|
||||||
|
"description": "%azure-sql-description%",
|
||||||
|
"platforms": "*",
|
||||||
|
"okButtonText": "%azure-sql-ok-button-text%",
|
||||||
|
"icon": {
|
||||||
|
"light": "./images/azure-sql.svg",
|
||||||
|
"dark": "./images/azure-sql.svg"
|
||||||
|
},
|
||||||
|
"options": [],
|
||||||
|
"providers": [
|
||||||
|
{
|
||||||
|
"webPageUrl": "https://portal.azure.com/#create/Microsoft.AzureSQL",
|
||||||
|
"requiredTools": []
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"linux-release-info": "^2.0.0",
|
"linux-release-info": "^2.0.0",
|
||||||
"promisify-child-process": "^3.1.1",
|
"promisify-child-process": "^3.1.1",
|
||||||
|
"semver": "^7.3.2",
|
||||||
"sudo-prompt": "9.1.1",
|
"sudo-prompt": "9.1.1",
|
||||||
"vscode-nls": "^4.0.0",
|
"vscode-nls": "^4.0.0",
|
||||||
"yamljs": "^0.3.0"
|
"yamljs": "^0.3.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/mocha": "^5.2.5",
|
"@types/mocha": "^5.2.5",
|
||||||
|
"@types/semver": "^7.3.1",
|
||||||
"@types/yamljs": "0.2.30",
|
"@types/yamljs": "0.2.30",
|
||||||
"mocha": "^5.2.0",
|
"mocha": "^5.2.0",
|
||||||
"mocha-junit-reporter": "^1.17.0",
|
"mocha-junit-reporter": "^1.17.0",
|
||||||
"mocha-multi-reporters": "^1.1.7",
|
"mocha-multi-reporters": "^1.1.7",
|
||||||
|
"should": "^13.2.3",
|
||||||
"typemoq": "^2.1.0",
|
"typemoq": "^2.1.0",
|
||||||
"vscodetestcover": "^1.1.0",
|
"vscodetestcover": "^1.1.0"
|
||||||
"should": "^13.2.3"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,5 +46,8 @@
|
|||||||
"bdc-agreement-azdata-eula": "azdata License Terms",
|
"bdc-agreement-azdata-eula": "azdata License Terms",
|
||||||
"bdc-agreement-bdc-eula": "SQL Server License Terms",
|
"bdc-agreement-bdc-eula": "SQL Server License Terms",
|
||||||
"deployment.configuration.title": "Deployment configuration",
|
"deployment.configuration.title": "Deployment configuration",
|
||||||
"azdata-install-location-description": "Location of the azdata package used for the install command"
|
"azdata-install-location-description": "Location of the azdata package used for the install command",
|
||||||
|
"azure-sql-displayName":"Azure SQL",
|
||||||
|
"azure-sql-description":"Create a SQL Database, SQL Virtual Machine, or SQL Managed Instance by going to the Azure portal",
|
||||||
|
"azure-sql-ok-button-text": "Create in Azure portal"
|
||||||
}
|
}
|
||||||
|
|||||||
83
extensions/resource-deployment/src/helpers/cacheManager.ts
Normal file
83
extensions/resource-deployment/src/helpers/cacheManager.ts
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import { Deferred } from './promise';
|
||||||
|
|
||||||
|
const enum Status {
|
||||||
|
notStarted,
|
||||||
|
inProgress,
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
interface State<T> {
|
||||||
|
entry?: T,
|
||||||
|
error?: Error,
|
||||||
|
status: Status,
|
||||||
|
id: number,
|
||||||
|
pendingOperation: Deferred<void>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An implementation of Cache Manager which ensures that only one call to populate cache miss is pending at a given time.
|
||||||
|
* All remaining calls for retrieval are awaited until the one in progress finishes and then all awaited calls are resolved with the value
|
||||||
|
* from the cache.
|
||||||
|
*/
|
||||||
|
export class CacheManager<K, T> {
|
||||||
|
private _cache = new Map<K, State<T>>();
|
||||||
|
private _id = 0;
|
||||||
|
|
||||||
|
public async getCacheEntry(key: K, retrieveEntry: (key: K) => Promise<T>): Promise<T> {
|
||||||
|
const cacheHit: State<T> | undefined = this._cache.get(key);
|
||||||
|
// each branch either throws or returns the password.
|
||||||
|
if (cacheHit === undefined) {
|
||||||
|
// populate a new state entry and add it to the cache
|
||||||
|
const state: State<T> = {
|
||||||
|
status: Status.notStarted,
|
||||||
|
id: this._id++,
|
||||||
|
pendingOperation: new Deferred<void>()
|
||||||
|
};
|
||||||
|
this._cache.set(key, state);
|
||||||
|
// now that we have the state entry initialized, retry to fetch the cacheEntry
|
||||||
|
let returnValue: T = await this.getCacheEntry(key, retrieveEntry);
|
||||||
|
await state.pendingOperation;
|
||||||
|
return returnValue!;
|
||||||
|
} else {
|
||||||
|
switch (cacheHit.status) {
|
||||||
|
case Status.notStarted: {
|
||||||
|
cacheHit.status = Status.inProgress;
|
||||||
|
// retrieve and populate the missed cache hit.
|
||||||
|
try {
|
||||||
|
cacheHit.entry = await retrieveEntry(key);
|
||||||
|
} catch (error) {
|
||||||
|
cacheHit.error = error;
|
||||||
|
} finally {
|
||||||
|
cacheHit.status = Status.done;
|
||||||
|
// we do not reject here even in error case because we do not want our awaits on pendingOperation to throw
|
||||||
|
// We track our own error state and when all done we throw if an error had happened. This results
|
||||||
|
// in the rejection of the promised returned by this method.
|
||||||
|
cacheHit.pendingOperation.resolve();
|
||||||
|
}
|
||||||
|
return await this.getCacheEntry(key, retrieveEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
case Status.inProgress: {
|
||||||
|
await cacheHit.pendingOperation;
|
||||||
|
return await this.getCacheEntry(key, retrieveEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
case Status.done: {
|
||||||
|
if (cacheHit.error !== undefined) {
|
||||||
|
await cacheHit.pendingOperation;
|
||||||
|
throw cacheHit.error;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
await cacheHit.pendingOperation;
|
||||||
|
return cacheHit.entry!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
84
extensions/resource-deployment/src/helpers/optionSources.ts
Normal file
84
extensions/resource-deployment/src/helpers/optionSources.ts
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import * as arc from 'arc';
|
||||||
|
import { CategoryValue } from 'azdata';
|
||||||
|
import { IOptionsSource } from '../interfaces';
|
||||||
|
import * as loc from '../localizedConstants';
|
||||||
|
import { apiService } from '../services/apiService';
|
||||||
|
import { throwUnless } from '../utils';
|
||||||
|
import { CacheManager } from './cacheManager';
|
||||||
|
|
||||||
|
export enum OptionsSourceType {
|
||||||
|
ArcControllersOptionsSource = 'ArcControllersOptionsSource'
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class OptionsSource implements IOptionsSource {
|
||||||
|
|
||||||
|
get type(): OptionsSourceType { return this._type; }
|
||||||
|
get variableNames(): { [index: string]: string; } { return this._variableNames; }
|
||||||
|
|
||||||
|
abstract async getOptions(): Promise<string[] | CategoryValue[]>;
|
||||||
|
abstract async getVariableValue(variableName: string, input: string): Promise<string>;
|
||||||
|
abstract getIsPassword(variableName: string): boolean;
|
||||||
|
|
||||||
|
constructor(private _variableNames: { [index: string]: string }, private _type: OptionsSourceType) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ArcControllersOptionsSource extends OptionsSource {
|
||||||
|
private _cacheManager = new CacheManager<string, string>();
|
||||||
|
|
||||||
|
async getOptions(): Promise<string[] | CategoryValue[]> {
|
||||||
|
const controllers = await apiService.arcApi.getRegisteredDataControllers();
|
||||||
|
throwUnless(controllers !== undefined && controllers.length !== 0, loc.noControllersConnected);
|
||||||
|
return controllers.map(ci => {
|
||||||
|
return ci.label;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getVariableValue(variableName: string, controllerLabel: string): Promise<string> {
|
||||||
|
const retrieveVariable = async (key: string) => {
|
||||||
|
const [variableName, controllerLabel] = JSON.parse(key);
|
||||||
|
const controllers = await apiService.arcApi.getRegisteredDataControllers();
|
||||||
|
const controller = controllers!.find(ci => ci.label === controllerLabel);
|
||||||
|
throwUnless(controller !== undefined, loc.noControllerInfoFound(controllerLabel));
|
||||||
|
switch (variableName) {
|
||||||
|
case 'endpoint':
|
||||||
|
return controller.info.url;
|
||||||
|
case 'username':
|
||||||
|
return controller.info.username;
|
||||||
|
case 'password':
|
||||||
|
const fetchedPassword = await this.getPassword(controller);
|
||||||
|
return fetchedPassword;
|
||||||
|
default:
|
||||||
|
throw new Error(loc.variableValueFetchForUnsupportedVariable(variableName));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const variableValue = await this._cacheManager.getCacheEntry(JSON.stringify([variableName, controllerLabel]), retrieveVariable);
|
||||||
|
return variableValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getPassword(controller: arc.DataController): Promise<string> {
|
||||||
|
let password = await apiService.arcApi.getControllerPassword(controller.info);
|
||||||
|
if (!password) {
|
||||||
|
password = await apiService.arcApi.reacquireControllerPassword(controller.info, password);
|
||||||
|
}
|
||||||
|
throwUnless(password !== undefined, loc.noPasswordFound(controller.label));
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
|
||||||
|
getIsPassword(variableName: string): boolean {
|
||||||
|
switch (variableName) {
|
||||||
|
case 'endpoint':
|
||||||
|
case 'username':
|
||||||
|
return false;
|
||||||
|
case 'password':
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
throw new Error(loc.isPasswordFetchForUnsupportedVariable(variableName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
25
extensions/resource-deployment/src/helpers/promise.ts
Normal file
25
extensions/resource-deployment/src/helpers/promise.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deferred promise
|
||||||
|
*/
|
||||||
|
export class Deferred<T> {
|
||||||
|
promise: Promise<T>;
|
||||||
|
resolve!: (value?: T | PromiseLike<T>) => void;
|
||||||
|
reject!: (reason?: any) => void;
|
||||||
|
constructor() {
|
||||||
|
this.promise = new Promise<T>((resolve, reject) => {
|
||||||
|
this.resolve = resolve;
|
||||||
|
this.reject = reject;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
then<TResult>(onfulfilled?: (value: T) => TResult | Thenable<TResult>, onrejected?: (reason: any) => TResult | Thenable<TResult>): Thenable<TResult>;
|
||||||
|
then<TResult>(onfulfilled?: (value: T) => TResult | Thenable<TResult>, onrejected?: (reason: any) => void): Thenable<TResult>;
|
||||||
|
then<TResult>(onfulfilled?: (value: T) => TResult | Thenable<TResult>, onrejected?: (reason: any) => TResult | Thenable<TResult>): Thenable<TResult> {
|
||||||
|
return this.promise.then(onfulfilled, onrejected);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
import * as azdata from 'azdata';
|
import * as azdata from 'azdata';
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
|
import { OptionsSourceType } from './helpers/optionSources';
|
||||||
|
|
||||||
export const NoteBookEnvironmentVariablePrefix = 'AZDATA_NB_VAR_';
|
export const NoteBookEnvironmentVariablePrefix = 'AZDATA_NB_VAR_';
|
||||||
|
|
||||||
@@ -19,6 +20,7 @@ export interface ResourceType {
|
|||||||
agreement?: AgreementInfo;
|
agreement?: AgreementInfo;
|
||||||
displayIndex?: number;
|
displayIndex?: number;
|
||||||
getProvider(selectedOptions: { option: string, value: string }[]): DeploymentProvider | undefined;
|
getProvider(selectedOptions: { option: string, value: string }[]): DeploymentProvider | undefined;
|
||||||
|
okButtonText?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AgreementInfo {
|
export interface AgreementInfo {
|
||||||
@@ -171,9 +173,18 @@ export type ComponentCSSStyles = {
|
|||||||
[key: string]: string;
|
[key: string]: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export interface IOptionsSource {
|
||||||
|
readonly type: OptionsSourceType,
|
||||||
|
readonly variableNames: { [index: string]: string; },
|
||||||
|
getOptions(): Promise<string[] | azdata.CategoryValue[]>,
|
||||||
|
getVariableValue(variableName: string, input: string): Promise<string>;
|
||||||
|
getIsPassword(variableName: string): boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface OptionsInfo {
|
export interface OptionsInfo {
|
||||||
values: string[] | azdata.CategoryValue[],
|
values?: string[] | azdata.CategoryValue[],
|
||||||
|
source?: IOptionsSource,
|
||||||
defaultValue: string,
|
defaultValue: string,
|
||||||
optionsType?: OptionsType
|
optionsType?: OptionsType
|
||||||
}
|
}
|
||||||
@@ -363,6 +374,7 @@ export interface ITool {
|
|||||||
finishInitialization(): Promise<void>;
|
finishInitialization(): Promise<void>;
|
||||||
install(): Promise<void>;
|
install(): Promise<void>;
|
||||||
isSameOrNewerThan(version: string): boolean;
|
isSameOrNewerThan(version: string): boolean;
|
||||||
|
validateEula(): boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const enum BdcDeploymentType {
|
export const enum BdcDeploymentType {
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import * as nls from 'vscode-nls';
|
import * as nls from 'vscode-nls';
|
||||||
|
import { FieldType, OptionsType } from './interfaces';
|
||||||
|
import { OptionsSourceType } from './helpers/optionSources';
|
||||||
|
|
||||||
const localize = nls.loadMessageBundle();
|
const localize = nls.loadMessageBundle();
|
||||||
|
|
||||||
@@ -13,12 +15,23 @@ export const resourceGroup = localize('azure.account.resourceGroup', "Resource G
|
|||||||
export const location = localize('azure.account.location', "Azure Location");
|
export const location = localize('azure.account.location', "Azure Location");
|
||||||
export const browse = localize('filePicker.browse', "Browse");
|
export const browse = localize('filePicker.browse', "Browse");
|
||||||
export const select = localize('filePicker.select', "Select");
|
export const select = localize('filePicker.select', "Select");
|
||||||
export const kubeConfigFilePath = localize('kubeConfigClusterPicker.kubeConfigFilePatht', "Kube config file path");
|
export const kubeConfigFilePath = localize('kubeConfigClusterPicker.kubeConfigFilePath', "Kube config file path");
|
||||||
export const clusterContextNotFound = localize('kubeConfigClusterPicker.clusterContextNotFound', "No cluster context information found");
|
export const clusterContextNotFound = localize('kubeConfigClusterPicker.clusterContextNotFound', "No cluster context information found");
|
||||||
export const signIn = localize('azure.signin', "Sign in…");
|
export const signIn = localize('azure.signin', "Sign in…");
|
||||||
export const refresh = localize('azure.refresh', "Refresh");
|
export const refresh = localize('azure.refresh', "Refresh");
|
||||||
export const createNewResourceGroup = localize('azure.resourceGroup.createNewResourceGroup', "Create a new resource group");
|
export const createNewResourceGroup = localize('azure.resourceGroup.createNewResourceGroup', "Create a new resource group");
|
||||||
export const NewResourceGroupAriaLabel = localize('azure.resourceGroup.NewResourceGroupAriaLabel', "New resource group name");
|
export const NewResourceGroupAriaLabel = localize('azure.resourceGroup.NewResourceGroupAriaLabel', "New resource group name");
|
||||||
export const realm = localize('deployCluster.Realm', "Realm");
|
export const realm = localize('deployCluster.Realm', "Realm");
|
||||||
|
export const unexpectedOptionsSourceType = (type: OptionsSourceType) => localize('optionsSourceType.Invalid', "Invalid options source type:{0}", type);
|
||||||
|
export const unknownFieldTypeError = (type: FieldType) => localize('UnknownFieldTypeError', "Unknown field type: \"{0}\"", type);
|
||||||
|
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 noControllersConnected = localize('noControllersConnected', "No Azure Arc controllers are currently connected. Please run the command: 'Connect to Existing Azure Arc Controller' and then try again");
|
||||||
|
export const noOptionsSourceDefined = (optionsSourceType: string) => localize('noOptionsSourceDefined', "No OptionsSource defined for type: {0}", optionsSourceType);
|
||||||
|
export const noControllerInfoFound = (name: string) => localize('noControllerInfoFound', "controllerInfo could not be found with name: {0}", name);
|
||||||
|
export const noPasswordFound = (controllerName: string) => localize('noPasswordFound', "Password could not be retrieved for controller: {0} and user did not provide a password. Please retry later.", controllerName);
|
||||||
|
export const optionsNotDefined = (fieldType: FieldType) => localize('optionsNotDefined', "FieldInfo.options was not defined for field type: {0}", fieldType);
|
||||||
|
export const optionsNotObjectOrArray = localize('optionsNotObjectOrArray', "FieldInfo.options must be an object if it is not an array");
|
||||||
|
export const optionsTypeNotFound = localize('optionsTypeNotFound', "When FieldInfo.options is an object it must have 'optionsType' property");
|
||||||
|
export const optionsTypeRadioOrDropdown = localize('optionsTypeRadioOrDropdown', "When optionsType is not {0} then it must be {1}", OptionsType.Radio, OptionsType.Dropdown);
|
||||||
|
export const azdataEulaNotAccepted = localize('azdataEulaNotAccepted', "Deployment cannot continue. Azure Data CLI license terms have not been accepted. Execute the command: [Azure Data CLI: Accept EULA] to accept EULA to enable the features that requires Azure Data CLI.");
|
||||||
|
|||||||
@@ -3,28 +3,22 @@
|
|||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import * as arc from 'arc';
|
||||||
|
import * as azdataExt from 'azdata-ext';
|
||||||
import * as azurecore from 'azurecore';
|
import * as azurecore from 'azurecore';
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
|
|
||||||
export interface IApiService {
|
export interface IApiService {
|
||||||
getAzurecoreApi(): Promise<azurecore.IExtension>;
|
readonly azurecoreApi: azurecore.IExtension;
|
||||||
|
readonly azdataApi: azdataExt.IExtension;
|
||||||
|
readonly arcApi: arc.IExtension;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ApiService implements IApiService {
|
class ApiService implements IApiService {
|
||||||
|
|
||||||
private azurecoreApi: azurecore.IExtension | undefined;
|
|
||||||
|
|
||||||
constructor() { }
|
constructor() { }
|
||||||
|
public get azurecoreApi() { return vscode.extensions.getExtension(azurecore.extension.name)?.exports; }
|
||||||
public async getAzurecoreApi(): Promise<azurecore.IExtension> {
|
public get azdataApi() { return vscode.extensions.getExtension(azdataExt.extension.name)?.exports; }
|
||||||
if (!this.azurecoreApi) {
|
public get arcApi() { return vscode.extensions.getExtension(arc.extension.name)?.exports; }
|
||||||
this.azurecoreApi = <azurecore.IExtension>(await vscode.extensions.getExtension(azurecore.extension.name)?.activate());
|
|
||||||
if (!this.azurecoreApi) {
|
|
||||||
throw new Error('Unable to retrieve azurecore API');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this.azurecoreApi;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const apiService: IApiService = new ApiService();
|
export const apiService: IApiService = new ApiService();
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
/*---------------------------------------------------------------------------------------------
|
|
||||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
|
||||||
*--------------------------------------------------------------------------------------------*/
|
|
||||||
import * as arc from 'arc';
|
|
||||||
import * as vscode from 'vscode';
|
|
||||||
|
|
||||||
export class ArcService {
|
|
||||||
private _arcApi: arc.IExtension;
|
|
||||||
constructor() {
|
|
||||||
this._arcApi = vscode.extensions.getExtension(arc.extension.name)?.exports;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getRegisteredDataControllers(): Promise<arc.ControllerInfo[]> {
|
|
||||||
return await this._arcApi.getRegisteredDataControllers();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -8,10 +8,11 @@ import { SemVer } from 'semver';
|
|||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import * as nls from 'vscode-nls';
|
import * as nls from 'vscode-nls';
|
||||||
import { AzdataInstallLocationKey, DeploymentConfigurationKey } from '../../constants';
|
import { AzdataInstallLocationKey, DeploymentConfigurationKey } from '../../constants';
|
||||||
import { Command, OsDistribution, ToolType } from '../../interfaces';
|
import { Command, OsDistribution, ToolStatus, ToolType } from '../../interfaces';
|
||||||
|
import { apiService } from '../apiService';
|
||||||
import { IPlatformService } from '../platformService';
|
import { IPlatformService } from '../platformService';
|
||||||
import { dependencyType, ToolBase } from './toolBase';
|
import { dependencyType, ToolBase } from './toolBase';
|
||||||
import { SemVerProxy } from './SemVerProxy';
|
import * as loc from '../../localizedConstants';
|
||||||
|
|
||||||
const localize = nls.loadMessageBundle();
|
const localize = nls.loadMessageBundle();
|
||||||
export const AzdataToolName = 'azdata';
|
export const AzdataToolName = 'azdata';
|
||||||
@@ -44,25 +45,57 @@ export class AzdataTool extends ToolBase {
|
|||||||
return 'https://docs.microsoft.com/sql/big-data-cluster/deploy-install-azdata';
|
return 'https://docs.microsoft.com/sql/big-data-cluster/deploy-install-azdata';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public validateEula(): boolean {
|
||||||
|
if (apiService.azdataApi.isEulaAccepted()) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
this.setStatusDescription(loc.azdataEulaNotAccepted);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* unused */
|
||||||
protected get versionCommand(): Command {
|
protected get versionCommand(): Command {
|
||||||
return {
|
return {
|
||||||
command: 'azdata -v'
|
command: ''
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* unused */
|
||||||
protected get discoveryCommand(): Command {
|
protected get discoveryCommand(): Command {
|
||||||
return {
|
return {
|
||||||
command: this.discoveryCommandString('azdata')
|
command: ''
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* updates the version and status for the tool.
|
||||||
|
*/
|
||||||
|
protected async updateVersionAndStatus(): Promise<void> {
|
||||||
|
this.setStatusDescription('');
|
||||||
|
await this.addInstallationSearchPathsToSystemPath();
|
||||||
|
|
||||||
|
const commandOutput = await apiService.azdataApi.azdata.version();
|
||||||
|
this.version = apiService.azdataApi.azdata.getSemVersion();
|
||||||
|
if (this.version) {
|
||||||
|
if (this.autoInstallSupported) {
|
||||||
|
// set the installationPath
|
||||||
|
this.setInstallationPathOrAdditionalInformation(apiService.azdataApi.azdata.getPath());
|
||||||
|
}
|
||||||
|
this.setStatus(ToolStatus.Installed);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.setInstallationPathOrAdditionalInformation(localize('deployCluster.GetToolVersionErrorInformation', "Error retrieving version information. See output channel '{0}' for more details", this.outputChannelName));
|
||||||
|
this.setStatusDescription(localize('deployCluster.GetToolVersionError', "Error retrieving version information.{0}Invalid output received, get version command output: '{1}' ", EOL, commandOutput.stderr.join(EOL)));
|
||||||
|
this.setStatus(ToolStatus.NotInstalled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected getVersionFromOutput(output: string): SemVer | undefined {
|
protected getVersionFromOutput(output: string): SemVer | undefined {
|
||||||
let version: SemVer | undefined = undefined;
|
return apiService.azdataApi.azdata.getSemVersion();
|
||||||
if (output && output.split(EOL).length > 0) {
|
|
||||||
version = new SemVerProxy(output.split(EOL)[0].replace(/ /g, ''));
|
|
||||||
}
|
|
||||||
return version;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async getSearchPaths(): Promise<string[]> {
|
protected async getSearchPaths(): Promise<string[]> {
|
||||||
switch (this.osDistribution) {
|
switch (this.osDistribution) {
|
||||||
case OsDistribution.win32:
|
case OsDistribution.win32:
|
||||||
|
|||||||
@@ -0,0 +1,183 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
import { EOL } from 'os';
|
||||||
|
import * as path from 'path';
|
||||||
|
import { SemVer } from 'semver';
|
||||||
|
import * as vscode from 'vscode';
|
||||||
|
import * as nls from 'vscode-nls';
|
||||||
|
import { AzdataInstallLocationKey, DeploymentConfigurationKey } from '../../constants';
|
||||||
|
import { Command, OsDistribution, ToolType } from '../../interfaces';
|
||||||
|
import { IPlatformService } from '../platformService';
|
||||||
|
import { dependencyType, ToolBase } from './toolBase';
|
||||||
|
import { SemVerProxy } from './SemVerProxy';
|
||||||
|
|
||||||
|
const localize = nls.loadMessageBundle();
|
||||||
|
export const AzdataToolName = 'azdata-old';
|
||||||
|
const win32InstallationRoot = `${process.env['ProgramFiles(x86)']}\\Microsoft SDKs\\Azdata\\CLI\\wbin`;
|
||||||
|
const macInstallationRoot = '/usr/local/bin';
|
||||||
|
const debianInstallationRoot = '/usr/local/bin';
|
||||||
|
|
||||||
|
// TODO: This is a temporary shim until we can convert the BDC deployment fully over to using the azdata extension
|
||||||
|
export class AzdataToolOld extends ToolBase {
|
||||||
|
constructor(platformService: IPlatformService) {
|
||||||
|
super(platformService);
|
||||||
|
}
|
||||||
|
|
||||||
|
get name(): string {
|
||||||
|
return AzdataToolName;
|
||||||
|
}
|
||||||
|
|
||||||
|
get description(): string {
|
||||||
|
return localize('resourceDeployment.AzdataDescription', "Azure Data command line interface");
|
||||||
|
}
|
||||||
|
|
||||||
|
get type(): ToolType {
|
||||||
|
return ToolType.Azdata;
|
||||||
|
}
|
||||||
|
|
||||||
|
get displayName(): string {
|
||||||
|
return localize('resourceDeployment.AzdataDisplayName', "Azure Data CLI");
|
||||||
|
}
|
||||||
|
|
||||||
|
get homePage(): string {
|
||||||
|
return 'https://docs.microsoft.com/sql/big-data-cluster/deploy-install-azdata';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get versionCommand(): Command {
|
||||||
|
return {
|
||||||
|
command: 'azdata -v'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get discoveryCommand(): Command {
|
||||||
|
return {
|
||||||
|
command: this.discoveryCommandString('azdata')
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getVersionFromOutput(output: string): SemVer | undefined {
|
||||||
|
let version: SemVer | undefined = undefined;
|
||||||
|
if (output && output.split(EOL).length > 0) {
|
||||||
|
version = new SemVerProxy(output.split(EOL)[0].replace(/ /g, ''));
|
||||||
|
}
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
protected async getSearchPaths(): Promise<string[]> {
|
||||||
|
switch (this.osDistribution) {
|
||||||
|
case OsDistribution.win32:
|
||||||
|
return [win32InstallationRoot];
|
||||||
|
case OsDistribution.darwin:
|
||||||
|
return [macInstallationRoot];
|
||||||
|
case OsDistribution.debian:
|
||||||
|
return [debianInstallationRoot];
|
||||||
|
default:
|
||||||
|
const azdataCliInstallLocation = await this.getPip3InstallLocation('azdata-cli');
|
||||||
|
if (azdataCliInstallLocation) {
|
||||||
|
return [path.join(azdataCliInstallLocation, '..', 'Scripts'), path.join(azdataCliInstallLocation, '..', '..', '..', 'bin')];
|
||||||
|
} else {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get allInstallationCommands(): Map<OsDistribution, Command[]> {
|
||||||
|
return new Map<OsDistribution, Command[]>([
|
||||||
|
[OsDistribution.debian, this.debianInstallationCommands],
|
||||||
|
[OsDistribution.win32, this.win32InstallationCommands],
|
||||||
|
[OsDistribution.darwin, this.macOsInstallationCommands],
|
||||||
|
[OsDistribution.others, []]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private get azdataInstallLocation(): string {
|
||||||
|
return vscode.workspace.getConfiguration(DeploymentConfigurationKey)[AzdataInstallLocationKey] || this.defaultInstallLocationByDistribution.get(this.osDistribution);
|
||||||
|
}
|
||||||
|
|
||||||
|
private defaultInstallLocationByDistribution: Map<OsDistribution, string> = new Map<OsDistribution, string>([
|
||||||
|
[OsDistribution.debian, 'https://packages.microsoft.com/config/ubuntu/16.04/mssql-server-2019.list'],
|
||||||
|
[OsDistribution.win32, 'https://aka.ms/azdata-msi'],
|
||||||
|
[OsDistribution.darwin, 'microsoft/azdata-cli-release'],
|
||||||
|
[OsDistribution.others, '']
|
||||||
|
]);
|
||||||
|
|
||||||
|
protected dependenciesByOsType: Map<OsDistribution, dependencyType[]> = new Map<OsDistribution, dependencyType[]>([
|
||||||
|
[OsDistribution.debian, []],
|
||||||
|
[OsDistribution.win32, []],
|
||||||
|
[OsDistribution.darwin, [dependencyType.Brew]],
|
||||||
|
[OsDistribution.others, []]
|
||||||
|
]);
|
||||||
|
|
||||||
|
private get win32InstallationCommands() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
comment: localize('resourceDeployment.Azdata.DeletingPreviousAzdata.msi', "deleting previously downloaded Azdata.msi if one exists …"),
|
||||||
|
command: `IF EXIST .\\Azdata.msi DEL /F .\\Azdata.msi`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sudo: true,
|
||||||
|
comment: localize('resourceDeployment.Azdata.DownloadingAndInstallingAzdata', "downloading Azdata.msi and installing azdata-cli …"),
|
||||||
|
command: `powershell -NoLogo -NonInteractive -NoProfile -Command "& {try {(New-Object System.Net.WebClient).DownloadFile('${this.azdataInstallLocation}', 'Azdata.msi'); Start-Process msiexec.exe -Wait -ArgumentList '/I Azdata.msi /passive /quiet /lvx ADS_AzdataInstall.log'} catch { Write-Error $_.Exception; exit 1 }}"`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
comment: localize('resourceDeployment.Azdata.DisplayingInstallationLog', "displaying the installation log …"),
|
||||||
|
command: `type ADS_AzdataInstall.log | findstr /i /v ^MSI"`,
|
||||||
|
ignoreError: true
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private get macOsInstallationCommands() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
comment: localize('resourceDeployment.Azdata.TappingBrewRepository', "tapping into the brew repository for azdata-cli …"),
|
||||||
|
command: `brew tap ${this.azdataInstallLocation}`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
comment: localize('resourceDeployment.Azdata.UpdatingBrewRepository', "updating the brew repository for azdata-cli installation …"),
|
||||||
|
command: 'brew update'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
comment: localize('resourceDeployment.Azdata.InstallingAzdata', "installing azdata …"),
|
||||||
|
command: 'brew install azdata-cli'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private get debianInstallationCommands() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
sudo: true,
|
||||||
|
comment: localize('resourceDeployment.Azdata.AptGetUpdate', "updating repository information …"),
|
||||||
|
command: 'apt-get update'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sudo: true,
|
||||||
|
comment: localize('resourceDeployment.Azdata.AptGetPackages', "getting packages needed for azdata installation …"),
|
||||||
|
command: 'apt-get install gnupg ca-certificates curl apt-transport-https lsb-release -y'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sudo: true,
|
||||||
|
comment: localize('resourceDeployment.Azdata.DownloadAndInstallingSigningKey', "downloading and installing the signing key for azdata …"),
|
||||||
|
command: 'wget -qO- https://packages.microsoft.com/keys/microsoft.asc | apt-key add -'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sudo: true,
|
||||||
|
comment: localize('resourceDeployment.Azdata.AddingAzdataRepositoryInformation', "adding the azdata repository information …"),
|
||||||
|
command: `add-apt-repository "$(wget -qO- ${this.azdataInstallLocation})"`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sudo: true,
|
||||||
|
comment: localize('resourceDeployment.Azdata.AptGetUpdate', "updating repository information …"),
|
||||||
|
command: 'apt-get update'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sudo: true,
|
||||||
|
comment: localize('resourceDeployment.Azdata.InstallingAzdata', "installing azdata …"),
|
||||||
|
command: 'apt-get install -y azdata-cli'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -58,6 +58,8 @@ export abstract class ToolBase implements ITool {
|
|||||||
|
|
||||||
protected abstract readonly versionCommand: Command;
|
protected abstract readonly versionCommand: Command;
|
||||||
|
|
||||||
|
public validateEula(): boolean { return true; }
|
||||||
|
|
||||||
public get dependencyMessages(): string[] {
|
public get dependencyMessages(): string[] {
|
||||||
return (this.dependenciesByOsType.get(this.osDistribution) || []).map((msgType: dependencyType) => messageByDependencyType.get(msgType)!);
|
return (this.dependenciesByOsType.get(this.osDistribution) || []).map((msgType: dependencyType) => messageByDependencyType.get(msgType)!);
|
||||||
}
|
}
|
||||||
@@ -126,10 +128,18 @@ export abstract class ToolBase implements ITool {
|
|||||||
return this._statusDescription;
|
return this._statusDescription;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected setStatusDescription(value: string | undefined): void {
|
||||||
|
this._statusDescription = value;
|
||||||
|
}
|
||||||
|
|
||||||
public get installationPathOrAdditionalInformation(): string | undefined {
|
public get installationPathOrAdditionalInformation(): string | undefined {
|
||||||
return this._installationPathOrAdditionalInformation;
|
return this._installationPathOrAdditionalInformation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected setInstallationPathOrAdditionalInformation(value: string | undefined) {
|
||||||
|
this._installationPathOrAdditionalInformation = value;
|
||||||
|
}
|
||||||
|
|
||||||
protected get installationCommands(): Command[] | undefined {
|
protected get installationCommands(): Command[] | undefined {
|
||||||
return this.allInstallationCommands.get(this.osDistribution);
|
return this.allInstallationCommands.get(this.osDistribution);
|
||||||
}
|
}
|
||||||
@@ -250,7 +260,7 @@ export abstract class ToolBase implements ITool {
|
|||||||
/**
|
/**
|
||||||
* updates the version and status for the tool.
|
* updates the version and status for the tool.
|
||||||
*/
|
*/
|
||||||
private async updateVersionAndStatus(): Promise<void> {
|
protected async updateVersionAndStatus(): Promise<void> {
|
||||||
this._statusDescription = '';
|
this._statusDescription = '';
|
||||||
await this.addInstallationSearchPathsToSystemPath();
|
await this.addInstallationSearchPathsToSystemPath();
|
||||||
const commandOutput = await this.platformService.runCommand(
|
const commandOutput = await this.platformService.runCommand(
|
||||||
@@ -306,7 +316,7 @@ export abstract class ToolBase implements ITool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
isSameOrNewerThan(version?: string): boolean {
|
isSameOrNewerThan(version?: string): boolean {
|
||||||
return !version || (this._version ? SemVerCompare(this._version, version) >= 0 : false);
|
return !version || (this._version ? SemVerCompare(this._version.raw, version) >= 0 : false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _pendingVersionAndStatusUpdate!: Promise<void>;
|
private _pendingVersionAndStatusUpdate!: Promise<void>;
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { AzCliTool } from './tools/azCliTool';
|
|||||||
import { AzdataTool } from './tools/azdataTool';
|
import { AzdataTool } from './tools/azdataTool';
|
||||||
import { KubeCtlTool } from './tools/kubeCtlTool';
|
import { KubeCtlTool } from './tools/kubeCtlTool';
|
||||||
import { IPlatformService } from './platformService';
|
import { IPlatformService } from './platformService';
|
||||||
|
import { AzdataToolOld } from './tools/azdataToolOld';
|
||||||
|
|
||||||
export interface IToolsService {
|
export interface IToolsService {
|
||||||
getToolByName(toolName: string): ITool | undefined;
|
getToolByName(toolName: string): ITool | undefined;
|
||||||
@@ -24,6 +25,7 @@ export class ToolsService implements IToolsService {
|
|||||||
new DockerTool(this._platformService),
|
new DockerTool(this._platformService),
|
||||||
new AzCliTool(this._platformService),
|
new AzCliTool(this._platformService),
|
||||||
new AzdataTool(this._platformService),
|
new AzdataTool(this._platformService),
|
||||||
|
new AzdataToolOld(this._platformService),
|
||||||
new KubeCtlTool(this._platformService)
|
new KubeCtlTool(this._platformService)
|
||||||
].map<[string, ITool]>((tool: ITool) => [tool.name, tool])
|
].map<[string, ITool]>((tool: ITool) => [tool.name, tool])
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -8,9 +8,8 @@ import assert = require('assert');
|
|||||||
import { apiService } from '../services/apiService';
|
import { apiService } from '../services/apiService';
|
||||||
|
|
||||||
suite('API Service Tests', function (): void {
|
suite('API Service Tests', function (): void {
|
||||||
|
test('getAzurecoreApi returns azure api', () => {
|
||||||
test('getAzurecoreApi returns azure api', async () => {
|
const api = apiService.azurecoreApi;
|
||||||
const api = await apiService.getAzurecoreApi();
|
|
||||||
assert(api !== undefined);
|
assert(api !== undefined);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
/// <reference path='../../../../src/sql/azdata.d.ts'/>
|
/// <reference path='../../../../src/sql/azdata.d.ts'/>
|
||||||
/// <reference path='../../../../src/sql/azdata.proposed.d.ts'/>
|
/// <reference path='../../../../src/sql/azdata.proposed.d.ts'/>
|
||||||
/// <reference path='../../../arc/src/typings/arc.d.ts'/>
|
/// <reference path='../../../arc/src/typings/arc.d.ts'/>
|
||||||
|
/// <reference path='../../../azdata/src/typings/azdata-ext.d.ts'/>
|
||||||
/// <reference path='../../../azurecore/src/azurecore.d.ts'/>
|
/// <reference path='../../../azurecore/src/azurecore.d.ts'/>
|
||||||
/// <reference path='../../../azurecore/src/azureResource/azure-resource.d.ts'/>
|
/// <reference path='../../../azurecore/src/azureResource/azure-resource.d.ts'/>
|
||||||
/// <reference types='@types/node'/>
|
/// <reference types='@types/node'/>
|
||||||
|
|||||||
@@ -176,11 +176,11 @@ export class AzureSettingsPage extends WizardPageBase<DeployClusterWizard> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public onLeave(): void {
|
public async onLeave(): Promise<void> {
|
||||||
this.wizard.wizardObject.registerNavigationValidator((pcInfo) => {
|
this.wizard.wizardObject.registerNavigationValidator((pcInfo) => {
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
setModelValues(this.inputComponents, this.wizard.model);
|
await setModelValues(this.inputComponents, this.wizard.model);
|
||||||
Object.assign(this.wizard.inputComponents, this.inputComponents);
|
Object.assign(this.wizard.inputComponents, this.inputComponents);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -289,8 +289,8 @@ export class ClusterSettingsPage extends WizardPageBase<DeployClusterWizard> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public onLeave() {
|
public async onLeave(): Promise<void> {
|
||||||
setModelValues(this.inputComponents, this.wizard.model);
|
await setModelValues(this.inputComponents, this.wizard.model);
|
||||||
Object.assign(this.wizard.inputComponents, this.inputComponents);
|
Object.assign(this.wizard.inputComponents, this.inputComponents);
|
||||||
if (this.wizard.model.authenticationMode === AuthenticationMode.ActiveDirectory) {
|
if (this.wizard.model.authenticationMode === AuthenticationMode.ActiveDirectory) {
|
||||||
const variableDNSPrefixMapping: { [s: string]: string } = {};
|
const variableDNSPrefixMapping: { [s: string]: string } = {};
|
||||||
|
|||||||
@@ -233,7 +233,7 @@ export class DeploymentProfilePage extends WizardPageBase<DeployClusterWizard> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public onLeave() {
|
public async onLeave(): Promise<void> {
|
||||||
this.wizard.wizardObject.registerNavigationValidator((pcInfo) => {
|
this.wizard.wizardObject.registerNavigationValidator((pcInfo) => {
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -400,8 +400,8 @@ export class ServiceSettingsPage extends WizardPageBase<DeployClusterWizard> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public onLeave(): void {
|
public async onLeave(): Promise<void> {
|
||||||
setModelValues(this.inputComponents, this.wizard.model);
|
await setModelValues(this.inputComponents, this.wizard.model);
|
||||||
Object.assign(this.wizard.inputComponents, this.inputComponents);
|
Object.assign(this.wizard.inputComponents, this.inputComponents);
|
||||||
this.wizard.wizardObject.registerNavigationValidator((pcInfo) => {
|
this.wizard.wizardObject.registerNavigationValidator((pcInfo) => {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -326,7 +326,7 @@ export class SummaryPage extends WizardPageBase<DeployClusterWizard> {
|
|||||||
this.form.addFormItems(this.formItems);
|
this.form.addFormItems(this.formItems);
|
||||||
}
|
}
|
||||||
|
|
||||||
public onLeave() {
|
public async onLeave(): Promise<void> {
|
||||||
this.wizard.hideCustomButtons();
|
this.wizard.hideCustomButtons();
|
||||||
this.wizard.wizardObject.message = { text: '' };
|
this.wizard.wizardObject.message = { text: '' };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ export class TargetClusterContextPage extends WizardPageBase<DeployClusterWizard
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public onLeave() {
|
public async onLeave(): Promise<void> {
|
||||||
this.wizard.wizardObject.registerNavigationValidator((e) => {
|
this.wizard.wizardObject.registerNavigationValidator((e) => {
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -94,9 +94,9 @@ export class DeploymentInputDialog extends DialogBase {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected onComplete(): void {
|
protected async onComplete(): Promise<void> {
|
||||||
const model: Model = new Model();
|
const model: Model = new Model();
|
||||||
setModelValues(this.inputComponents, model);
|
await setModelValues(this.inputComponents, model);
|
||||||
if (instanceOfNotebookBasedDialogInfo(this.dialogInfo)) {
|
if (instanceOfNotebookBasedDialogInfo(this.dialogInfo)) {
|
||||||
model.setEnvironmentVariables();
|
model.setEnvironmentVariables();
|
||||||
if (this.dialogInfo.runNotebook) {
|
if (this.dialogInfo.runNotebook) {
|
||||||
@@ -110,7 +110,7 @@ export class DeploymentInputDialog extends DialogBase {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
vscode.commands.executeCommand(this.dialogInfo.command, model);
|
await vscode.commands.executeCommand(this.dialogInfo.command, model);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,12 +27,12 @@ export abstract class DialogBase {
|
|||||||
this.dispose();
|
this.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
private onOkButtonClicked(): void {
|
private async onOkButtonClicked(): Promise<void> {
|
||||||
this.onComplete();
|
await this.onComplete();
|
||||||
this.dispose();
|
this.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected onComplete(): void {
|
protected async onComplete(): Promise<void> {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected dispose(): void {
|
protected dispose(): void {
|
||||||
|
|||||||
@@ -3,27 +3,28 @@
|
|||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
import * as azdata from 'azdata';
|
import * as azdata from 'azdata';
|
||||||
|
import { azureResource } from 'azureResource';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import { EOL, homedir as os_homedir } from 'os';
|
import { EOL, homedir as os_homedir } from 'os';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import * as nls from 'vscode-nls';
|
import * as nls from 'vscode-nls';
|
||||||
import { azureResource } from 'azureResource';
|
import { ArcControllersOptionsSource, OptionsSourceType } from '../helpers/optionSources';
|
||||||
import { AzureAccountFieldInfo, AzureLocationsFieldInfo, ComponentCSSStyles, DialogInfoBase, FieldInfo, FieldType, FilePickerFieldInfo, KubeClusterContextFieldInfo, LabelPosition, NoteBookEnvironmentVariablePrefix, OptionsInfo, OptionsType, PageInfoBase, RowInfo, SectionInfo, TextCSSStyles } from '../interfaces';
|
import { AzureAccountFieldInfo, AzureLocationsFieldInfo, ComponentCSSStyles, DialogInfoBase, FieldInfo, FieldType, FilePickerFieldInfo, IOptionsSource, KubeClusterContextFieldInfo, LabelPosition, NoteBookEnvironmentVariablePrefix, OptionsInfo, OptionsType, PageInfoBase, RowInfo, SectionInfo, TextCSSStyles } from '../interfaces';
|
||||||
import * as loc from '../localizedConstants';
|
import * as loc from '../localizedConstants';
|
||||||
import { apiService } from '../services/apiService';
|
import { apiService } from '../services/apiService';
|
||||||
import { getDefaultKubeConfigPath, getKubeConfigClusterContexts } from '../services/kubeService';
|
import { getDefaultKubeConfigPath, getKubeConfigClusterContexts } from '../services/kubeService';
|
||||||
import { assert, getDateTimeString, getErrorMessage } from '../utils';
|
import { KubeCtlTool, KubeCtlToolName } from '../services/tools/kubeCtlTool';
|
||||||
|
import { IToolsService } from '../services/toolsService';
|
||||||
|
import { getDateTimeString, getErrorMessage, throwUnless } from '../utils';
|
||||||
import { WizardInfoBase } from './../interfaces';
|
import { WizardInfoBase } from './../interfaces';
|
||||||
import { Model } from './model';
|
import { Model } from './model';
|
||||||
import { RadioGroupLoadingComponentBuilder } from './radioGroupLoadingComponentBuilder';
|
import { RadioGroupLoadingComponentBuilder } from './radioGroupLoadingComponentBuilder';
|
||||||
import { IToolsService } from '../services/toolsService';
|
|
||||||
import { KubeCtlToolName, KubeCtlTool } from '../services/tools/kubeCtlTool';
|
|
||||||
|
|
||||||
const localize = nls.loadMessageBundle();
|
const localize = nls.loadMessageBundle();
|
||||||
|
|
||||||
export type Validator = () => { valid: boolean, message: string };
|
export type Validator = () => { valid: boolean, message: string };
|
||||||
export type InputValueTransformer = (inputValue: string) => string;
|
export type InputValueTransformer = (inputValue: string) => string | Promise<string>;
|
||||||
export type InputComponent = azdata.TextComponent | azdata.InputBoxComponent | azdata.DropDownComponent | azdata.CheckBoxComponent | RadioGroupLoadingComponentBuilder;
|
export type InputComponent = azdata.TextComponent | azdata.InputBoxComponent | azdata.DropDownComponent | azdata.CheckBoxComponent | RadioGroupLoadingComponentBuilder;
|
||||||
export type InputComponentInfo = {
|
export type InputComponentInfo = {
|
||||||
component: InputComponent;
|
component: InputComponent;
|
||||||
@@ -116,7 +117,16 @@ interface ContextBase {
|
|||||||
onNewInputComponentCreated: (name: string, inputComponentInfo: InputComponentInfo) => void;
|
onNewInputComponentCreated: (name: string, inputComponentInfo: InputComponentInfo) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createTextInput(view: azdata.ModelView, inputInfo: { defaultValue?: string, ariaLabel: string, required?: boolean, placeHolder?: string, width?: string, enabled?: boolean }): azdata.InputBoxComponent {
|
export function createTextInput(view: azdata.ModelView, inputInfo: {
|
||||||
|
defaultValue?: string,
|
||||||
|
ariaLabel: string,
|
||||||
|
required?: boolean,
|
||||||
|
placeHolder?: string,
|
||||||
|
width?: string,
|
||||||
|
enabled?: boolean,
|
||||||
|
validationRegex?: RegExp,
|
||||||
|
validationErrorMessage?: string
|
||||||
|
}): azdata.InputBoxComponent {
|
||||||
return view.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({
|
return view.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({
|
||||||
value: inputInfo.defaultValue,
|
value: inputInfo.defaultValue,
|
||||||
ariaLabel: inputInfo.ariaLabel,
|
ariaLabel: inputInfo.ariaLabel,
|
||||||
@@ -124,7 +134,13 @@ export function createTextInput(view: azdata.ModelView, inputInfo: { defaultValu
|
|||||||
required: inputInfo.required,
|
required: inputInfo.required,
|
||||||
placeHolder: inputInfo.placeHolder,
|
placeHolder: inputInfo.placeHolder,
|
||||||
width: inputInfo.width,
|
width: inputInfo.width,
|
||||||
enabled: inputInfo.enabled
|
enabled: inputInfo.enabled,
|
||||||
|
validationErrorMessage: inputInfo.validationErrorMessage
|
||||||
|
}).withValidation(component => {
|
||||||
|
if (inputInfo.validationRegex?.test(component.value || '') === false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}).component();
|
}).component();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -353,7 +369,7 @@ function addLabelInputPairToContainer(view: azdata.ModelView, components: azdata
|
|||||||
async function processField(context: FieldContext): Promise<void> {
|
async function processField(context: FieldContext): Promise<void> {
|
||||||
switch (context.fieldInfo.type) {
|
switch (context.fieldInfo.type) {
|
||||||
case FieldType.Options:
|
case FieldType.Options:
|
||||||
processOptionsTypeField(context);
|
await processOptionsTypeField(context);
|
||||||
break;
|
break;
|
||||||
case FieldType.DateTimeText:
|
case FieldType.DateTimeText:
|
||||||
processDateTimeTextField(context);
|
processDateTimeTextField(context);
|
||||||
@@ -390,12 +406,23 @@ async function processField(context: FieldContext): Promise<void> {
|
|||||||
await processKubeStorageClassField(context);
|
await processKubeStorageClassField(context);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error(localize('UnknownFieldTypeError', "Unknown field type: \"{0}\"", context.fieldInfo.type));
|
throw new Error(loc.unknownFieldTypeError(context.fieldInfo.type));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function processOptionsTypeField(context: FieldContext): void {
|
function disableControlButtons(container: azdata.window.Dialog | azdata.window.Wizard): void {
|
||||||
assert(context.fieldInfo.options !== undefined, `FieldInfo.options must be defined for FieldType:${FieldType.Options}`);
|
if ('okButton' in container) {
|
||||||
|
container.okButton.enabled = false;
|
||||||
|
} else {
|
||||||
|
container.doneButton.enabled = false;
|
||||||
|
container.nextButton.enabled = false;
|
||||||
|
container.backButton.enabled = false;
|
||||||
|
container.customButtons.forEach(b => b.enabled = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function processOptionsTypeField(context: FieldContext): Promise<void> {
|
||||||
|
throwUnless(context.fieldInfo.options !== undefined, loc.optionsNotDefined(context.fieldInfo.type));
|
||||||
if (Array.isArray(context.fieldInfo.options)) {
|
if (Array.isArray(context.fieldInfo.options)) {
|
||||||
context.fieldInfo.options = <OptionsInfo>{
|
context.fieldInfo.options = <OptionsInfo>{
|
||||||
values: context.fieldInfo.options,
|
values: context.fieldInfo.options,
|
||||||
@@ -403,17 +430,69 @@ function processOptionsTypeField(context: FieldContext): void {
|
|||||||
optionsType: OptionsType.Dropdown
|
optionsType: OptionsType.Dropdown
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
assert(typeof context.fieldInfo.options === 'object', `FieldInfo.options must be an object if it is not an array`);
|
throwUnless(typeof context.fieldInfo.options === 'object', loc.optionsNotObjectOrArray);
|
||||||
assert('optionsType' in context.fieldInfo.options, `When FieldInfo.options is an object it must have 'optionsType' property`);
|
throwUnless('optionsType' in context.fieldInfo.options, loc.optionsTypeNotFound);
|
||||||
|
if (context.fieldInfo.options.source) {
|
||||||
|
try {
|
||||||
|
let optionsSource: IOptionsSource;
|
||||||
|
switch (context.fieldInfo.options.source.type) {
|
||||||
|
case OptionsSourceType.ArcControllersOptionsSource:
|
||||||
|
optionsSource = new ArcControllersOptionsSource(context.fieldInfo.options.source.variableNames, context.fieldInfo.options.source.type);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(loc.noOptionsSourceDefined(context.fieldInfo.options.source.type));
|
||||||
|
}
|
||||||
|
context.fieldInfo.options.source = optionsSource;
|
||||||
|
context.fieldInfo.options.values = await context.fieldInfo.options.source.getOptions();
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
disableControlButtons(context.container);
|
||||||
|
context.container.message = {
|
||||||
|
text: getErrorMessage(e),
|
||||||
|
description: '',
|
||||||
|
level: azdata.window.MessageLevel.Error
|
||||||
|
};
|
||||||
|
context.fieldInfo.options.values = [];
|
||||||
|
}
|
||||||
|
context.fieldInfo.subFields = context.fieldInfo.subFields || [];
|
||||||
|
}
|
||||||
|
let optionsComponent: InputComponent;
|
||||||
if (context.fieldInfo.options.optionsType === OptionsType.Radio) {
|
if (context.fieldInfo.options.optionsType === OptionsType.Radio) {
|
||||||
processRadioOptionsTypeField(context);
|
optionsComponent = await processRadioOptionsTypeField(context);
|
||||||
} else {
|
} else {
|
||||||
assert(context.fieldInfo.options.optionsType === OptionsType.Dropdown, `When optionsType is not ${OptionsType.Radio} then it must be ${OptionsType.Dropdown}`);
|
throwUnless(context.fieldInfo.options.optionsType === OptionsType.Dropdown, loc.optionsTypeRadioOrDropdown);
|
||||||
processDropdownOptionsTypeField(context);
|
optionsComponent = processDropdownOptionsTypeField(context);
|
||||||
|
}
|
||||||
|
if (context.fieldInfo.options.source) {
|
||||||
|
const optionsSource = context.fieldInfo.options.source;
|
||||||
|
for (const key of Object.keys(optionsSource.variableNames ?? {})) {
|
||||||
|
context.fieldInfo.subFields!.push({
|
||||||
|
label: context.fieldInfo.label,
|
||||||
|
variableName: optionsSource.variableNames[key]
|
||||||
|
});
|
||||||
|
context.onNewInputComponentCreated(optionsSource.variableNames[key], {
|
||||||
|
component: optionsComponent,
|
||||||
|
inputValueTransformer: async (controllerName: string) => {
|
||||||
|
try {
|
||||||
|
const variableValue = await optionsSource.getVariableValue(key, controllerName);
|
||||||
|
return variableValue;
|
||||||
|
} catch (e) {
|
||||||
|
disableControlButtons(context.container);
|
||||||
|
context.container.message = {
|
||||||
|
text: getErrorMessage(e),
|
||||||
|
description: '',
|
||||||
|
level: azdata.window.MessageLevel.Error
|
||||||
|
};
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
isPassword: optionsSource.getIsPassword(key)
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function processDropdownOptionsTypeField(context: FieldContext): void {
|
function processDropdownOptionsTypeField(context: FieldContext): azdata.DropDownComponent {
|
||||||
const label = createLabel(context.view, { text: context.fieldInfo.label, description: context.fieldInfo.description, required: context.fieldInfo.required, width: context.fieldInfo.labelWidth, cssStyles: context.fieldInfo.labelCSSStyles });
|
const label = createLabel(context.view, { text: context.fieldInfo.label, description: context.fieldInfo.description, required: context.fieldInfo.required, width: context.fieldInfo.labelWidth, cssStyles: context.fieldInfo.labelCSSStyles });
|
||||||
const options = context.fieldInfo.options as OptionsInfo;
|
const options = context.fieldInfo.options as OptionsInfo;
|
||||||
const dropdown = createDropdown(context.view, {
|
const dropdown = createDropdown(context.view, {
|
||||||
@@ -427,6 +506,7 @@ function processDropdownOptionsTypeField(context: FieldContext): void {
|
|||||||
dropdown.fireOnTextChange = true;
|
dropdown.fireOnTextChange = true;
|
||||||
context.onNewInputComponentCreated(context.fieldInfo.variableName!, { component: dropdown });
|
context.onNewInputComponentCreated(context.fieldInfo.variableName!, { component: dropdown });
|
||||||
addLabelInputPairToContainer(context.view, context.components, label, dropdown, context.fieldInfo);
|
addLabelInputPairToContainer(context.view, context.components, label, dropdown, context.fieldInfo);
|
||||||
|
return dropdown;
|
||||||
}
|
}
|
||||||
|
|
||||||
function processDateTimeTextField(context: FieldContext): void {
|
function processDateTimeTextField(context: FieldContext): void {
|
||||||
@@ -460,6 +540,7 @@ function processNumberField(context: FieldContext): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function processTextField(context: FieldContext): void {
|
function processTextField(context: FieldContext): void {
|
||||||
|
let validationRegex: RegExp | undefined = context.fieldInfo.textValidationRequired ? new RegExp(context.fieldInfo.textValidationRegex!) : undefined;
|
||||||
const label = createLabel(context.view, { text: context.fieldInfo.label, description: context.fieldInfo.description, required: context.fieldInfo.required, width: context.fieldInfo.labelWidth, cssStyles: context.fieldInfo.labelCSSStyles });
|
const label = createLabel(context.view, { text: context.fieldInfo.label, description: context.fieldInfo.description, required: context.fieldInfo.required, width: context.fieldInfo.labelWidth, cssStyles: context.fieldInfo.labelCSSStyles });
|
||||||
const input = createTextInput(context.view, {
|
const input = createTextInput(context.view, {
|
||||||
defaultValue: context.fieldInfo.defaultValue,
|
defaultValue: context.fieldInfo.defaultValue,
|
||||||
@@ -467,16 +548,16 @@ function processTextField(context: FieldContext): void {
|
|||||||
required: context.fieldInfo.required,
|
required: context.fieldInfo.required,
|
||||||
placeHolder: context.fieldInfo.placeHolder,
|
placeHolder: context.fieldInfo.placeHolder,
|
||||||
width: context.fieldInfo.inputWidth,
|
width: context.fieldInfo.inputWidth,
|
||||||
enabled: context.fieldInfo.enabled
|
enabled: context.fieldInfo.enabled,
|
||||||
|
validationRegex: validationRegex,
|
||||||
|
validationErrorMessage: context.fieldInfo.textValidationDescription
|
||||||
});
|
});
|
||||||
context.onNewInputComponentCreated(context.fieldInfo.variableName!, { component: input });
|
context.onNewInputComponentCreated(context.fieldInfo.variableName!, { component: input });
|
||||||
addLabelInputPairToContainer(context.view, context.components, label, input, context.fieldInfo);
|
addLabelInputPairToContainer(context.view, context.components, label, input, context.fieldInfo);
|
||||||
|
|
||||||
if (context.fieldInfo.textValidationRequired) {
|
if (context.fieldInfo.textValidationRequired) {
|
||||||
let validationRegex: RegExp = new RegExp(context.fieldInfo.textValidationRegex!);
|
|
||||||
|
|
||||||
const removeInvalidInputMessage = (): void => {
|
const removeInvalidInputMessage = (): void => {
|
||||||
if (validationRegex.test(input.value!)) { // input is valid
|
if (validationRegex!.test(input.value!)) { // input is valid
|
||||||
removeValidationMessage(context.container, context.fieldInfo.textValidationDescription!);
|
removeValidationMessage(context.container, context.fieldInfo.textValidationDescription!);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -486,7 +567,7 @@ function processTextField(context: FieldContext): void {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
const inputValidator: Validator = (): { valid: boolean; message: string; } => {
|
const inputValidator: Validator = (): { valid: boolean; message: string; } => {
|
||||||
const inputIsValid = validationRegex.test(input.value!);
|
const inputIsValid = validationRegex!.test(input.value!);
|
||||||
return { valid: inputIsValid, message: context.fieldInfo.textValidationDescription! };
|
return { valid: inputIsValid, message: context.fieldInfo.textValidationDescription! };
|
||||||
};
|
};
|
||||||
context.onNewValidatorCreated(inputValidator);
|
context.onNewValidatorCreated(inputValidator);
|
||||||
@@ -579,8 +660,8 @@ function processEvaluatedTextField(context: FieldContext): ReadOnlyFieldInputs {
|
|||||||
const readOnlyField = processReadonlyTextField(context, false /*allowEvaluation*/);
|
const readOnlyField = processReadonlyTextField(context, false /*allowEvaluation*/);
|
||||||
context.onNewInputComponentCreated(context.fieldInfo.variableName || context.fieldInfo.label, {
|
context.onNewInputComponentCreated(context.fieldInfo.variableName || context.fieldInfo.label, {
|
||||||
component: readOnlyField.text!,
|
component: readOnlyField.text!,
|
||||||
inputValueTransformer: () => {
|
inputValueTransformer: async () => {
|
||||||
readOnlyField.text!.value = substituteVariableValues(context.inputComponents, context.fieldInfo.defaultValue);
|
readOnlyField.text!.value = await substituteVariableValues(context.inputComponents, context.fieldInfo.defaultValue);
|
||||||
return readOnlyField.text?.value!;
|
return readOnlyField.text?.value!;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -596,14 +677,15 @@ function processEvaluatedTextField(context: FieldContext): ReadOnlyFieldInputs {
|
|||||||
* @param inputValue
|
* @param inputValue
|
||||||
* @param inputComponents
|
* @param inputComponents
|
||||||
*/
|
*/
|
||||||
function substituteVariableValues(inputComponents: InputComponents, inputValue?: string): string | undefined {
|
async function substituteVariableValues(inputComponents: InputComponents, inputValue?: string): Promise<string | undefined> {
|
||||||
Object.keys(inputComponents)
|
await Promise.all(Object.keys(inputComponents)
|
||||||
.filter(key => key.startsWith(NoteBookEnvironmentVariablePrefix))
|
.filter(key => key.startsWith(NoteBookEnvironmentVariablePrefix))
|
||||||
.forEach(key => {
|
.map(async key => {
|
||||||
const value = getInputComponentValue(inputComponents, key) ?? '<undefined>';
|
const value = (await getInputComponentValue(inputComponents, key)) ?? '<undefined>';
|
||||||
const re: RegExp = new RegExp(`\\\$\\\(${key}\\\)`, 'gi');
|
const re: RegExp = new RegExp(`\\\$\\\(${key}\\\)`, 'gi');
|
||||||
inputValue = inputValue?.replace(re, value);
|
inputValue = inputValue?.replace(re, value);
|
||||||
});
|
})
|
||||||
|
);
|
||||||
return inputValue;
|
return inputValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -984,7 +1066,7 @@ async function handleSelectedAccountChanged(
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await (await apiService.getAzurecoreApi()).getSubscriptions(selectedAccount, true);
|
const response = await apiService.azurecoreApi.getSubscriptions(selectedAccount, true);
|
||||||
if (!response) {
|
if (!response) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1099,7 +1181,7 @@ async function handleSelectedSubscriptionChanged(context: AzureAccountFieldConte
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const response = await (await apiService.getAzurecoreApi()).getResourceGroups(selectedAccount, selectedSubscription, true);
|
const response = await apiService.azurecoreApi.getResourceGroups(selectedAccount, selectedSubscription, true);
|
||||||
if (!response) {
|
if (!response) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1150,8 +1232,7 @@ async function processAzureLocationsField(context: AzureLocationsFieldContext):
|
|||||||
width: context.fieldInfo.labelWidth,
|
width: context.fieldInfo.labelWidth,
|
||||||
cssStyles: context.fieldInfo.labelCSSStyles
|
cssStyles: context.fieldInfo.labelCSSStyles
|
||||||
});
|
});
|
||||||
const azurecoreApi = await apiService.getAzurecoreApi();
|
const locationValues = context.fieldInfo.locations?.map(l => { return { name: l, displayName: apiService.azurecoreApi.getRegionDisplayName(l) }; });
|
||||||
const locationValues = context.fieldInfo.locations?.map(l => { return { name: l, displayName: azurecoreApi.getRegionDisplayName(l) }; });
|
|
||||||
const locationDropdown = createDropdown(context.view, {
|
const locationDropdown = createDropdown(context.view, {
|
||||||
defaultValue: locationValues?.find(l => l.name === context.fieldInfo.defaultValue),
|
defaultValue: locationValues?.find(l => l.name === context.fieldInfo.defaultValue),
|
||||||
width: context.fieldInfo.inputWidth,
|
width: context.fieldInfo.inputWidth,
|
||||||
@@ -1174,7 +1255,7 @@ async function processAzureLocationsField(context: AzureLocationsFieldContext):
|
|||||||
label: label.value!,
|
label: label.value!,
|
||||||
variableName: context.fieldInfo.displayLocationVariableName
|
variableName: context.fieldInfo.displayLocationVariableName
|
||||||
});
|
});
|
||||||
context.onNewInputComponentCreated(context.fieldInfo.displayLocationVariableName, { component: locationDropdown, inputValueTransformer: (value => azurecoreApi.getRegionDisplayName(value)) });
|
context.onNewInputComponentCreated(context.fieldInfo.displayLocationVariableName, { component: locationDropdown, inputValueTransformer: (value => apiService.azurecoreApi.getRegionDisplayName(value)) });
|
||||||
}
|
}
|
||||||
addLabelInputPairToContainer(context.view, context.components, label, locationDropdown, context.fieldInfo);
|
addLabelInputPairToContainer(context.view, context.components, label, locationDropdown, context.fieldInfo);
|
||||||
return locationDropdown;
|
return locationDropdown;
|
||||||
@@ -1207,14 +1288,14 @@ export function getPasswordMismatchMessage(fieldName: string): string {
|
|||||||
return localize('passwordNotMatch', "{0} doesn't match the confirmation password", fieldName);
|
return localize('passwordNotMatch', "{0} doesn't match the confirmation password", fieldName);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setModelValues(inputComponents: InputComponents, model: Model): void {
|
export async function setModelValues(inputComponents: InputComponents, model: Model): Promise<void> {
|
||||||
Object.keys(inputComponents).forEach(key => {
|
await Promise.all(Object.keys(inputComponents).map(async key => {
|
||||||
const value = getInputComponentValue(inputComponents, key);
|
const value = await getInputComponentValue(inputComponents, key);
|
||||||
model.setPropertyValue(key, value);
|
model.setPropertyValue(key, value);
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
function getInputComponentValue(inputComponents: InputComponents, key: string): string | undefined {
|
async function getInputComponentValue(inputComponents: InputComponents, key: string): Promise<string | undefined> {
|
||||||
const input = inputComponents[key].component;
|
const input = inputComponents[key].component;
|
||||||
if (input === undefined) {
|
if (input === undefined) {
|
||||||
return undefined;
|
return undefined;
|
||||||
@@ -1236,7 +1317,10 @@ function getInputComponentValue(inputComponents: InputComponents, key: string):
|
|||||||
}
|
}
|
||||||
const inputValueTransformer = inputComponents[key].inputValueTransformer;
|
const inputValueTransformer = inputComponents[key].inputValueTransformer;
|
||||||
if (inputValueTransformer) {
|
if (inputValueTransformer) {
|
||||||
value = inputValueTransformer(value || '');
|
value = inputValueTransformer(value ?? '');
|
||||||
|
if (typeof value !== 'string') {
|
||||||
|
value = await value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export class NotebookWizard extends WizardBase<NotebookWizard, NotebookWizardPag
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected async onOk(): Promise<void> {
|
protected async onOk(): Promise<void> {
|
||||||
setModelValues(this.inputComponents, this.model);
|
await setModelValues(this.inputComponents, this.model);
|
||||||
const env: NodeJS.ProcessEnv = {};
|
const env: NodeJS.ProcessEnv = {};
|
||||||
this.model.setEnvironmentVariables(env, (varName) => {
|
this.model.setEnvironmentVariables(env, (varName) => {
|
||||||
const isPassword = !!this.inputComponents[varName]?.isPassword;
|
const isPassword = !!this.inputComponents[varName]?.isPassword;
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export class NotebookWizardAutoSummaryPage extends NotebookWizardPage {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public onLeave(): void {
|
public async onLeave(): Promise<void> {
|
||||||
this.wizard.wizardObject.message = { text: '' };
|
this.wizard.wizardObject.message = { text: '' };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ export class NotebookWizardPage extends WizardPageBase<NotebookWizard> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public onLeave(): void {
|
public async onLeave(): Promise<void> {
|
||||||
// The following callback registration clears previous navigation validators.
|
// The following callback registration clears previous navigation validators.
|
||||||
this.wizard.wizardObject.registerNavigationValidator((pcInfo) => {
|
this.wizard.wizardObject.registerNavigationValidator((pcInfo) => {
|
||||||
return true;
|
return true;
|
||||||
@@ -66,7 +66,7 @@ export class NotebookWizardPage extends WizardPageBase<NotebookWizard> {
|
|||||||
|
|
||||||
public async onEnter(): Promise<void> {
|
public async onEnter(): Promise<void> {
|
||||||
if (this.pageInfo.isSummaryPage) {
|
if (this.pageInfo.isSummaryPage) {
|
||||||
setModelValues(this.wizard.inputComponents, this.wizard.model);
|
await setModelValues(this.wizard.inputComponents, this.wizard.model);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.wizard.wizardObject.registerNavigationValidator((pcInfo) => {
|
this.wizard.wizardObject.registerNavigationValidator((pcInfo) => {
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ export class ResourceTypePickerDialog extends DialogBase {
|
|||||||
private _agreementContainer!: azdata.DivContainer;
|
private _agreementContainer!: azdata.DivContainer;
|
||||||
private _agreementCheckboxChecked: boolean = false;
|
private _agreementCheckboxChecked: boolean = false;
|
||||||
private _installToolButton: azdata.window.Button;
|
private _installToolButton: azdata.window.Button;
|
||||||
|
private _recheckEulaButton: azdata.window.Button;
|
||||||
private _installationInProgress: boolean = false;
|
private _installationInProgress: boolean = false;
|
||||||
private _tools: ITool[] = [];
|
private _tools: ITool[] = [];
|
||||||
|
|
||||||
@@ -37,10 +38,16 @@ export class ResourceTypePickerDialog extends DialogBase {
|
|||||||
super(localize('resourceTypePickerDialog.title', "Select the deployment options"), 'ResourceTypePickerDialog', true);
|
super(localize('resourceTypePickerDialog.title', "Select the deployment options"), 'ResourceTypePickerDialog', true);
|
||||||
this._selectedResourceType = defaultResourceType;
|
this._selectedResourceType = defaultResourceType;
|
||||||
this._installToolButton = azdata.window.createButton(localize('deploymentDialog.InstallToolsButton', "Install tools"));
|
this._installToolButton = azdata.window.createButton(localize('deploymentDialog.InstallToolsButton', "Install tools"));
|
||||||
|
this._recheckEulaButton = azdata.window.createButton(localize('deploymentDialog.RecheckEulaButton', "Validate EULA"));
|
||||||
|
this._recheckEulaButton.hidden = true;
|
||||||
this._toDispose.push(this._installToolButton.onClick(() => {
|
this._toDispose.push(this._installToolButton.onClick(() => {
|
||||||
this.installTools().catch(error => console.log(error));
|
this.installTools().catch(error => console.log(error));
|
||||||
}));
|
}));
|
||||||
this._dialogObject.customButtons = [this._installToolButton];
|
this._toDispose.push(this._recheckEulaButton.onClick(() => {
|
||||||
|
this._dialogObject.message = { text: '' }; // clear any previous message.
|
||||||
|
this._dialogObject.okButton.enabled = this.validateToolsEula(); // re-enable the okButton if validation succeeds.
|
||||||
|
}));
|
||||||
|
this._dialogObject.customButtons = [this._installToolButton, this._recheckEulaButton];
|
||||||
this._installToolButton.hidden = true;
|
this._installToolButton.hidden = true;
|
||||||
this._dialogObject.okButton.label = localize('deploymentDialog.OKButtonText', "Select");
|
this._dialogObject.okButton.label = localize('deploymentDialog.OKButtonText', "Select");
|
||||||
this._dialogObject.okButton.enabled = false; // this is enabled after all tools are discovered.
|
this._dialogObject.okButton.enabled = false; // this is enabled after all tools are discovered.
|
||||||
@@ -96,6 +103,8 @@ export class ResourceTypePickerDialog extends DialogBase {
|
|||||||
iconPosition: 'left'
|
iconPosition: 'left'
|
||||||
}).component();
|
}).component();
|
||||||
this._toDispose.push(this._cardGroup.onSelectionChanged(({ cardId }) => {
|
this._toDispose.push(this._cardGroup.onSelectionChanged(({ cardId }) => {
|
||||||
|
this._recheckEulaButton.hidden = true;
|
||||||
|
this._dialogObject.okButton.enabled = true;
|
||||||
const resourceType = resourceTypes.find(rt => { return rt.name === cardId; });
|
const resourceType = resourceTypes.find(rt => { return rt.name === cardId; });
|
||||||
if (resourceType) {
|
if (resourceType) {
|
||||||
this.selectResourceType(resourceType);
|
this.selectResourceType(resourceType);
|
||||||
@@ -176,6 +185,15 @@ export class ResourceTypePickerDialog extends DialogBase {
|
|||||||
|
|
||||||
private selectResourceType(resourceType: ResourceType): void {
|
private selectResourceType(resourceType: ResourceType): void {
|
||||||
this._selectedResourceType = resourceType;
|
this._selectedResourceType = resourceType;
|
||||||
|
|
||||||
|
//handle special case when resource type has different OK button.
|
||||||
|
if (this._selectedResourceType.okButtonText) {
|
||||||
|
this._dialogObject.okButton.label = this._selectedResourceType.okButtonText;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this._dialogObject.okButton.label = localize('deploymentDialog.OKButtonText', "Select");
|
||||||
|
}
|
||||||
|
|
||||||
this._agreementCheckboxChecked = false;
|
this._agreementCheckboxChecked = false;
|
||||||
this._agreementContainer.clearItems();
|
this._agreementContainer.clearItems();
|
||||||
if (resourceType.agreement) {
|
if (resourceType.agreement) {
|
||||||
@@ -270,12 +288,12 @@ export class ResourceTypePickerDialog extends DialogBase {
|
|||||||
return [tool.displayName, tool.description, tool.displayStatus, tool.fullVersion || '', toolRequirement.version || '', tool.installationPathOrAdditionalInformation || ''];
|
return [tool.displayName, tool.description, tool.displayStatus, tool.fullVersion || '', toolRequirement.version || '', tool.installationPathOrAdditionalInformation || ''];
|
||||||
});
|
});
|
||||||
this._installToolButton.hidden = erroredOrFailedTool || minVersionCheckFailed || (toolsToAutoInstall.length === 0);
|
this._installToolButton.hidden = erroredOrFailedTool || minVersionCheckFailed || (toolsToAutoInstall.length === 0);
|
||||||
this._dialogObject.okButton.enabled = !erroredOrFailedTool && messages.length === 0 && !minVersionCheckFailed && (toolsToAutoInstall.length === 0);
|
this._dialogObject.okButton.enabled = !erroredOrFailedTool && messages.length === 0 && !minVersionCheckFailed && (toolsToAutoInstall.length === 0) && this.validateToolsEula();
|
||||||
if (messages.length !== 0) {
|
if (messages.length !== 0) {
|
||||||
if (messages.length > 1) {
|
if (messages.length > 1) {
|
||||||
messages = messages.map(message => `• ${message}`);
|
messages = messages.map(message => `• ${message}`);
|
||||||
}
|
}
|
||||||
messages.push(localize('deploymentDialog.VersionInformationDebugHint', "You will need to restart Azure Data Studio if the tools are installed manually after Azure Data Studio is launched to pick up the updated PATH environment variable. You may find additional details in 'Deployments' output channel"));
|
messages.push(localize('deploymentDialog.VersionInformationDebugHint', "You will need to restart Azure Data Studio if the tools are installed manually to pick up the change. You may find additional details in 'Deployments' and 'azdata' output channels"));
|
||||||
this._dialogObject.message = {
|
this._dialogObject.message = {
|
||||||
level: azdata.window.MessageLevel.Error,
|
level: azdata.window.MessageLevel.Error,
|
||||||
text: messages.join(EOL)
|
text: messages.join(EOL)
|
||||||
@@ -305,6 +323,21 @@ export class ResourceTypePickerDialog extends DialogBase {
|
|||||||
this._toolsLoadingComponent.loading = false;
|
this._toolsLoadingComponent.loading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private validateToolsEula(): boolean {
|
||||||
|
const validationSucceeded = this._tools.every(tool => {
|
||||||
|
const eulaValidated = tool.validateEula();
|
||||||
|
if (!eulaValidated) {
|
||||||
|
this._dialogObject.message = {
|
||||||
|
level: azdata.window.MessageLevel.Error,
|
||||||
|
text: tool.statusDescription!
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return eulaValidated;
|
||||||
|
});
|
||||||
|
this._recheckEulaButton.hidden = validationSucceeded;
|
||||||
|
return validationSucceeded;
|
||||||
|
}
|
||||||
|
|
||||||
private get toolRequirements() {
|
private get toolRequirements() {
|
||||||
return this.getCurrentProvider().requiredTools;
|
return this.getCurrentProvider().requiredTools;
|
||||||
}
|
}
|
||||||
@@ -347,7 +380,7 @@ export class ResourceTypePickerDialog extends DialogBase {
|
|||||||
return this._selectedResourceType.getProvider(options)!;
|
return this._selectedResourceType.getProvider(options)!;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected onComplete(): void {
|
protected async onComplete(): Promise<void> {
|
||||||
this.toolsService.toolsForCurrentProvider = this._tools;
|
this.toolsService.toolsForCurrentProvider = this._tools;
|
||||||
this.resourceTypeService.startDeployment(this.getCurrentProvider());
|
this.resourceTypeService.startDeployment(this.getCurrentProvider());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export abstract class WizardBase<T, P extends WizardPageBase<T>, M extends Model
|
|||||||
this.toDispose.push(this.wizardObject.onPageChanged(async (e) => {
|
this.toDispose.push(this.wizardObject.onPageChanged(async (e) => {
|
||||||
let previousPage = this.pages[e.lastPage];
|
let previousPage = this.pages[e.lastPage];
|
||||||
let newPage = this.pages[e.newPage];
|
let newPage = this.pages[e.newPage];
|
||||||
previousPage.onLeave();
|
await previousPage.onLeave();
|
||||||
await newPage.onEnter();
|
await newPage.onEnter();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export abstract class WizardPageBase<T> {
|
|||||||
|
|
||||||
public async onEnter(): Promise<void> { }
|
public async onEnter(): Promise<void> { }
|
||||||
|
|
||||||
public onLeave(): void { }
|
public async onLeave(): Promise<void> { }
|
||||||
|
|
||||||
public abstract initialize(): void;
|
public abstract initialize(): void;
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,15 @@ export function setEnvironmentVariablesForInstallPaths(tools: ITool[], env: Node
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function assert(condition: boolean, message?: string): asserts condition {
|
/**
|
||||||
|
* Throws an Error with given {@link message} unless {@link condition} is true.
|
||||||
|
* This also tells the typescript compiler that the condition is 'truthy' in the remainder of the scope
|
||||||
|
* where this function was called.
|
||||||
|
*
|
||||||
|
* @param condition
|
||||||
|
* @param message
|
||||||
|
*/
|
||||||
|
export function throwUnless(condition: boolean, message?: string): asserts condition {
|
||||||
if (!condition) {
|
if (!condition) {
|
||||||
throw new Error(message);
|
throw new Error(message);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -194,6 +194,11 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-5.2.7.tgz#315d570ccb56c53452ff8638738df60726d5b6ea"
|
resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-5.2.7.tgz#315d570ccb56c53452ff8638738df60726d5b6ea"
|
||||||
integrity sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==
|
integrity sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==
|
||||||
|
|
||||||
|
"@types/semver@^7.3.1":
|
||||||
|
version "7.3.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.4.tgz#43d7168fec6fa0988bb1a513a697b29296721afb"
|
||||||
|
integrity sha512-+nVsLKlcUCeMzD2ufHEYuJ9a2ovstb6Dp52A5VsoKxDXgvE051XgHI/33I1EymwkRGQkwnA0LkhnUzituGs4EQ==
|
||||||
|
|
||||||
"@types/yamljs@0.2.30":
|
"@types/yamljs@0.2.30":
|
||||||
version "0.2.30"
|
version "0.2.30"
|
||||||
resolved "https://registry.yarnpkg.com/@types/yamljs/-/yamljs-0.2.30.tgz#d034e1d329e46e8d0f737c9a8db97f68f81b5382"
|
resolved "https://registry.yarnpkg.com/@types/yamljs/-/yamljs-0.2.30.tgz#d034e1d329e46e8d0f737c9a8db97f68f81b5382"
|
||||||
@@ -694,6 +699,11 @@ semver@^6.0.0, semver@^6.3.0:
|
|||||||
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
|
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
|
||||||
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
|
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
|
||||||
|
|
||||||
|
semver@^7.3.2:
|
||||||
|
version "7.3.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938"
|
||||||
|
integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==
|
||||||
|
|
||||||
should-equal@^2.0.0:
|
should-equal@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/should-equal/-/should-equal-2.0.0.tgz#6072cf83047360867e68e98b09d71143d04ee0c3"
|
resolved "https://registry.yarnpkg.com/should-equal/-/should-equal-2.0.0.tgz#6072cf83047360867e68e98b09d71143d04ee0c3"
|
||||||
|
|||||||
@@ -2,12 +2,12 @@
|
|||||||
"name": "sql-database-projects",
|
"name": "sql-database-projects",
|
||||||
"displayName": "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.",
|
"description": "The SQL Database Projects extension for Azure Data Studio allows users to develop and publish database schemas.",
|
||||||
"version": "0.1.2",
|
"version": "0.1.3",
|
||||||
"publisher": "Microsoft",
|
"publisher": "Microsoft",
|
||||||
"preview": true,
|
"preview": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"vscode": "^1.30.1",
|
"vscode": "^1.30.1",
|
||||||
"azdata": ">=1.20.0"
|
"azdata": ">=1.22.0"
|
||||||
},
|
},
|
||||||
"license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/main/LICENSE.txt",
|
"license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/main/LICENSE.txt",
|
||||||
"icon": "images/sqlDatabaseProjects.png",
|
"icon": "images/sqlDatabaseProjects.png",
|
||||||
@@ -26,8 +26,7 @@
|
|||||||
},
|
},
|
||||||
"extensionDependencies": [
|
"extensionDependencies": [
|
||||||
"Microsoft.mssql",
|
"Microsoft.mssql",
|
||||||
"Microsoft.schema-compare",
|
"Microsoft.schema-compare"
|
||||||
"Microsoft.data-workspace"
|
|
||||||
],
|
],
|
||||||
"contributes": {
|
"contributes": {
|
||||||
"projects": [
|
"projects": [
|
||||||
@@ -57,12 +56,14 @@
|
|||||||
{
|
{
|
||||||
"command": "sqlDatabaseProjects.new",
|
"command": "sqlDatabaseProjects.new",
|
||||||
"title": "%sqlDatabaseProjects.new%",
|
"title": "%sqlDatabaseProjects.new%",
|
||||||
"category": "%sqlDatabaseProjects.displayName%"
|
"category": "%sqlDatabaseProjects.displayName%",
|
||||||
|
"icon": "$(add)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "sqlDatabaseProjects.open",
|
"command": "sqlDatabaseProjects.open",
|
||||||
"title": "%sqlDatabaseProjects.open%",
|
"title": "%sqlDatabaseProjects.open%",
|
||||||
"category": "%sqlDatabaseProjects.displayName%"
|
"category": "%sqlDatabaseProjects.displayName%",
|
||||||
|
"icon": "$(folder-opened)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "sqlDatabaseProjects.close",
|
"command": "sqlDatabaseProjects.close",
|
||||||
@@ -245,6 +246,18 @@
|
|||||||
"when": "false"
|
"when": "false"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"view/title": [
|
||||||
|
{
|
||||||
|
"command": "sqlDatabaseProjects.new",
|
||||||
|
"when": "view == sqlDatabaseProjectsView",
|
||||||
|
"group": "navigation@1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "sqlDatabaseProjects.open",
|
||||||
|
"when": "view == sqlDatabaseProjectsView",
|
||||||
|
"group": "navigation@2"
|
||||||
|
}
|
||||||
|
],
|
||||||
"view/item/context": [
|
"view/item/context": [
|
||||||
{
|
{
|
||||||
"command": "sqlDatabaseProjects.build",
|
"command": "sqlDatabaseProjects.build",
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ export const sameDatabase = localize('sameDatabase', "Same database");
|
|||||||
export const differentDbSameServer = localize('differentDbSameServer', "Different database, same server");
|
export const differentDbSameServer = localize('differentDbSameServer', "Different database, same server");
|
||||||
export const differentDbDifferentServer = localize('differentDbDifferentServer', "Different database, different server");
|
export const differentDbDifferentServer = localize('differentDbDifferentServer', "Different database, different server");
|
||||||
export const systemDbLocationDropdownValues = [differentDbSameServer];
|
export const systemDbLocationDropdownValues = [differentDbSameServer];
|
||||||
export const locationDropdownValues = [sameDatabase, differentDbSameServer, differentDbDifferentServer];
|
export const locationDropdownValues = [differentDbSameServer, differentDbDifferentServer];
|
||||||
export const databaseName = localize('databaseName', "Database name");
|
export const databaseName = localize('databaseName', "Database name");
|
||||||
export const databaseVariable = localize('databaseVariable', "Database variable");
|
export const databaseVariable = localize('databaseVariable', "Database variable");
|
||||||
export const serverName = localize('serverName', "Server name");
|
export const serverName = localize('serverName', "Server name");
|
||||||
@@ -120,7 +120,7 @@ export const enterSystemDbName = localize('enterSystemDbName', "Enter a database
|
|||||||
export const databaseNameRequiredVariableOptional = localize('databaseNameRequiredVariableOptional', "A database name is required. The database variable is optional.");
|
export const databaseNameRequiredVariableOptional = localize('databaseNameRequiredVariableOptional', "A database name is required. The database variable is optional.");
|
||||||
export const databaseNameServerNameVariableRequired = localize('databaseNameServerNameVariableRequired', "A database name, server name, and server variable are required. The database variable is optional");
|
export const databaseNameServerNameVariableRequired = localize('databaseNameServerNameVariableRequired', "A database name, server name, and server variable are required. The database variable is optional");
|
||||||
export const otherServer = 'OtherServer';
|
export const otherServer = 'OtherServer';
|
||||||
export const otherSeverVariable = '$(OtherServer)';
|
export const otherSeverVariable = 'OtherServer';
|
||||||
export const databaseProject = localize('databaseProject', "Database project");
|
export const databaseProject = localize('databaseProject', "Database project");
|
||||||
|
|
||||||
// Error messages
|
// Error messages
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
|
|
||||||
import * as azdata from 'azdata';
|
import * as azdata from 'azdata';
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import * as dataworkspace from 'dataworkspace';
|
|
||||||
import * as templates from '../templates/templates';
|
import * as templates from '../templates/templates';
|
||||||
import * as constants from '../common/constants';
|
import * as constants from '../common/constants';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
@@ -19,7 +18,6 @@ import { NetCoreTool } from '../tools/netcoreTool';
|
|||||||
import { Project } from '../models/project';
|
import { Project } from '../models/project';
|
||||||
import { FileNode, FolderNode } from '../models/tree/fileFolderTreeItem';
|
import { FileNode, FolderNode } from '../models/tree/fileFolderTreeItem';
|
||||||
import { IconPathHelper } from '../common/iconHelper';
|
import { IconPathHelper } from '../common/iconHelper';
|
||||||
import { SqlDatabaseProjectProvider } from '../projectProvider/projectProvider';
|
|
||||||
|
|
||||||
const SQL_DATABASE_PROJECTS_VIEW_ID = 'sqlDatabaseProjectsView';
|
const SQL_DATABASE_PROJECTS_VIEW_ID = 'sqlDatabaseProjectsView';
|
||||||
|
|
||||||
@@ -79,7 +77,6 @@ export default class MainController implements vscode.Disposable {
|
|||||||
vscode.commands.registerCommand('sqlDatabaseProjects.exclude', async (node: FileNode | FolderNode) => { await this.projectsController.exclude(node); });
|
vscode.commands.registerCommand('sqlDatabaseProjects.exclude', async (node: FileNode | FolderNode) => { await this.projectsController.exclude(node); });
|
||||||
|
|
||||||
IconPathHelper.setExtensionContext(this.extensionContext);
|
IconPathHelper.setExtensionContext(this.extensionContext);
|
||||||
this.registerProjectProvider();
|
|
||||||
|
|
||||||
// init view
|
// init view
|
||||||
const treeView = vscode.window.createTreeView(SQL_DATABASE_PROJECTS_VIEW_ID, {
|
const treeView = vscode.window.createTreeView(SQL_DATABASE_PROJECTS_VIEW_ID, {
|
||||||
@@ -189,13 +186,6 @@ export default class MainController implements vscode.Disposable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private registerProjectProvider(): void {
|
|
||||||
const dataWorkspaceApi: dataworkspace.IExtension = <dataworkspace.IExtension>vscode.extensions.getExtension(dataworkspace.extension.name)?.exports;
|
|
||||||
if (dataWorkspaceApi) {
|
|
||||||
dataWorkspaceApi.registerProjectProvider(new SqlDatabaseProjectProvider());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public dispose(): void {
|
public dispose(): void {
|
||||||
this.deactivate();
|
this.deactivate();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -257,6 +257,10 @@ export class AddDatabaseReferenceDialog {
|
|||||||
ariaLabel: constants.databaseProject
|
ariaLabel: constants.databaseProject
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
|
this.projectDropdown.onValueChanged(() => {
|
||||||
|
this.setDefaultDatabaseValues();
|
||||||
|
});
|
||||||
|
|
||||||
// get projects in workspace
|
// get projects in workspace
|
||||||
const workspaceFolders = vscode.workspace.workspaceFolders;
|
const workspaceFolders = vscode.workspace.workspaceFolders;
|
||||||
if (workspaceFolders?.length) {
|
if (workspaceFolders?.length) {
|
||||||
@@ -429,7 +433,7 @@ export class AddDatabaseReferenceDialog {
|
|||||||
switch (this.currentReferenceType) {
|
switch (this.currentReferenceType) {
|
||||||
case ReferenceType.project: {
|
case ReferenceType.project: {
|
||||||
this.databaseNameTextbox!.value = <string>this.projectDropdown?.value;
|
this.databaseNameTextbox!.value = <string>this.projectDropdown?.value;
|
||||||
this.databaseVariableTextbox!.value = `$(${this.projectDropdown?.value})`;
|
this.databaseVariableTextbox!.value = `${this.projectDropdown?.value}`;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ReferenceType.systemDb: {
|
case ReferenceType.systemDb: {
|
||||||
@@ -439,7 +443,7 @@ export class AddDatabaseReferenceDialog {
|
|||||||
case ReferenceType.dacpac: {
|
case ReferenceType.dacpac: {
|
||||||
const dacpacName = this.dacpacTextbox!.value ? path.parse(this.dacpacTextbox!.value!).name : '';
|
const dacpacName = this.dacpacTextbox!.value ? path.parse(this.dacpacTextbox!.value!).name : '';
|
||||||
this.databaseNameTextbox!.value = dacpacName;
|
this.databaseNameTextbox!.value = dacpacName;
|
||||||
this.databaseVariableTextbox!.value = dacpacName ? `$(${dacpacName})` : '';
|
this.databaseVariableTextbox!.value = dacpacName ? `${dacpacName}` : '';
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "azuredatastudio",
|
"name": "azuredatastudio",
|
||||||
"version": "1.22.0",
|
"version": "1.22.1",
|
||||||
"distro": "05f08ef15e584dd5d5b5e3d03d447265869d2b0d",
|
"distro": "05f08ef15e584dd5d5b5e3d03d447265869d2b0d",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Microsoft Corporation"
|
"name": "Microsoft Corporation"
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
"gettingStartedUrl": "https://go.microsoft.com/fwlink/?linkid=862039",
|
"gettingStartedUrl": "https://go.microsoft.com/fwlink/?linkid=862039",
|
||||||
"releaseNotesUrl": "https://go.microsoft.com/fwlink/?linkid=875578",
|
"releaseNotesUrl": "https://go.microsoft.com/fwlink/?linkid=875578",
|
||||||
"documentationUrl": "https://go.microsoft.com/fwlink/?linkid=862277",
|
"documentationUrl": "https://go.microsoft.com/fwlink/?linkid=862277",
|
||||||
"vscodeVersion": "1.48.0",
|
"vscodeVersion": "1.49.0",
|
||||||
"commit": "9ca6200018fc206d67a47229f991901a8a453781",
|
"commit": "9ca6200018fc206d67a47229f991901a8a453781",
|
||||||
"date": "2017-12-15T12:00:00.000Z",
|
"date": "2017-12-15T12:00:00.000Z",
|
||||||
"recommendedExtensions": [
|
"recommendedExtensions": [
|
||||||
|
|||||||
@@ -91,6 +91,28 @@
|
|||||||
"optionsType": "radio"
|
"optionsType": "radio"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "options",
|
||||||
|
"label": "%wizard.data.controllers%",
|
||||||
|
"required": true,
|
||||||
|
"variableName": "AZDATA_NB_VAR_CONTROLLER",
|
||||||
|
"editable": false,
|
||||||
|
"options": {
|
||||||
|
"source": {
|
||||||
|
"type": "ArcControllersOptionsSource",
|
||||||
|
"variableNames": {
|
||||||
|
"endpoint": "AZDATA_NB_VAR_CONTROLLER_ENDPOINT",
|
||||||
|
"username": "AZDATA_NB_VAR_CONTROLLER_USERNAME",
|
||||||
|
"password": "AZDATA_NB_VAR_CONTROLLER_PASSWORD"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"values":[
|
||||||
|
"ignored1",
|
||||||
|
"ignored2"
|
||||||
|
],
|
||||||
|
"optionsType": "dropdown"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"label": "%wizard.dropdown.options.field%",
|
"label": "%wizard.dropdown.options.field%",
|
||||||
"variableName": "AZDATA_NB_VAR_DROPDOWN_OPTIONS",
|
"variableName": "AZDATA_NB_VAR_DROPDOWN_OPTIONS",
|
||||||
@@ -245,7 +267,37 @@
|
|||||||
"type": "readonly_text",
|
"type": "readonly_text",
|
||||||
"isEvaluated": true,
|
"isEvaluated": true,
|
||||||
"defaultValue": "$(AZDATA_NB_VAR_PROFILE)"
|
"defaultValue": "$(AZDATA_NB_VAR_PROFILE)"
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
"label": "%wizard.dropdown.options.field%",
|
||||||
|
"type": "readonly_text",
|
||||||
|
"isEvaluated": true,
|
||||||
|
"defaultValue": "$(AZDATA_NB_VAR_DROPDOWN_OPTIONS)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "%wizard.summary.controller%",
|
||||||
|
"type": "readonly_text",
|
||||||
|
"isEvaluated": true,
|
||||||
|
"defaultValue": "$(AZDATA_NB_VAR_CONTROLLER)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "%wizard.summary.controller.endpoint%",
|
||||||
|
"type": "readonly_text",
|
||||||
|
"isEvaluated": true,
|
||||||
|
"defaultValue": "$(AZDATA_NB_VAR_CONTROLLER_ENDPOINT)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "%wizard.summary.controller.username%",
|
||||||
|
"type": "readonly_text",
|
||||||
|
"isEvaluated": true,
|
||||||
|
"defaultValue": "$(AZDATA_NB_VAR_CONTROLLER_USERNAME)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "%wizard.summary.controller.password%",
|
||||||
|
"type": "readonly_text",
|
||||||
|
"isEvaluated": true,
|
||||||
|
"defaultValue": "$(AZDATA_NB_VAR_CONTROLLER_PASSWORD)"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
"wizard.kube.cluster.context": "Cluster context",
|
"wizard.kube.cluster.context": "Cluster context",
|
||||||
"wizard.cluster.config.profile.title": "Choose the config profile",
|
"wizard.cluster.config.profile.title": "Choose the config profile",
|
||||||
"wizard.cluster.config.profile": "Config profile",
|
"wizard.cluster.config.profile": "Config profile",
|
||||||
|
"wizard.data.controllers": "Pick a data controller",
|
||||||
"wizard.dropdown.options.field": "dropdown field",
|
"wizard.dropdown.options.field": "dropdown field",
|
||||||
"wizard.project.details.title": "Project details",
|
"wizard.project.details.title": "Project details",
|
||||||
"wizard.project.details.description": "Project details for Contoso corporation",
|
"wizard.project.details.description": "Project details for Contoso corporation",
|
||||||
@@ -44,6 +45,10 @@
|
|||||||
"wizard.summary.kube.config.file.path": "Kube config file path",
|
"wizard.summary.kube.config.file.path": "Kube config file path",
|
||||||
"wizard.summary.cluster.context": "Cluster context",
|
"wizard.summary.cluster.context": "Cluster context",
|
||||||
"wizard.summary.profile": "Config profile",
|
"wizard.summary.profile": "Config profile",
|
||||||
|
"wizard.summary.controller": "Controller",
|
||||||
|
"wizard.summary.controller.endpoint": "Controller endpoint",
|
||||||
|
"wizard.summary.controller.username": "Controller username",
|
||||||
|
"wizard.summary.controller.password": "Controller password",
|
||||||
"wizard.data.controller.agreement": "I accept {0} and {1}.",
|
"wizard.data.controller.agreement": "I accept {0} and {1}.",
|
||||||
"contoso.agreement.privacy.statement":"contoso Privacy Statement",
|
"contoso.agreement.privacy.statement":"contoso Privacy Statement",
|
||||||
"wizard.agreement.contosoCmd.eula":"contoso cmd license terms",
|
"wizard.agreement.contosoCmd.eula":"contoso cmd license terms",
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import { escape } from 'sql/base/common/strings';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Definition for column with icon on the left of text.
|
* Definition for column with icon on the left of text.
|
||||||
*/
|
*/
|
||||||
@@ -37,7 +39,7 @@ export class TextWithIconColumn<T extends Slick.SlickData> {
|
|||||||
}
|
}
|
||||||
private formatter(row: number, cell: number, value: any, columnDef: Slick.Column<T>, dataContext: T): string {
|
private formatter(row: number, cell: number, value: any, columnDef: Slick.Column<T>, dataContext: T): string {
|
||||||
const iconColumn = columnDef as TextWithIconColumnDefinition<T>;
|
const iconColumn = columnDef as TextWithIconColumnDefinition<T>;
|
||||||
return `<div class="icon codicon slick-icon-cell-content ${iconColumn.iconCssClassField ? dataContext[iconColumn.iconCssClassField] : ''}">${value}</div>`;
|
return `<div class="icon codicon slick-icon-cell-content ${iconColumn.iconCssClassField ? dataContext[iconColumn.iconCssClassField] : ''}">${escape(value)}</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get definition(): TextWithIconColumnDefinition<T> {
|
public get definition(): TextWithIconColumnDefinition<T> {
|
||||||
|
|||||||
@@ -15,12 +15,12 @@
|
|||||||
<div [class]="iconClass" [style.width]="iconWidth" [style.height]="iconHeight"></div>
|
<div [class]="iconClass" [style.width]="iconWidth" [style.height]="iconHeight"></div>
|
||||||
</div>
|
</div>
|
||||||
<h4 class="card-label">{{label}}</h4>
|
<h4 class="card-label">{{label}}</h4>
|
||||||
<div *ngIf="descriptions.length > 0" class="model-card-description-container">
|
<div *ngIf="descriptions.length > 0" class="model-card-description-container-legacy">
|
||||||
<div *ngFor="let desc of descriptions">
|
<div *ngFor="let desc of descriptions">
|
||||||
<div *ngIf="desc.label; else separator" [style.font-weight]="desc.fontWeight"
|
<div *ngIf="desc.label; else separator" [style.font-weight]="desc.fontWeight"
|
||||||
class="model-card-list-item-description">
|
class="model-card-list-item-description-legacy">
|
||||||
<span>{{desc.label}}</span><span
|
<span>{{desc.label}}</span><span
|
||||||
class="model-card-list-item-description-value">{{desc.value}}</span>
|
class="model-card-list-item-description-value-legacy">{{desc.value}}</span>
|
||||||
</div>
|
</div>
|
||||||
<ng-template #separator>
|
<ng-template #separator>
|
||||||
<div style="height: 12px"></div>
|
<div style="height: 12px"></div>
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
<h4 class="card-label">{{label}}</h4>
|
<h4 class="card-label">{{label}}</h4>
|
||||||
<p class="card-value">{{value}}</p>
|
<p class="card-value">{{value}}</p>
|
||||||
<span *ngIf="actions">
|
<span *ngIf="actions">
|
||||||
<table class="model-table">
|
<table class="model-table-legacy">
|
||||||
<tr *ngFor="let action of actions">
|
<tr *ngFor="let action of actions">
|
||||||
<td class="table-row">{{action.label}}</td>
|
<td class="table-row">{{action.label}}</td>
|
||||||
<td *ngIf="action.actionTitle" class="table-row">
|
<td *ngIf="action.actionTitle" class="table-row">
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
import 'vs/css!./media/card';
|
import 'vs/css!./media/legacycard';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Component, Input, Inject, ChangeDetectorRef, forwardRef, ElementRef, OnDestroy, ViewChild
|
Component, Input, Inject, ChangeDetectorRef, forwardRef, ElementRef, OnDestroy, ViewChild
|
||||||
@@ -117,7 +117,7 @@ export default class CardComponent extends ComponentWithIconBase<azdata.CardProp
|
|||||||
}
|
}
|
||||||
|
|
||||||
public getClass(): string {
|
public getClass(): string {
|
||||||
let cardClass = this.isListItemCard ? 'model-card-list-item' : 'model-card';
|
let cardClass = this.isListItemCard ? 'model-card-list-item-legacy' : 'model-card-legacy';
|
||||||
return (this.selectable && this.selected || this._hasFocus) ? `${cardClass} selected` :
|
return (this.selectable && this.selected || this._hasFocus) ? `${cardClass} selected` :
|
||||||
`${cardClass} unselected`;
|
`${cardClass} unselected`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,8 +34,8 @@
|
|||||||
<span *ngIf="isLabel(c)" (click)="onCellClick(r)">
|
<span *ngIf="isLabel(c)" (click)="onCellClick(r)">
|
||||||
{{cellData.value}}
|
{{cellData.value}}
|
||||||
</span>
|
</span>
|
||||||
<model-component-wrapper *ngIf="isComponent(c) && getItemDescriptor(cellData)"
|
<model-component-wrapper *ngIf="isComponent(c) && getItemDescriptor(cellData.value)"
|
||||||
[descriptor]="getItemDescriptor(cellData)" [modelStore]="modelStore">
|
[descriptor]="getItemDescriptor(cellData.value)" [modelStore]="modelStore">
|
||||||
</model-component-wrapper>
|
</model-component-wrapper>
|
||||||
</td>
|
</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|||||||
200
src/sql/workbench/browser/modelComponents/media/legacycard.css
Normal file
200
src/sql/workbench/browser/modelComponents/media/legacycard.css
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
.model-card-legacy {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
height: 90%;
|
||||||
|
width: auto;
|
||||||
|
margin: 15px;
|
||||||
|
border-width: 1px;
|
||||||
|
border-style: solid;
|
||||||
|
text-align: left;
|
||||||
|
vertical-align: top;
|
||||||
|
border-color: rgb(214, 214, 214);
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-card-legacy .card-content {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
height: auto;
|
||||||
|
width: auto;
|
||||||
|
padding: 10px 45px 20px 45px;
|
||||||
|
min-height: 30px;
|
||||||
|
min-width: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-card-legacy .card-vertical-button {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
text-align: center;
|
||||||
|
height: auto;
|
||||||
|
width: auto;
|
||||||
|
padding: 5px 5px 5px 5px;
|
||||||
|
min-height: 130px;
|
||||||
|
min-width: 130px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-card-legacy .card-label {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-card-legacy .card-value {
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-card-legacy .iconContainer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-grow: 1;
|
||||||
|
border-bottom-width: 1px;
|
||||||
|
border-bottom-style: solid;
|
||||||
|
padding: 10px 0px 10px 0px;
|
||||||
|
border-color: rgb(214, 214, 214);
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-card-legacy .cardIcon {
|
||||||
|
display: inline-block;
|
||||||
|
flex-grow: 1;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
max-width: 50px;
|
||||||
|
max-height: 50px;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-card-legacy .card-status {
|
||||||
|
position: absolute;
|
||||||
|
top: 7px;
|
||||||
|
left: 5px;
|
||||||
|
overflow: hidden;
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-card-legacy .status-content {
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
right: 0px;
|
||||||
|
min-width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-card-list-item-legacy .selection-indicator-container, .model-card-legacy .selection-indicator-container {
|
||||||
|
position: absolute;
|
||||||
|
top: 5px;
|
||||||
|
right: 5px;
|
||||||
|
overflow: hidden;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: white;
|
||||||
|
border-width: 1px;
|
||||||
|
border-color: rgb(0, 120, 215);
|
||||||
|
border-style: solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-card-list-item-legacy .selection-indicator-container, .model-card-legacy .selection-indicator-container {
|
||||||
|
position: absolute;
|
||||||
|
overflow: hidden;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: white;
|
||||||
|
border-width: 1px;
|
||||||
|
border-color: rgb(214, 214, 214);
|
||||||
|
border-style: solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-card-list-item-legacy .selection-indicator-container {
|
||||||
|
top: 10px;
|
||||||
|
right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-card-legacy .selection-indicator-container {
|
||||||
|
top: 5px;
|
||||||
|
right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-card-list-item-legacy .selection-indicator, .model-card-legacy .selection-indicator {
|
||||||
|
margin: 4px;
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: rgb(0, 120, 215);
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-card-legacy .model-table-legacy {
|
||||||
|
border-spacing: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-table-legacy .table-row {
|
||||||
|
width: auto;
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-table-legacy .table-cell {
|
||||||
|
vertical-align: top;
|
||||||
|
padding: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-table-legacy a {
|
||||||
|
cursor: pointer;
|
||||||
|
text-decoration: underline
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-card-list-item-legacy {
|
||||||
|
display: inline-block;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
margin: 5px 0px 5px 0px;
|
||||||
|
border-width: 1px;
|
||||||
|
border-style: solid;
|
||||||
|
text-align: left;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-card-list-item-legacy .list-item-content {
|
||||||
|
height: auto;
|
||||||
|
padding: 5px 26px 5px 5px;
|
||||||
|
min-height: 30px;
|
||||||
|
min-width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-card-list-item-legacy .list-item-icon {
|
||||||
|
background-position: 2px 2px;
|
||||||
|
padding-left: 22px;
|
||||||
|
font-size: 15px;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: 16px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-card-list-item-legacy .list-item-description {
|
||||||
|
padding-left: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-card-description-container-legacy {
|
||||||
|
border-top-width: 1px;
|
||||||
|
border-top-style: solid;
|
||||||
|
border-color: rgb(214, 214, 214);
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-card-list-item-description-legacy {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-card-list-item-description-value-legacy {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
@@ -886,13 +886,14 @@ export class EditDataGridPanel extends GridParentComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
loadValue(item, rowNumber): void {
|
loadValue(item, rowNumber): void {
|
||||||
|
const itemForDisplay = deepClone(item);
|
||||||
if (self.overrideCellFn) {
|
if (self.overrideCellFn) {
|
||||||
let overrideValue = self.overrideCellFn(rowNumber, this._args.column.id, item[this._args.column.id]);
|
let overrideValue = self.overrideCellFn(rowNumber, this._args.column.id, itemForDisplay[this._args.column.id]);
|
||||||
if (overrideValue !== undefined) {
|
if (overrideValue !== undefined) {
|
||||||
item[this._args.column.id] = overrideValue;
|
itemForDisplay[this._args.column.id] = overrideValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this._textEditor.loadValue(item);
|
this._textEditor.loadValue(itemForDisplay);
|
||||||
}
|
}
|
||||||
|
|
||||||
serializeValue(): string {
|
serializeValue(): string {
|
||||||
|
|||||||
@@ -67,17 +67,18 @@ export class TextCellComponent extends CellView implements OnInit, OnChanges {
|
|||||||
this.enableActiveCellEditOnDoubleClick();
|
this.enableActiveCellEditOnDoubleClick();
|
||||||
}
|
}
|
||||||
|
|
||||||
@HostListener('document:keydown.meta.a', ['$event'])
|
@HostListener('document:keydown', ['$event'])
|
||||||
onkeydown(e) {
|
onkeydown(e) {
|
||||||
// use preventDefault() to avoid invoking the editor's select all
|
// use preventDefault() to avoid invoking the editor's select all
|
||||||
// select the active .
|
// select the active .
|
||||||
e.preventDefault();
|
if ((e.ctrlKey || e.metaKey) && e.key === 'a') {
|
||||||
document.execCommand('selectAll');
|
e.preventDefault();
|
||||||
}
|
document.execCommand('selectAll');
|
||||||
|
}
|
||||||
@HostListener('document:keydown.meta.z', ['$event'])
|
if ((e.ctrlKey || e.metaKey) && e.key === 'z') {
|
||||||
onUndo(e) {
|
e.preventDefault();
|
||||||
document.execCommand('undo');
|
document.execCommand('undo');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _content: string | string[];
|
private _content: string | string[];
|
||||||
@@ -432,7 +433,7 @@ export class TextCellComponent extends CellView implements OnInit, OnChanges {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private setTurndownOptions() {
|
private setTurndownOptions() {
|
||||||
this.turndownService = new TurndownService({ 'emDelimiter': '_', 'bulletListMarker': '-' });
|
this.turndownService = new TurndownService({ 'emDelimiter': '_', 'bulletListMarker': '-', 'headingStyle': 'atx' });
|
||||||
this.turndownService.keep(['u', 'mark']);
|
this.turndownService.keep(['u', 'mark']);
|
||||||
this.turndownService.use(turndownPluginGfm.gfm);
|
this.turndownService.use(turndownPluginGfm.gfm);
|
||||||
this.turndownService.addRule('pre', {
|
this.turndownService.addRule('pre', {
|
||||||
@@ -441,6 +442,13 @@ export class TextCellComponent extends CellView implements OnInit, OnChanges {
|
|||||||
return '\n```\n' + node.textContent + '\n```\n';
|
return '\n```\n' + node.textContent + '\n```\n';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
this.turndownService.addRule('caption', {
|
||||||
|
filter: 'caption',
|
||||||
|
replacement: function (content, node) {
|
||||||
|
return `${node.outerHTML}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
});
|
||||||
this.turndownService.addRule('span', {
|
this.turndownService.addRule('span', {
|
||||||
filter: function (node, options) {
|
filter: function (node, options) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -34,9 +34,6 @@ text-cell-component .show-markdown .notebook-preview {
|
|||||||
.notebook-preview.actionselect {
|
.notebook-preview.actionselect {
|
||||||
user-select: text;
|
user-select: text;
|
||||||
}
|
}
|
||||||
text-cell-component code-component .monaco-scrollable-element.editor-scrollable.vs {
|
|
||||||
left: 16px!important;
|
|
||||||
}
|
|
||||||
text-cell-component .monaco-editor .margin,
|
text-cell-component .monaco-editor .margin,
|
||||||
text-cell-component code-component .monaco-editor,
|
text-cell-component code-component .monaco-editor,
|
||||||
text-cell-component code-component .monaco-editor-background,
|
text-cell-component code-component .monaco-editor-background,
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import { onUnexpectedError } from 'vs/base/common/errors';
|
|||||||
import { INotification, INotificationService } from 'vs/platform/notification/common/notification';
|
import { INotification, INotificationService } from 'vs/platform/notification/common/notification';
|
||||||
import Severity from 'vs/base/common/severity';
|
import Severity from 'vs/base/common/severity';
|
||||||
import * as nls from 'vs/nls';
|
import * as nls from 'vs/nls';
|
||||||
|
import { NotebookModel } from 'sql/workbench/services/notebook/browser/models/notebookModel';
|
||||||
|
|
||||||
export type ModeViewSaveHandler = (handle: number) => Thenable<boolean>;
|
export type ModeViewSaveHandler = (handle: number) => Thenable<boolean>;
|
||||||
|
|
||||||
@@ -87,6 +88,12 @@ export class NotebookEditorModel extends EditorModel {
|
|||||||
let dirty = this.textEditorModel instanceof ResourceEditorModel ? false : this.textEditorModel.isDirty();
|
let dirty = this.textEditorModel instanceof ResourceEditorModel ? false : this.textEditorModel.isDirty();
|
||||||
this.setDirty(dirty);
|
this.setDirty(dirty);
|
||||||
}));
|
}));
|
||||||
|
this._register(this.textEditorModel.onDidLoad(async (e) => {
|
||||||
|
if (this.textEditorModel instanceof TextFileEditorModel) {
|
||||||
|
let model = this.getNotebookModel() as NotebookModel;
|
||||||
|
await model.loadContents(model.trustedMode, true);
|
||||||
|
}
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this._dirty = this.textEditorModel instanceof ResourceEditorModel ? false : this.textEditorModel.isDirty();
|
this._dirty = this.textEditorModel instanceof ResourceEditorModel ? false : this.textEditorModel.isDirty();
|
||||||
|
|||||||
@@ -105,33 +105,55 @@ export class NotebookTextFileModel {
|
|||||||
}]);
|
}]);
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
|
} else if (contentChange && areRangePropertiesPopulated(cellGuidRange)) {
|
||||||
|
// If no modelContentChanged event, then we're replacing the entire source for that cell
|
||||||
|
let sourceEnd = this.getSourceEndRange(textEditorModel, contentChange.cells[0].cellGuid);
|
||||||
|
if (sourceEnd) {
|
||||||
|
// Need to subtract one because we're going from 1-based to 0-based
|
||||||
|
let startSpaces: string = repeat(' ', cellGuidRange.startColumn - 1);
|
||||||
|
let escapedQuotesAndBackslashes = contentChange.cells[0].source.join('\n').replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
||||||
|
|
||||||
|
// The text here transforms a string from 'This is a string\n this is another string' to:
|
||||||
|
// This is a string
|
||||||
|
// this is another string
|
||||||
|
|
||||||
|
// Note: Adding 1 to startColumn to avoid overwriting first "
|
||||||
|
textEditorModel.textEditorModel.applyEdits([{
|
||||||
|
range: new Range(this._sourceBeginRange.startLineNumber, this._sourceBeginRange.startColumn + 1, sourceEnd.endLineNumber, sourceEnd.endColumn),
|
||||||
|
text: escapedQuotesAndBackslashes.split(/[\r\n]+/gm).join('\\n\",'.concat(this._eol).concat(startSpaces).concat('\"'))
|
||||||
|
}]);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public transformAndApplyEditForOutputUpdate(contentChange: NotebookContentChange, textEditorModel: ITextEditorModel): boolean {
|
public transformAndApplyEditForOutputUpdate(contentChange: NotebookContentChange, textEditorModel: ITextEditorModel): boolean {
|
||||||
|
this.transformAndApplyEditForClearOutput(contentChange, textEditorModel);
|
||||||
if (Array.isArray(contentChange.cells[0].outputs) && contentChange.cells[0].outputs.length > 0) {
|
if (Array.isArray(contentChange.cells[0].outputs) && contentChange.cells[0].outputs.length > 0) {
|
||||||
let newOutput = JSON.stringify(contentChange.cells[0].outputs[contentChange.cells[0].outputs.length - 1], undefined, ' ');
|
for (let i = 0; i < contentChange.cells[0].outputs.length; i++) {
|
||||||
if (contentChange.cells[0].outputs.length > 1) {
|
let newOutput = JSON.stringify(contentChange.cells[0].outputs[i], undefined, ' ');
|
||||||
newOutput = ', '.concat(newOutput);
|
if (i > 0) {
|
||||||
} else {
|
newOutput = ', '.concat(newOutput);
|
||||||
newOutput = '\n'.concat(newOutput).concat('\n');
|
} else {
|
||||||
}
|
newOutput = '\n'.concat(newOutput).concat('\n');
|
||||||
|
}
|
||||||
|
|
||||||
// Execution count will always be after the end of the outputs in JSON. This is a sanity mechanism.
|
// Execution count will always be after the end of the outputs in JSON. This is a sanity mechanism.
|
||||||
let executionCountMatch = this.getExecutionCountRange(textEditorModel, contentChange.cells[0].cellGuid);
|
let executionCountMatch = this.getExecutionCountRange(textEditorModel, contentChange.cells[0].cellGuid);
|
||||||
if (!executionCountMatch || !executionCountMatch.range) {
|
if (!executionCountMatch || !executionCountMatch.range) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let endOutputsRange = this.getEndOfOutputs(textEditorModel, contentChange.cells[0].cellGuid);
|
let endOutputsRange = this.getEndOfOutputs(textEditorModel, contentChange.cells[0].cellGuid);
|
||||||
if (endOutputsRange && endOutputsRange.startLineNumber < executionCountMatch.range.startLineNumber) {
|
if (endOutputsRange && endOutputsRange.startLineNumber < executionCountMatch.range.startLineNumber) {
|
||||||
textEditorModel.textEditorModel.applyEdits([{
|
textEditorModel.textEditorModel.applyEdits([{
|
||||||
range: new Range(endOutputsRange.startLineNumber, endOutputsRange.startColumn, endOutputsRange.startLineNumber, endOutputsRange.startColumn),
|
range: new Range(endOutputsRange.startLineNumber, endOutputsRange.startColumn, endOutputsRange.startLineNumber, endOutputsRange.startColumn),
|
||||||
text: newOutput
|
text: newOutput
|
||||||
}]);
|
}]);
|
||||||
return true;
|
}
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -209,6 +231,45 @@ export class NotebookTextFileModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getSourceEndRange(textEditorModel: ITextEditorModel, cellGuid: string): IRange | undefined {
|
||||||
|
if (!cellGuid) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
let cellGuidMatches = findOrSetCellGuidMatch(textEditorModel, cellGuid);
|
||||||
|
if (cellGuidMatches?.length > 0) {
|
||||||
|
if (!this._sourceBeginRange) {
|
||||||
|
this.updateSourceBeginRange(textEditorModel, cellGuid);
|
||||||
|
}
|
||||||
|
// Source begin range tracks where the first " in exists.
|
||||||
|
// The line before that will always include '"source": ['
|
||||||
|
let sourceBeforeLineNumber = this._sourceBeginRange?.startLineNumber - 1;
|
||||||
|
if (sourceBeforeLineNumber) {
|
||||||
|
// The 2nd to last column (ie before newline) is guaranteed to be [
|
||||||
|
let sourceBeforeColumn = textEditorModel.textEditorModel.getLineMaxColumn(sourceBeforeLineNumber);
|
||||||
|
if (sourceBeforeColumn) {
|
||||||
|
// Match the end of the source array
|
||||||
|
let sourceEnd = textEditorModel.textEditorModel.matchBracket({ column: sourceBeforeColumn - 1, lineNumber: sourceBeforeLineNumber });
|
||||||
|
if (sourceEnd?.length === 2) {
|
||||||
|
// Last quote in the source array will end the line before the source array
|
||||||
|
// e.g.
|
||||||
|
// "source": [
|
||||||
|
// "SELECT 12" <-- Looking for this " position
|
||||||
|
// ],
|
||||||
|
let lineForSourceEnd = sourceEnd[1].endLineNumber - 1;
|
||||||
|
let lastCharacterPosition = textEditorModel.textEditorModel.getLineLength(lineForSourceEnd);
|
||||||
|
return {
|
||||||
|
startColumn: lastCharacterPosition,
|
||||||
|
startLineNumber: lineForSourceEnd,
|
||||||
|
endLineNumber: lineForSourceEnd,
|
||||||
|
endColumn: lastCharacterPosition
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
// Find the beginning of a cell's outputs in the text editor model
|
// Find the beginning of a cell's outputs in the text editor model
|
||||||
private updateOutputBeginRange(textEditorModel: ITextEditorModel, cellGuid: string): void {
|
private updateOutputBeginRange(textEditorModel: ITextEditorModel, cellGuid: string): void {
|
||||||
if (!cellGuid) {
|
if (!cellGuid) {
|
||||||
|
|||||||
@@ -339,6 +339,7 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe
|
|||||||
this._register(model.onProviderIdChange((provider) => this.handleProviderIdChanged(provider)));
|
this._register(model.onProviderIdChange((provider) => this.handleProviderIdChanged(provider)));
|
||||||
this._register(model.kernelChanged((kernelArgs) => this.handleKernelChanged(kernelArgs)));
|
this._register(model.kernelChanged((kernelArgs) => this.handleKernelChanged(kernelArgs)));
|
||||||
this._register(model.onCellTypeChanged(() => this.detectChanges()));
|
this._register(model.onCellTypeChanged(() => this.detectChanges()));
|
||||||
|
this._register(model.layoutChanged(() => this.detectChanges()));
|
||||||
this._model = this._register(model);
|
this._model = this._register(model);
|
||||||
await this._model.loadContents(trusted);
|
await this._model.loadContents(trusted);
|
||||||
this.setLoading(false);
|
this.setLoading(false);
|
||||||
|
|||||||
@@ -457,8 +457,15 @@ export class AttachToDropdown extends SelectBox {
|
|||||||
try {
|
try {
|
||||||
// Get all providers to show all available connections in connection dialog
|
// Get all providers to show all available connections in connection dialog
|
||||||
let providers = this.model.getApplicableConnectionProviderIds(this.model.clientSession.kernel.name);
|
let providers = this.model.getApplicableConnectionProviderIds(this.model.clientSession.kernel.name);
|
||||||
for (let alias of this.model.kernelAliases) {
|
// Spark kernels are unable to get providers from above, therefore ensure that we get the
|
||||||
providers = providers.concat(this.model.getApplicableConnectionProviderIds(alias));
|
// correct providers for the selected kernel and load the proper connections for the connection dialog
|
||||||
|
// Example Scenario: Spark Kernels should only have MSSQL connections in connection dialog
|
||||||
|
if (!this.model.kernelAliases.includes(this.model.selectedKernelDisplayName) && this.model.clientSession.kernel.name !== 'SQL') {
|
||||||
|
providers = providers.concat(this.model.getApplicableConnectionProviderIds(this.model.selectedKernelDisplayName));
|
||||||
|
} else {
|
||||||
|
for (let alias of this.model.kernelAliases) {
|
||||||
|
providers = providers.concat(this.model.getApplicableConnectionProviderIds(alias));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let connection = await this._connectionDialogService.openDialogAndWait(this._connectionManagementService,
|
let connection = await this._connectionDialogService.openDialogAndWait(this._connectionManagementService,
|
||||||
{
|
{
|
||||||
@@ -505,7 +512,7 @@ export class AttachToDropdown extends SelectBox {
|
|||||||
//Changes kernel based on connection attached to
|
//Changes kernel based on connection attached to
|
||||||
if (this.model.kernelAliases.includes(connectionProfile.serverCapabilities.notebookKernelAlias)) {
|
if (this.model.kernelAliases.includes(connectionProfile.serverCapabilities.notebookKernelAlias)) {
|
||||||
this.model.changeKernel(connectionProfile.serverCapabilities.notebookKernelAlias);
|
this.model.changeKernel(connectionProfile.serverCapabilities.notebookKernelAlias);
|
||||||
} else {
|
} else if (this.model.clientSession.kernel.name === 'SQL') {
|
||||||
this.model.changeKernel('SQL');
|
this.model.changeKernel('SQL');
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ import { NotebookSearchWidget, INotebookExplorerSearchOptions } from 'sql/workbe
|
|||||||
import * as Constants from 'sql/workbench/contrib/notebook/common/constants';
|
import * as Constants from 'sql/workbench/contrib/notebook/common/constants';
|
||||||
import { IChangeEvent } from 'vs/workbench/contrib/search/common/searchModel';
|
import { IChangeEvent } from 'vs/workbench/contrib/search/common/searchModel';
|
||||||
import { Delayer } from 'vs/base/common/async';
|
import { Delayer } from 'vs/base/common/async';
|
||||||
import { ITextQuery, IPatternInfo, IFolderQuery } from 'vs/workbench/services/search/common/search';
|
import { ITextQuery, IPatternInfo } from 'vs/workbench/services/search/common/search';
|
||||||
import { MessageType } from 'vs/base/browser/ui/inputbox/inputBox';
|
import { MessageType } from 'vs/base/browser/ui/inputbox/inputBox';
|
||||||
import { QueryBuilder, ITextQueryBuilderOptions } from 'vs/workbench/contrib/search/common/queryBuilder';
|
import { QueryBuilder, ITextQueryBuilderOptions } from 'vs/workbench/contrib/search/common/queryBuilder';
|
||||||
import { IFileService } from 'vs/platform/files/common/files';
|
import { IFileService } from 'vs/platform/files/common/files';
|
||||||
@@ -39,6 +39,7 @@ import { NotebookSearchView } from 'sql/workbench/contrib/notebook/browser/noteb
|
|||||||
import * as path from 'vs/base/common/path';
|
import * as path from 'vs/base/common/path';
|
||||||
import { URI } from 'vs/base/common/uri';
|
import { URI } from 'vs/base/common/uri';
|
||||||
import { isString } from 'vs/base/common/types';
|
import { isString } from 'vs/base/common/types';
|
||||||
|
import { TreeViewPane } from 'vs/workbench/browser/parts/views/treeView';
|
||||||
|
|
||||||
export const VIEWLET_ID = 'workbench.view.notebooks';
|
export const VIEWLET_ID = 'workbench.view.notebooks';
|
||||||
|
|
||||||
@@ -125,7 +126,7 @@ export class NotebookExplorerViewPaneContainer extends ViewPaneContainer {
|
|||||||
@IMenuService private menuService: IMenuService,
|
@IMenuService private menuService: IMenuService,
|
||||||
@IContextKeyService private contextKeyService: IContextKeyService,
|
@IContextKeyService private contextKeyService: IContextKeyService,
|
||||||
@IViewDescriptorService viewDescriptorService: IViewDescriptorService,
|
@IViewDescriptorService viewDescriptorService: IViewDescriptorService,
|
||||||
@IFileService private readonly fileService: IFileService,
|
@IFileService private readonly fileService: IFileService
|
||||||
) {
|
) {
|
||||||
super(VIEWLET_ID, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService);
|
super(VIEWLET_ID, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService);
|
||||||
this.inputBoxFocused = Constants.InputBoxFocusedKey.bindTo(this.contextKeyService);
|
this.inputBoxFocused = Constants.InputBoxFocusedKey.bindTo(this.contextKeyService);
|
||||||
@@ -264,15 +265,22 @@ export class NotebookExplorerViewPaneContainer extends ViewPaneContainer {
|
|||||||
if (this.views.length > 1) {
|
if (this.views.length > 1) {
|
||||||
let filesToIncludeFiltered: string = '';
|
let filesToIncludeFiltered: string = '';
|
||||||
this.views.forEach(async (v) => {
|
this.views.forEach(async (v) => {
|
||||||
const { treeView } = (<ITreeViewDescriptor>Registry.as<IViewsRegistry>(ViewContainerExtensions.ViewsRegistry).getView(v.id));
|
if (v instanceof TreeViewPane) {
|
||||||
let items = await treeView?.dataProvider.getChildren();
|
const { treeView } = (<ITreeViewDescriptor>Registry.as<IViewsRegistry>(ViewContainerExtensions.ViewsRegistry).getView(v.id));
|
||||||
items?.forEach(root => {
|
if (treeView.dataProvider) {
|
||||||
this.updateViewletsState();
|
let items = await treeView?.dataProvider.getChildren(treeView?.root);
|
||||||
let folderToSearch: IFolderQuery = { folder: URI.file(path.join(isString(root.tooltip) ? root.tooltip : root.tooltip.value, 'content')) };
|
items?.forEach(root => {
|
||||||
query.folderQueries.push(folderToSearch);
|
if (root.contextValue !== 'pinnedNotebook') {
|
||||||
filesToIncludeFiltered = filesToIncludeFiltered + path.join(folderToSearch.folder.fsPath, '**', '*.md') + ',' + path.join(folderToSearch.folder.fsPath, '**', '*.ipynb') + ',';
|
this.updateViewletsState();
|
||||||
this.searchView.startSearch(query, null, filesToIncludeFiltered, false, this.searchWidget);
|
let rootFolder = URI.file(isString(root.tooltip) ? root.tooltip : root.tooltip.value);
|
||||||
});
|
let folderToSearch = { folder: rootFolder };
|
||||||
|
query.folderQueries.push(folderToSearch);
|
||||||
|
filesToIncludeFiltered = filesToIncludeFiltered + path.join(folderToSearch.folder.fsPath, '**', '*.md') + ',' + path.join(folderToSearch.folder.fsPath, '**', '*.ipynb') + ',';
|
||||||
|
this.searchView.startSearch(query, null, filesToIncludeFiltered, false, this.searchWidget);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,7 +301,7 @@ export class NotebookExplorerViewPaneContainer extends ViewPaneContainer {
|
|||||||
let allViews = containerModel.allViewDescriptors;
|
let allViews = containerModel.allViewDescriptors;
|
||||||
allViews.forEach(v => {
|
allViews.forEach(v => {
|
||||||
let view = this.getView(v.id);
|
let view = this.getView(v.id);
|
||||||
if (view !== this.searchView) {
|
if (view && view !== this.searchView) {
|
||||||
view.setExpanded(false);
|
view.setExpanded(false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -349,8 +357,6 @@ export class NotebookExplorerViewPaneContainer extends ViewPaneContainer {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async refreshTree(event?: IChangeEvent): Promise<void> {
|
async refreshTree(event?: IChangeEvent): Promise<void> {
|
||||||
await this.searchView.refreshTree(event);
|
await this.searchView.refreshTree(event);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ import { IQueryManagementService } from 'sql/workbench/services/query/common/que
|
|||||||
import { values } from 'vs/base/common/collections';
|
import { values } from 'vs/base/common/collections';
|
||||||
import { URI } from 'vs/base/common/uri';
|
import { URI } from 'vs/base/common/uri';
|
||||||
import { assign } from 'vs/base/common/objects';
|
import { assign } from 'vs/base/common/objects';
|
||||||
|
import { escape } from 'sql/base/common/strings';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: GridOutputComponent.SELECTOR,
|
selector: GridOutputComponent.SELECTOR,
|
||||||
@@ -294,6 +295,11 @@ class DataResourceTable extends GridTableBase<any> {
|
|||||||
public convertData(set: ResultSetSummary): Promise<void> {
|
public convertData(set: ResultSetSummary): Promise<void> {
|
||||||
return this._gridDataProvider.convertAllData(set);
|
return this._gridDataProvider.convertAllData(set);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public updateResult(resultSet: ResultSetSummary): void {
|
||||||
|
super.updateResult(resultSet);
|
||||||
|
this._gridDataProvider.updateResultSet(resultSet);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DataResourceDataProvider implements IGridDataProvider {
|
export class DataResourceDataProvider implements IGridDataProvider {
|
||||||
@@ -371,8 +377,12 @@ export class DataResourceDataProvider implements IGridDataProvider {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public updateResultSet(resultSet: ResultSetSummary): void {
|
||||||
|
this._resultSet = resultSet;
|
||||||
|
}
|
||||||
|
|
||||||
public async convertAllData(result: ResultSetSummary): Promise<void> {
|
public async convertAllData(result: ResultSetSummary): Promise<void> {
|
||||||
// Querying 50 rows at a time. Querying large amount of rows will be slow and
|
// Querying 100 rows at a time. Querying large amount of rows will be slow and
|
||||||
// affect table rendering since each time the user scrolls, getRowData is called.
|
// affect table rendering since each time the user scrolls, getRowData is called.
|
||||||
let numRows = 100;
|
let numRows = 100;
|
||||||
for (let i = 0; i < result.rowCount; i += 100) {
|
for (let i = 0; i < result.rowCount; i += 100) {
|
||||||
@@ -382,6 +392,7 @@ export class DataResourceDataProvider implements IGridDataProvider {
|
|||||||
let rows = await this._queryRunner.getQueryRows(i, numRows, this._batchId, this._id);
|
let rows = await this._queryRunner.getQueryRows(i, numRows, this._batchId, this._id);
|
||||||
this.convertData(rows);
|
this.convertData(rows);
|
||||||
}
|
}
|
||||||
|
this.cellModel.sendChangeToNotebook(NotebookChangeType.CellOutputUpdated);
|
||||||
}
|
}
|
||||||
|
|
||||||
private convertData(rows: ResultSetSubset): void {
|
private convertData(rows: ResultSetSubset): void {
|
||||||
|
|||||||
@@ -400,6 +400,87 @@ suite('Notebook Editor Model', function (): void {
|
|||||||
assert(!notebookEditorModel.lastEditFullReplacement);
|
assert(!notebookEditorModel.lastEditFullReplacement);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should not replace entire text model but replace entire source when no modelContentChangedEvent passed in', async function (): Promise<void> {
|
||||||
|
await createNewNotebookModel();
|
||||||
|
let notebookEditorModel = await createTextEditorModel(this);
|
||||||
|
notebookEditorModel.replaceEntireTextEditorModel(notebookModel, undefined);
|
||||||
|
|
||||||
|
let newCell = notebookModel.addCell(CellTypes.Code);
|
||||||
|
|
||||||
|
let contentChange: NotebookContentChange = {
|
||||||
|
changeType: NotebookChangeType.CellsModified,
|
||||||
|
cells: [newCell],
|
||||||
|
cellIndex: 0
|
||||||
|
};
|
||||||
|
notebookEditorModel.updateModel(contentChange, NotebookChangeType.CellsModified);
|
||||||
|
assert(notebookEditorModel.lastEditFullReplacement);
|
||||||
|
|
||||||
|
assert.equal(notebookEditorModel.editorModel.textEditorModel.getLineContent(14), ' "outputs": [');
|
||||||
|
|
||||||
|
newCell.source = 'This is a test';
|
||||||
|
|
||||||
|
contentChange = {
|
||||||
|
changeType: NotebookChangeType.CellSourceUpdated,
|
||||||
|
cells: [newCell],
|
||||||
|
cellIndex: 0,
|
||||||
|
modelContentChangedEvent: undefined
|
||||||
|
};
|
||||||
|
|
||||||
|
notebookEditorModel.updateModel(contentChange, NotebookChangeType.CellSourceUpdated);
|
||||||
|
|
||||||
|
assert(!notebookEditorModel.lastEditFullReplacement, 'should not do a full replacement for a source update');
|
||||||
|
|
||||||
|
assert.equal(notebookEditorModel.editorModel.textEditorModel.getLineContent(8), ' "source": [');
|
||||||
|
assert.equal(notebookEditorModel.editorModel.textEditorModel.getLineContent(9), ' "This is a test"');
|
||||||
|
assert.equal(notebookEditorModel.editorModel.textEditorModel.getLineContent(10), ' ],');
|
||||||
|
assert.equal(notebookEditorModel.editorModel.textEditorModel.getLineContent(12), ' "azdata_cell_guid": "' + newCell.cellGuid + '"');
|
||||||
|
assert.equal(notebookEditorModel.editorModel.textEditorModel.getLineContent(14), ' "outputs": [');
|
||||||
|
assert.equal(notebookEditorModel.editorModel.textEditorModel.getLineContent(25), ' "execution_count": null');
|
||||||
|
assert.equal(notebookEditorModel.editorModel.textEditorModel.getLineContent(26), ' }');
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not replace entire text model but replace entire source when no modelContentChangedEvent passed in multiline change', async function (): Promise<void> {
|
||||||
|
await createNewNotebookModel();
|
||||||
|
let notebookEditorModel = await createTextEditorModel(this);
|
||||||
|
notebookEditorModel.replaceEntireTextEditorModel(notebookModel, undefined);
|
||||||
|
|
||||||
|
let newCell = notebookModel.addCell(CellTypes.Code);
|
||||||
|
|
||||||
|
let contentChange: NotebookContentChange = {
|
||||||
|
changeType: NotebookChangeType.CellsModified,
|
||||||
|
cells: [newCell],
|
||||||
|
cellIndex: 0
|
||||||
|
};
|
||||||
|
notebookEditorModel.updateModel(contentChange, NotebookChangeType.CellsModified);
|
||||||
|
assert(notebookEditorModel.lastEditFullReplacement);
|
||||||
|
|
||||||
|
assert.equal(notebookEditorModel.editorModel.textEditorModel.getLineContent(14), ' "outputs": [');
|
||||||
|
|
||||||
|
newCell.source = 'This is a test' + os.EOL + 'Line 2 test' + os.EOL + 'Line 3 test';
|
||||||
|
|
||||||
|
contentChange = {
|
||||||
|
changeType: NotebookChangeType.CellSourceUpdated,
|
||||||
|
cells: [newCell],
|
||||||
|
cellIndex: 0,
|
||||||
|
modelContentChangedEvent: undefined
|
||||||
|
};
|
||||||
|
|
||||||
|
notebookEditorModel.updateModel(contentChange, NotebookChangeType.CellSourceUpdated);
|
||||||
|
|
||||||
|
assert(!notebookEditorModel.lastEditFullReplacement, 'should not do a full replacement for a source update');
|
||||||
|
|
||||||
|
assert.equal(notebookEditorModel.editorModel.textEditorModel.getLineContent(8), ' "source": [');
|
||||||
|
assert.equal(notebookEditorModel.editorModel.textEditorModel.getLineContent(9), ' "This is a test\\n",');
|
||||||
|
assert.equal(notebookEditorModel.editorModel.textEditorModel.getLineContent(10), ' "Line 2 test\\n",');
|
||||||
|
assert.equal(notebookEditorModel.editorModel.textEditorModel.getLineContent(11), ' "Line 3 test"');
|
||||||
|
assert.equal(notebookEditorModel.editorModel.textEditorModel.getLineContent(12), ' ],');
|
||||||
|
assert.equal(notebookEditorModel.editorModel.textEditorModel.getLineContent(14), ' "azdata_cell_guid": "' + newCell.cellGuid + '"');
|
||||||
|
assert.equal(notebookEditorModel.editorModel.textEditorModel.getLineContent(16), ' "outputs": [');
|
||||||
|
assert.equal(notebookEditorModel.editorModel.textEditorModel.getLineContent(27), ' "execution_count": null');
|
||||||
|
assert.equal(notebookEditorModel.editorModel.textEditorModel.getLineContent(28), ' }');
|
||||||
|
});
|
||||||
|
|
||||||
test('should not replace entire text model for single line source change then delete', async function (): Promise<void> {
|
test('should not replace entire text model for single line source change then delete', async function (): Promise<void> {
|
||||||
await createNewNotebookModel();
|
await createNewNotebookModel();
|
||||||
let notebookEditorModel = await createTextEditorModel(this);
|
let notebookEditorModel = await createTextEditorModel(this);
|
||||||
@@ -612,9 +693,9 @@ suite('Notebook Editor Model', function (): void {
|
|||||||
assert.equal(notebookEditorModel.editorModel.textEditorModel.getLineContent(8), ' "source": [');
|
assert.equal(notebookEditorModel.editorModel.textEditorModel.getLineContent(8), ' "source": [');
|
||||||
assert.equal(notebookEditorModel.editorModel.textEditorModel.getLineContent(12), ' "azdata_cell_guid": "' + newCell.cellGuid + '"');
|
assert.equal(notebookEditorModel.editorModel.textEditorModel.getLineContent(12), ' "azdata_cell_guid": "' + newCell.cellGuid + '"');
|
||||||
assert.equal(notebookEditorModel.editorModel.textEditorModel.getLineContent(14), ' "outputs": [');
|
assert.equal(notebookEditorModel.editorModel.textEditorModel.getLineContent(14), ' "outputs": [');
|
||||||
assert.equal(notebookEditorModel.editorModel.textEditorModel.getLineContent(23), ' }, {');
|
assert.equal(notebookEditorModel.editorModel.textEditorModel.getLineContent(23), '}, {');
|
||||||
assert.equal(notebookEditorModel.editorModel.textEditorModel.getLineContent(31), '}');
|
assert.equal(notebookEditorModel.editorModel.textEditorModel.getLineContent(31), '}');
|
||||||
assert.equal(notebookEditorModel.editorModel.textEditorModel.getLineContent(32), ' ],');
|
assert.equal(notebookEditorModel.editorModel.textEditorModel.getLineContent(32), '],');
|
||||||
assert.equal(notebookEditorModel.editorModel.textEditorModel.getLineContent(33), ' "execution_count": null');
|
assert.equal(notebookEditorModel.editorModel.textEditorModel.getLineContent(33), ' "execution_count": null');
|
||||||
assert.equal(notebookEditorModel.editorModel.textEditorModel.getLineContent(34), ' }');
|
assert.equal(notebookEditorModel.editorModel.textEditorModel.getLineContent(34), ' }');
|
||||||
|
|
||||||
@@ -653,7 +734,7 @@ suite('Notebook Editor Model', function (): void {
|
|||||||
assert.equal(notebookEditorModel.editorModel.textEditorModel.getLineContent(14), ' "outputs": [');
|
assert.equal(notebookEditorModel.editorModel.textEditorModel.getLineContent(14), ' "outputs": [');
|
||||||
assert.equal(notebookEditorModel.editorModel.textEditorModel.getLineContent(26), ' "text": "[0em"');
|
assert.equal(notebookEditorModel.editorModel.textEditorModel.getLineContent(26), ' "text": "[0em"');
|
||||||
assert.equal(notebookEditorModel.editorModel.textEditorModel.getLineContent(27), '}');
|
assert.equal(notebookEditorModel.editorModel.textEditorModel.getLineContent(27), '}');
|
||||||
assert.equal(notebookEditorModel.editorModel.textEditorModel.getLineContent(28), ' ],');
|
assert.equal(notebookEditorModel.editorModel.textEditorModel.getLineContent(28), '],');
|
||||||
assert.equal(notebookEditorModel.editorModel.textEditorModel.getLineContent(29), ' "execution_count": null');
|
assert.equal(notebookEditorModel.editorModel.textEditorModel.getLineContent(29), ' "execution_count": null');
|
||||||
assert.equal(notebookEditorModel.editorModel.textEditorModel.getLineContent(30), ' }');
|
assert.equal(notebookEditorModel.editorModel.textEditorModel.getLineContent(30), ' }');
|
||||||
|
|
||||||
|
|||||||
@@ -269,12 +269,13 @@ export class GridPanel extends Disposable {
|
|||||||
|
|
||||||
for (let i = this.tables.length - 1; i >= 0; i--) {
|
for (let i = this.tables.length - 1; i >= 0; i--) {
|
||||||
if (this.tables[i].id === tableid) {
|
if (this.tables[i].id === tableid) {
|
||||||
this.tables[i].state.maximized = true;
|
const selectedTable = this.tables[i];
|
||||||
this.maximizedGrid = this.tables[i];
|
selectedTable.state.maximized = true;
|
||||||
continue;
|
this.maximizedGrid = selectedTable;
|
||||||
|
this.scrollableView.clear();
|
||||||
|
this.scrollableView.addViews([selectedTable]);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.scrollableView.clear();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,16 +12,9 @@ import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
|||||||
import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||||
import { IEditorService, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService';
|
import { IEditorService, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService';
|
||||||
import { isString } from 'vs/base/common/types';
|
import { isString } from 'vs/base/common/types';
|
||||||
import { IViewContainersRegistry, ViewContainerLocation, Extensions as ViewExtensions, IViewsRegistry } from 'vs/workbench/common/views';
|
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
|
||||||
import { RESOURCE_VIEWER_VIEW_CONTAINER_ID, RESOURCE_VIEWER_VIEW_ID } from 'sql/workbench/contrib/resourceViewer/common/resourceViewer';
|
|
||||||
import { localize } from 'vs/nls';
|
|
||||||
import { Codicon } from 'vs/base/common/codicons';
|
|
||||||
import { ResourceViewerViewlet } from 'sql/workbench/contrib/resourceViewer/browser/resourceViewerViewlet';
|
|
||||||
import { ResourceViewerView } from 'sql/workbench/contrib/resourceViewer/browser/resourceViewerView';
|
|
||||||
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
|
||||||
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
|
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
|
||||||
import { ResourceViewResourcesExtensionHandler } from 'sql/workbench/contrib/resourceViewer/common/resourceViewerViewExtensionPoint';
|
import { ResourceViewResourcesExtensionHandler } from 'sql/workbench/contrib/resourceViewer/common/resourceViewerViewExtensionPoint';
|
||||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
|
||||||
|
|
||||||
CommandsRegistry.registerCommand({
|
CommandsRegistry.registerCommand({
|
||||||
id: 'resourceViewer.openResourceViewer',
|
id: 'resourceViewer.openResourceViewer',
|
||||||
@@ -48,40 +41,39 @@ Registry.as<IEditorRegistry>(EditorExtensions.Editors)
|
|||||||
|
|
||||||
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(ResourceViewResourcesExtensionHandler, LifecyclePhase.Ready);
|
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(ResourceViewResourcesExtensionHandler, LifecyclePhase.Ready);
|
||||||
|
|
||||||
class ResourceViewerContributor implements IWorkbenchContribution {
|
// TODO: chgagnon disabling until the resource viewer is feature complete
|
||||||
constructor(
|
// class ResourceViewerContributor implements IWorkbenchContribution {
|
||||||
@IExtensionService private readonly extensionService: IExtensionService
|
// constructor(
|
||||||
) {
|
// @IExtensionService private readonly extensionService: IExtensionService
|
||||||
void this.checkForArc();
|
// ) {
|
||||||
}
|
// this.checkForArc();
|
||||||
|
// }
|
||||||
|
|
||||||
private async checkForArc(): Promise<void> {
|
// private async checkForArc(): Promise<void> {
|
||||||
if (await this.extensionService.getExtension('Microsoft.arc')) {
|
// if (await this.extensionService.getExtension('Microsoft.arc')) {
|
||||||
registerResourceViewerContainer();
|
// registerResourceViewerContainer();
|
||||||
} else {
|
// } else {
|
||||||
const disposable = this.extensionService.onDidChangeExtensions(async () => {
|
// const disposable = this.extensionService.onDidChangeExtensions(async () => {
|
||||||
if (await this.extensionService.getExtension('Microsoft.arc')) {
|
// if (await this.extensionService.getExtension('Microsoft.arc')) {
|
||||||
registerResourceViewerContainer();
|
// registerResourceViewerContainer();
|
||||||
disposable.dispose();
|
// disposable.dispose();
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(ResourceViewerContributor, LifecyclePhase.Ready);
|
// Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(ResourceViewerContributor, LifecyclePhase.Ready);
|
||||||
|
|
||||||
function registerResourceViewerContainer() {
|
// function registerResourceViewerContainer() {
|
||||||
const viewContainer = Registry.as<IViewContainersRegistry>(ViewExtensions.ViewContainersRegistry).registerViewContainer({
|
// const viewContainer = Registry.as<IViewContainersRegistry>(ViewExtensions.ViewContainersRegistry).registerViewContainer({
|
||||||
id: RESOURCE_VIEWER_VIEW_CONTAINER_ID,
|
// id: RESOURCE_VIEWER_VIEW_CONTAINER_ID,
|
||||||
name: localize('resourceViewer', "Resource Viewer"),
|
// name: localize('resourceViewer', "Resource Viewer"),
|
||||||
ctorDescriptor: new SyncDescriptor(ResourceViewerViewlet),
|
// ctorDescriptor: new SyncDescriptor(ResourceViewerViewlet),
|
||||||
icon: Codicon.database.classNames,
|
// icon: Codicon.database.classNames,
|
||||||
alwaysUseContainerInfo: true
|
// alwaysUseContainerInfo: true
|
||||||
}, ViewContainerLocation.Sidebar);
|
// }, ViewContainerLocation.Sidebar);
|
||||||
// registry.registerWorkbenchAction(SyncActionDescriptor.from(OpenDebugViewletAction, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_D }), 'View: Show Run and Debug', nls.localize('view', "View"));
|
|
||||||
|
|
||||||
// Register default debug views
|
// const viewsRegistry = Registry.as<IViewsRegistry>(ViewExtensions.ViewsRegistry);
|
||||||
const viewsRegistry = Registry.as<IViewsRegistry>(ViewExtensions.ViewsRegistry);
|
// viewsRegistry.registerViews([{ id: RESOURCE_VIEWER_VIEW_ID, name: localize('resourceViewer', "Resource Viewer"), containerIcon: Codicon.database.classNames, ctorDescriptor: new SyncDescriptor(ResourceViewerView), canToggleVisibility: false, canMoveView: false }], viewContainer);
|
||||||
viewsRegistry.registerViews([{ id: RESOURCE_VIEWER_VIEW_ID, name: localize('resourceViewer', "Resource Viewer"), containerIcon: Codicon.database.classNames, ctorDescriptor: new SyncDescriptor(ResourceViewerView), canToggleVisibility: false, canMoveView: false }], viewContainer);
|
// }
|
||||||
}
|
|
||||||
|
|||||||
@@ -94,7 +94,26 @@ MenuRegistry.appendMenuItem(MenuId.ObjectExplorerItemContext, {
|
|||||||
id: commands.OE_SCRIPT_AS_SELECT_COMMAND_ID,
|
id: commands.OE_SCRIPT_AS_SELECT_COMMAND_ID,
|
||||||
title: localize('scriptSelect', "Select Top 1000")
|
title: localize('scriptSelect', "Select Top 1000")
|
||||||
},
|
},
|
||||||
when: ContextKeyExpr.or(TreeNodeContextKey.NodeType.isEqualTo('Table'), TreeNodeContextKey.NodeType.isEqualTo('View'))
|
when: ContextKeyExpr.and(
|
||||||
|
ConnectionContextKey.Provider.notEqualsTo('KUSTO'),
|
||||||
|
ContextKeyExpr.or(
|
||||||
|
TreeNodeContextKey.NodeType.isEqualTo('Table'),
|
||||||
|
TreeNodeContextKey.NodeType.isEqualTo('View')
|
||||||
|
)
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
MenuRegistry.appendMenuItem(MenuId.ObjectExplorerItemContext, {
|
||||||
|
group: '0_query',
|
||||||
|
order: 1,
|
||||||
|
command: {
|
||||||
|
id: commands.OE_SCRIPT_AS_SELECT_COMMAND_ID,
|
||||||
|
title: localize('scriptKustoSelect', "Take 10")
|
||||||
|
},
|
||||||
|
when: ContextKeyExpr.and(
|
||||||
|
ConnectionContextKey.Provider.isEqualTo('KUSTO'),
|
||||||
|
TreeNodeContextKey.NodeType.isEqualTo('Table')
|
||||||
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
MenuRegistry.appendMenuItem(MenuId.ObjectExplorerItemContext, {
|
MenuRegistry.appendMenuItem(MenuId.ObjectExplorerItemContext, {
|
||||||
|
|||||||
@@ -167,3 +167,8 @@
|
|||||||
/* Hide twisties */
|
/* Hide twisties */
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.connection-dialog .tabbedPanel .tabList,
|
||||||
|
.connection-dialog .tabbedPanel .tabBody {
|
||||||
|
padding-left: 10px;
|
||||||
|
}
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ export class ClientSession implements IClientSession {
|
|||||||
private isServerStarted: boolean;
|
private isServerStarted: boolean;
|
||||||
private notebookManager: INotebookManager;
|
private notebookManager: INotebookManager;
|
||||||
private _kernelConfigActions: ((kernelName: string) => Promise<any>)[] = [];
|
private _kernelConfigActions: ((kernelName: string) => Promise<any>)[] = [];
|
||||||
|
private _connectionId: string = '';
|
||||||
|
|
||||||
constructor(private options: IClientSessionOptions) {
|
constructor(private options: IClientSessionOptions) {
|
||||||
this._notebookUri = options.notebookUri;
|
this._notebookUri = options.notebookUri;
|
||||||
@@ -288,8 +289,9 @@ export class ClientSession implements IClientSession {
|
|||||||
// TODO is there any case where skipping causes errors? So far it seems like it gets called twice
|
// TODO is there any case where skipping causes errors? So far it seems like it gets called twice
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (connection.id !== '-1') {
|
if (connection.id !== '-1' && connection.id !== this._connectionId) {
|
||||||
await this._session.configureConnection(connection);
|
await this._session.configureConnection(connection);
|
||||||
|
this._connectionId = connection.id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -322,7 +322,7 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
|||||||
return !ids ? [] : ids;
|
return !ids ? [] : ids;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async loadContents(isTrusted: boolean = false): Promise<void> {
|
public async loadContents(isTrusted = false, forceLayoutChange = false): Promise<void> {
|
||||||
try {
|
try {
|
||||||
this._trustedMode = isTrusted;
|
this._trustedMode = isTrusted;
|
||||||
|
|
||||||
@@ -369,6 +369,9 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
|||||||
if (this._cells.length === 0 || this._cells.every(cell => cell.cellType === CellTypes.Markdown)) {
|
if (this._cells.length === 0 || this._cells.every(cell => cell.cellType === CellTypes.Markdown)) {
|
||||||
this.trustedMode = true;
|
this.trustedMode = true;
|
||||||
}
|
}
|
||||||
|
if (forceLayoutChange) {
|
||||||
|
this._layoutChanged.fire();
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this._inErrorState = true;
|
this._inErrorState = true;
|
||||||
throw error;
|
throw error;
|
||||||
@@ -653,9 +656,15 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
|||||||
let standardKernels = find(this._standardKernels, kernel => this._defaultKernel && kernel.displayName === this._defaultKernel.display_name);
|
let standardKernels = find(this._standardKernels, kernel => this._defaultKernel && kernel.displayName === this._defaultKernel.display_name);
|
||||||
let connectionProviderIds = standardKernels ? standardKernels.connectionProviderIds : undefined;
|
let connectionProviderIds = standardKernels ? standardKernels.connectionProviderIds : undefined;
|
||||||
let providerFeatures = this._capabilitiesService.getCapabilities(profile.providerName);
|
let providerFeatures = this._capabilitiesService.getCapabilities(profile.providerName);
|
||||||
if (connectionProviderIds.length > 0 && this._currentKernelAlias) {
|
if (connectionProviderIds?.length) {
|
||||||
this._currentKernelAlias = providerFeatures?.connection.notebookKernelAlias;
|
this._currentKernelAlias = providerFeatures?.connection.notebookKernelAlias;
|
||||||
this._kernelDisplayNameToConnectionProviderIds.set(this._currentKernelAlias, [profile.providerName]);
|
// Switching from Kusto to another kernel should set the currentKernelAlias to undefined
|
||||||
|
if (this._selectedKernelDisplayName !== this._currentKernelAlias && this._selectedKernelDisplayName) {
|
||||||
|
this._currentKernelAlias = undefined;
|
||||||
|
} else {
|
||||||
|
// Adds Kernel Alias and Connection Provider to Map if new Notebook connection contains notebookKernelAlias
|
||||||
|
this._kernelDisplayNameToConnectionProviderIds.set(this._currentKernelAlias, [profile.providerName]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return this._currentKernelAlias || profile && connectionProviderIds && find(connectionProviderIds, provider => provider === profile.providerName) !== undefined;
|
return this._currentKernelAlias || profile && connectionProviderIds && find(connectionProviderIds, provider => provider === profile.providerName) !== undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ import { startsWith } from 'vs/base/common/strings';
|
|||||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||||
import { FutureInternal, notebookConstants } from 'sql/workbench/services/notebook/browser/interfaces';
|
import { FutureInternal, notebookConstants } from 'sql/workbench/services/notebook/browser/interfaces';
|
||||||
import { tryMatchCellMagic } from 'sql/workbench/services/notebook/browser/utils';
|
import { tryMatchCellMagic } from 'sql/workbench/services/notebook/browser/utils';
|
||||||
import { IQueryManagementService } from 'sql/workbench/services/query/common/queryManagement';
|
|
||||||
|
|
||||||
export const sqlKernelError: string = localize("sqlKernelError", "SQL kernel error");
|
export const sqlKernelError: string = localize("sqlKernelError", "SQL kernel error");
|
||||||
export const MAX_ROWS = 5000;
|
export const MAX_ROWS = 5000;
|
||||||
@@ -177,8 +176,7 @@ class SqlKernel extends Disposable implements nb.IKernel {
|
|||||||
@IErrorMessageService private _errorMessageService: IErrorMessageService,
|
@IErrorMessageService private _errorMessageService: IErrorMessageService,
|
||||||
@IConfigurationService private _configurationService: IConfigurationService,
|
@IConfigurationService private _configurationService: IConfigurationService,
|
||||||
@ILogService private readonly logService: ILogService,
|
@ILogService private readonly logService: ILogService,
|
||||||
@ITextResourcePropertiesService private readonly textResourcePropertiesService: ITextResourcePropertiesService,
|
@ITextResourcePropertiesService private readonly textResourcePropertiesService: ITextResourcePropertiesService
|
||||||
@IQueryManagementService private queryManagementService: IQueryManagementService
|
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.initMagics();
|
this.initMagics();
|
||||||
@@ -285,7 +283,6 @@ class SqlKernel extends Disposable implements nb.IKernel {
|
|||||||
this._queryRunner.runQuery(code).catch(err => onUnexpectedError(err));
|
this._queryRunner.runQuery(code).catch(err => onUnexpectedError(err));
|
||||||
} else if (this._currentConnection && this._currentConnectionProfile) {
|
} else if (this._currentConnection && this._currentConnectionProfile) {
|
||||||
this._queryRunner = this._instantiationService.createInstance(QueryRunner, this._connectionPath);
|
this._queryRunner = this._instantiationService.createInstance(QueryRunner, this._connectionPath);
|
||||||
this.queryManagementService.registerRunner(this._queryRunner, this._connectionPath);
|
|
||||||
this._connectionManagementService.connect(this._currentConnectionProfile, this._connectionPath).then((result) => {
|
this._connectionManagementService.connect(this._currentConnectionProfile, this._connectionPath).then((result) => {
|
||||||
this.addQueryEventListeners(this._queryRunner);
|
this.addQueryEventListeners(this._queryRunner);
|
||||||
this._queryRunner.runQuery(code).catch(err => onUnexpectedError(err));
|
this._queryRunner.runQuery(code).catch(err => onUnexpectedError(err));
|
||||||
@@ -352,14 +349,9 @@ class SqlKernel extends Disposable implements nb.IKernel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
this._register(queryRunner.onResultSet(resultSet => {
|
|
||||||
if (this._future) {
|
|
||||||
this._future.onResultSet(resultSet);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
this._register(queryRunner.onBatchEnd(batch => {
|
this._register(queryRunner.onBatchEnd(batch => {
|
||||||
if (this._future) {
|
if (this._future) {
|
||||||
this._future.onBatchEnd(batch);
|
this._future.handleBatchEnd(batch);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@@ -392,9 +384,9 @@ export class SQLFuture extends Disposable implements FutureInternal {
|
|||||||
private doneDeferred = new Deferred<nb.IShellMessage>();
|
private doneDeferred = new Deferred<nb.IShellMessage>();
|
||||||
private configuredMaxRows: number = MAX_ROWS;
|
private configuredMaxRows: number = MAX_ROWS;
|
||||||
private _outputAddedPromises: Promise<void>[] = [];
|
private _outputAddedPromises: Promise<void>[] = [];
|
||||||
|
private _querySubsetResultMap: Map<number, ResultSetSubset> = new Map<number, ResultSetSubset>();
|
||||||
private _errorOccurred: boolean = false;
|
private _errorOccurred: boolean = false;
|
||||||
private _stopOnError: boolean = true;
|
private _stopOnError: boolean = true;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private _queryRunner: QueryRunner,
|
private _queryRunner: QueryRunner,
|
||||||
private _executionCount: number | undefined,
|
private _executionCount: number | undefined,
|
||||||
@@ -450,6 +442,7 @@ export class SQLFuture extends Disposable implements FutureInternal {
|
|||||||
this.doneHandler.handle(msg);
|
this.doneHandler.handle(msg);
|
||||||
}
|
}
|
||||||
this.doneDeferred.resolve(msg);
|
this.doneDeferred.resolve(msg);
|
||||||
|
this._querySubsetResultMap.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
sendInputReply(content: nb.IInputReply): void {
|
sendInputReply(content: nb.IInputReply): void {
|
||||||
@@ -480,32 +473,28 @@ export class SQLFuture extends Disposable implements FutureInternal {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public onResultSet(resultSet: ResultSetSummary | ResultSetSummary[]): void {
|
public handleBatchEnd(batch: BatchSummary): void {
|
||||||
if (this.ioHandler) {
|
if (this.ioHandler) {
|
||||||
this._outputAddedPromises.push(this.sendInitialResultSets(resultSet));
|
this._outputAddedPromises.push(this.processResultSets(batch));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public onBatchEnd(batch: BatchSummary): void {
|
private async processResultSets(batch: BatchSummary): Promise<void> {
|
||||||
if (this.ioHandler) {
|
|
||||||
for (let set of batch.resultSetSummaries) {
|
|
||||||
if (set.rowCount > this.configuredMaxRows) {
|
|
||||||
this.handleMessage(localize('sqlMaxRowsDisplayed', "Displaying Top {0} rows.", this.configuredMaxRows));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async sendInitialResultSets(resultSet: ResultSetSummary | ResultSetSummary[]): Promise<void> {
|
|
||||||
try {
|
try {
|
||||||
let resultsToAdd: ResultSetSummary[];
|
let queryRowsPromises: Promise<void>[] = [];
|
||||||
if (!Array.isArray(resultSet)) {
|
for (let resultSet of batch.resultSetSummaries) {
|
||||||
resultsToAdd = [resultSet];
|
let rowCount = resultSet.rowCount > this.configuredMaxRows ? this.configuredMaxRows : resultSet.rowCount;
|
||||||
} else {
|
if (rowCount === this.configuredMaxRows) {
|
||||||
resultsToAdd = resultSet?.splice(0);
|
this.handleMessage(localize('sqlMaxRowsDisplayed', "Displaying Top {0} rows.", rowCount));
|
||||||
|
}
|
||||||
|
queryRowsPromises.push(this.getAllQueryRows(rowCount, resultSet));
|
||||||
}
|
}
|
||||||
for (let set of resultsToAdd) {
|
// We want to display table in the same order
|
||||||
this.sendIOPubMessage(set, false);
|
let i = 0;
|
||||||
|
for (let resultSet of batch.resultSetSummaries) {
|
||||||
|
await queryRowsPromises[i];
|
||||||
|
this.sendResultSetAsIOPub(resultSet);
|
||||||
|
i++;
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// TODO should we output this somewhere else?
|
// TODO should we output this somewhere else?
|
||||||
@@ -513,7 +502,31 @@ export class SQLFuture extends Disposable implements FutureInternal {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private sendIOPubMessage(resultSet: ResultSetSummary, conversionComplete?: boolean, subsetResult?: ResultSetSubset): void {
|
private async getAllQueryRows(rowCount: number, resultSet: ResultSetSummary): Promise<void> {
|
||||||
|
let deferred: Deferred<void> = new Deferred<void>();
|
||||||
|
if (rowCount > 0) {
|
||||||
|
this._queryRunner.getQueryRows(0, rowCount, resultSet.batchId, resultSet.id).then((result) => {
|
||||||
|
this._querySubsetResultMap.set(resultSet.id, result);
|
||||||
|
deferred.resolve();
|
||||||
|
}, (err) => {
|
||||||
|
this._querySubsetResultMap.set(resultSet.id, { rowCount: 0, rows: [] });
|
||||||
|
deferred.reject(err);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this._querySubsetResultMap.set(resultSet.id, { rowCount: 0, rows: [] });
|
||||||
|
deferred.resolve();
|
||||||
|
}
|
||||||
|
return deferred;
|
||||||
|
}
|
||||||
|
|
||||||
|
private sendResultSetAsIOPub(resultSet: ResultSetSummary): void {
|
||||||
|
if (this._querySubsetResultMap && this._querySubsetResultMap.get(resultSet.id)) {
|
||||||
|
let subsetResult = this._querySubsetResultMap.get(resultSet.id);
|
||||||
|
this.sendIOPubMessage(subsetResult, resultSet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sendIOPubMessage(subsetResult: ResultSetSubset, resultSet: ResultSetSummary): void {
|
||||||
let msg: nb.IIOPubMessage = {
|
let msg: nb.IIOPubMessage = {
|
||||||
channel: 'iopub',
|
channel: 'iopub',
|
||||||
type: 'iopub',
|
type: 'iopub',
|
||||||
@@ -525,21 +538,16 @@ export class SQLFuture extends Disposable implements FutureInternal {
|
|||||||
output_type: 'execute_result',
|
output_type: 'execute_result',
|
||||||
metadata: {},
|
metadata: {},
|
||||||
execution_count: this._executionCount,
|
execution_count: this._executionCount,
|
||||||
// Initial data sent to notebook only contains column headers since
|
|
||||||
// onResultSet only returns the column info (and no row data).
|
|
||||||
// Row data conversion will be handled in DataResourceDataProvider
|
|
||||||
data: {
|
data: {
|
||||||
'application/vnd.dataresource+json': this.convertToDataResource(resultSet.columnInfo),
|
'application/vnd.dataresource+json': this.convertToDataResource(resultSet.columnInfo, subsetResult),
|
||||||
'text/html': this.convertToHtmlTable(resultSet.columnInfo)
|
'text/html': this.convertToHtmlTable(resultSet.columnInfo, subsetResult)
|
||||||
},
|
}
|
||||||
batchId: resultSet.batchId,
|
|
||||||
id: resultSet.id,
|
|
||||||
queryRunnerUri: this._queryRunner.uri,
|
|
||||||
},
|
},
|
||||||
metadata: undefined,
|
metadata: undefined,
|
||||||
parent_header: undefined
|
parent_header: undefined
|
||||||
};
|
};
|
||||||
this.ioHandler.handle(msg);
|
this.ioHandler.handle(msg);
|
||||||
|
this._querySubsetResultMap.delete(resultSet.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
setIOPubHandler(handler: nb.MessageHandler<nb.IIOPubMessage>): void {
|
setIOPubHandler(handler: nb.MessageHandler<nb.IIOPubMessage>): void {
|
||||||
@@ -553,31 +561,49 @@ export class SQLFuture extends Disposable implements FutureInternal {
|
|||||||
// no-op
|
// no-op
|
||||||
}
|
}
|
||||||
|
|
||||||
private convertToDataResource(columns: IColumn[]): IDataResource {
|
private convertToDataResource(columns: IColumn[], subsetResult: ResultSetSubset): IDataResource {
|
||||||
let columnsResources: IDataResourceSchema[] = [];
|
let columnsResources: IDataResourceSchema[] = [];
|
||||||
columns.forEach(column => {
|
columns.forEach(column => {
|
||||||
columnsResources.push({ name: escape(column.columnName) });
|
columnsResources.push({ name: escape(column.columnName) });
|
||||||
});
|
});
|
||||||
let columnsFields: IDataResourceFields = { fields: columnsResources };
|
let columnsFields: IDataResourceFields = { fields: undefined };
|
||||||
|
columnsFields.fields = columnsResources;
|
||||||
return {
|
return {
|
||||||
schema: columnsFields,
|
schema: columnsFields,
|
||||||
data: []
|
data: subsetResult.rows.map(row => {
|
||||||
|
let rowObject: { [key: string]: any; } = {};
|
||||||
|
row.forEach((val, index) => {
|
||||||
|
rowObject[index] = val.displayValue;
|
||||||
|
});
|
||||||
|
return rowObject;
|
||||||
|
})
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private convertToHtmlTable(columns: IColumn[]): string[] {
|
private convertToHtmlTable(columns: IColumn[], d: ResultSetSubset): string[] {
|
||||||
let htmlTable: string[] = new Array(3);
|
// Adding 3 for <table>, column title rows, </table>
|
||||||
htmlTable[0] = '<table>';
|
let htmlStringArr: string[] = new Array(d.rowCount + 3);
|
||||||
|
htmlStringArr[0] = '<table>';
|
||||||
if (columns.length > 0) {
|
if (columns.length > 0) {
|
||||||
let columnHeaders = '<tr>';
|
let columnHeaders = '<tr>';
|
||||||
for (let column of columns) {
|
for (let column of columns) {
|
||||||
columnHeaders += `<th>${escape(column.columnName)}</th>`;
|
columnHeaders += `<th>${escape(column.columnName)}</th>`;
|
||||||
}
|
}
|
||||||
columnHeaders += '</tr>';
|
columnHeaders += '</tr>';
|
||||||
htmlTable[1] = columnHeaders;
|
htmlStringArr[1] = columnHeaders;
|
||||||
}
|
}
|
||||||
htmlTable[2] = '</table>';
|
let i = 2;
|
||||||
return htmlTable;
|
for (const row of d.rows) {
|
||||||
|
let rowData = '<tr>';
|
||||||
|
for (let columnIndex = 0; columnIndex < columns.length; columnIndex++) {
|
||||||
|
rowData += `<td>${escape(row[columnIndex].displayValue)}</td>`;
|
||||||
|
}
|
||||||
|
rowData += '</tr>';
|
||||||
|
htmlStringArr[i] = rowData;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
htmlStringArr[htmlStringArr.length - 1] = '</table>';
|
||||||
|
return htmlStringArr;
|
||||||
}
|
}
|
||||||
|
|
||||||
private convertToDisplayMessage(msg: IResultMessage | string): nb.IIOPubMessage {
|
private convertToDisplayMessage(msg: IResultMessage | string): nb.IIOPubMessage {
|
||||||
|
|||||||
@@ -29,8 +29,13 @@ export class ServerTreeDataSource implements IDataSource {
|
|||||||
* Returns the unique identifier of the given element.
|
* Returns the unique identifier of the given element.
|
||||||
* No more than one element may use a given identifier.
|
* No more than one element may use a given identifier.
|
||||||
*/
|
*/
|
||||||
public getId(tree: ITree, element: any): string {
|
public getId(tree: ITree, element?: any): string {
|
||||||
return element.id;
|
// Note there really shouldn't be any undefined elements in the tree, but the original implementation
|
||||||
|
// didn't do that correctly and since this is going to replaced by the async tree at some point just
|
||||||
|
// making it so we handle the undefined case here.
|
||||||
|
// This should be safe to do since the undefined element is only used when we want to clear the tree
|
||||||
|
// so it'll be the only "element" in the tree and thus there shouldn't be any duplicate ids
|
||||||
|
return element?.id || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -555,6 +555,8 @@ export interface ITreeView extends IDisposable {
|
|||||||
|
|
||||||
title: string;
|
title: string;
|
||||||
|
|
||||||
|
root: ITreeItem; // {{SQL CARBON EDIT}}
|
||||||
|
|
||||||
readonly visible: boolean;
|
readonly visible: boolean;
|
||||||
|
|
||||||
readonly onDidExpandItem: Event<ITreeItem>;
|
readonly onDidExpandItem: Event<ITreeItem>;
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ export class TreeView extends Disposable implements ITreeView {
|
|||||||
private tree: Tree | undefined;
|
private tree: Tree | undefined;
|
||||||
private treeLabels: ResourceLabels | undefined;
|
private treeLabels: ResourceLabels | undefined;
|
||||||
|
|
||||||
private root: ITreeItem;
|
public root: ITreeItem; // {{SQL CARBON EDIT}}
|
||||||
private elementsToRefresh: ITreeItem[] = [];
|
private elementsToRefresh: ITreeItem[] = [];
|
||||||
|
|
||||||
private readonly _onDidExpandItem: Emitter<ITreeItem> = this._register(new Emitter<ITreeItem>());
|
private readonly _onDidExpandItem: Emitter<ITreeItem> = this._register(new Emitter<ITreeItem>());
|
||||||
|
|||||||
Reference in New Issue
Block a user