mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-23 18:47:06 -05:00
Compare commits
282 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e8eb7bec1b | ||
|
|
565b7404f9 | ||
|
|
9cffe4d476 | ||
|
|
43be88a37c | ||
|
|
ea67859de7 | ||
|
|
c8986464ec | ||
|
|
7804f94d8b | ||
|
|
bfa77aebfc | ||
|
|
487fb02313 | ||
|
|
ef64038107 | ||
|
|
5d336accbc | ||
|
|
99047b2866 | ||
|
|
f611cf3b5a | ||
|
|
4ad059605c | ||
|
|
dc2ff97dd8 | ||
|
|
2b5265c103 | ||
|
|
2e98fde053 | ||
|
|
d5176e0eb7 | ||
|
|
eb0b2a847b | ||
|
|
cff5482f69 | ||
|
|
afc37973d0 | ||
|
|
3eada6c6ab | ||
|
|
7c39268fe5 | ||
|
|
eb67b299de | ||
|
|
3e7a09c1e3 | ||
|
|
637dc9b9b2 | ||
|
|
1de16d4715 | ||
|
|
49090d774d | ||
|
|
9a695b5cdd | ||
|
|
e0339b50c0 | ||
|
|
d0c584672f | ||
|
|
27816acaeb | ||
|
|
4de3cc8a09 | ||
|
|
5c16ceb2fa | ||
|
|
9db3f73413 | ||
|
|
e0ceddce09 | ||
|
|
6dc4096299 | ||
|
|
1fa03b5c74 | ||
|
|
f8f57a93c3 | ||
|
|
960fe63312 | ||
|
|
7545b94128 | ||
|
|
1263a27c1c | ||
|
|
e1c084d365 | ||
|
|
7465ec0bbd | ||
|
|
17ed57836f | ||
|
|
d0acb51fd7 | ||
|
|
71c1ed6c49 | ||
|
|
bfb68254a4 | ||
|
|
18f7662209 | ||
|
|
a0d84f383c | ||
|
|
1f447ae681 | ||
|
|
8bd6691331 | ||
|
|
42afcf9322 | ||
|
|
3d3694bb8d | ||
|
|
589b913960 | ||
|
|
7ba4f42494 | ||
|
|
c96118d2b5 | ||
|
|
0285d8cd38 | ||
|
|
ee87604a4d | ||
|
|
2235ebaf20 | ||
|
|
954d0d954f | ||
|
|
e31747d087 | ||
|
|
fc581253a4 | ||
|
|
47c4609f23 | ||
|
|
2d52bc2a49 | ||
|
|
5367101330 | ||
|
|
db145b4999 | ||
|
|
7f950ddb80 | ||
|
|
50e2251e74 | ||
|
|
33d5455b6f | ||
|
|
ac018500cd | ||
|
|
3e661db283 | ||
|
|
18fb78b3ec | ||
|
|
58ff13d399 | ||
|
|
0ac0175bb1 | ||
|
|
f39007cd2d | ||
|
|
2349aa4df8 | ||
|
|
a93a173183 | ||
|
|
42e55dd2dd | ||
|
|
ca3146d38f | ||
|
|
7f6cd514a5 | ||
|
|
88e24e92b5 | ||
|
|
8b447e361f | ||
|
|
a92dd2d4e4 | ||
|
|
852ec44567 | ||
|
|
b6e32cdeb4 | ||
|
|
4bd264d9be | ||
|
|
4a4b8574d0 | ||
|
|
ded073edd9 | ||
|
|
568f95e7a3 | ||
|
|
5adcabc8de | ||
|
|
e3bce7172c | ||
|
|
96fb618390 | ||
|
|
2d4fdcb661 | ||
|
|
7a84cff5b4 | ||
|
|
2af627b704 | ||
|
|
77fdf18686 | ||
|
|
944a77fe42 | ||
|
|
049678b32e | ||
|
|
3325e4d854 | ||
|
|
1e90e88d4b | ||
|
|
8aeb33c98c | ||
|
|
3b08721835 | ||
|
|
5a30878599 | ||
|
|
c8a8935db0 | ||
|
|
ec196f57bb | ||
|
|
f7809ec3a7 | ||
|
|
71d3ec3616 | ||
|
|
4a7cf8d870 | ||
|
|
4bf8836c0a | ||
|
|
1ca36ee29c | ||
|
|
3446ff88cf | ||
|
|
de5a91a13f | ||
|
|
814cd73019 | ||
|
|
c21611661b | ||
|
|
8f817ce689 | ||
|
|
971b5111e7 | ||
|
|
07069a64ae | ||
|
|
6acea51f12 | ||
|
|
7aa2dab307 | ||
|
|
3091be8f67 | ||
|
|
487531cc52 | ||
|
|
58bfcb4273 | ||
|
|
8d8be27f22 | ||
|
|
27a978cba5 | ||
|
|
71b4e6afa4 | ||
|
|
e1f3b19c0c | ||
|
|
649c2aa5a6 | ||
|
|
cac8cc99e1 | ||
|
|
cb162b16f2 | ||
|
|
86e54ce145 | ||
|
|
efd809971f | ||
|
|
38ae14cc4d | ||
|
|
c7e33a90fe | ||
|
|
5add835750 | ||
|
|
734c614cba | ||
|
|
f6b347fa62 | ||
|
|
08d2f3125e | ||
|
|
385c48dcad | ||
|
|
0926057bfe | ||
|
|
6912e3893e | ||
|
|
d3052657df | ||
|
|
a5ca4d8edf | ||
|
|
afb1ebebd5 | ||
|
|
a04a9eb5ad | ||
|
|
027badd21f | ||
|
|
1affc760e6 | ||
|
|
3ca72b7398 | ||
|
|
702dbddd78 | ||
|
|
8fbecc0227 | ||
|
|
421271acfa | ||
|
|
98af76b3ac | ||
|
|
3952fdbe2d | ||
|
|
bc13beaa85 | ||
|
|
59b2e706ca | ||
|
|
8bf835c531 | ||
|
|
087ed7c132 | ||
|
|
4c075df327 | ||
|
|
9ea8baca05 | ||
|
|
9b6784720e | ||
|
|
3761e1dd60 | ||
|
|
b3eb809550 | ||
|
|
cb72865dcc | ||
|
|
d646b4729b | ||
|
|
a2dd903d0d | ||
|
|
28ed378ee7 | ||
|
|
15ae55136f | ||
|
|
b18abd954f | ||
|
|
b45f79a1f8 | ||
|
|
01a03b4c84 | ||
|
|
e48328af34 | ||
|
|
8925d44807 | ||
|
|
213283510f | ||
|
|
9f8190dc28 | ||
|
|
c32d4ee2f7 | ||
|
|
2c867a4b2c | ||
|
|
da5194bdcb | ||
|
|
bbb27aed10 | ||
|
|
c02fbaeae7 | ||
|
|
847218da73 | ||
|
|
90dc788893 | ||
|
|
f3525cc555 | ||
|
|
198f243181 | ||
|
|
8e049f4af5 | ||
|
|
ff465a59b6 | ||
|
|
68b4f3ca04 | ||
|
|
6b31f2b3f2 | ||
|
|
63cf0f1548 | ||
|
|
e607f68b3e | ||
|
|
db3bb82dbd | ||
|
|
5889c600fa | ||
|
|
d7d4c7236c | ||
|
|
f54d8ce36f | ||
|
|
43faa13cb5 | ||
|
|
85a2d994f3 | ||
|
|
d8cd78cd6b | ||
|
|
3e59a5bcd2 | ||
|
|
0efb89d6ff | ||
|
|
6697c075cb | ||
|
|
06660160e7 | ||
|
|
0b571737b7 | ||
|
|
0a486a280d | ||
|
|
bd53e685d0 | ||
|
|
a2bbf3f44e | ||
|
|
410bb62906 | ||
|
|
cbb4ac3e20 | ||
|
|
7508192ab9 | ||
|
|
bbf6cbd8fb | ||
|
|
9765269d27 | ||
|
|
61746b7ff7 | ||
|
|
e6066c2cb5 | ||
|
|
633a918590 | ||
|
|
71c14a0837 | ||
|
|
9bbed2c275 | ||
|
|
d9ba4d9130 | ||
|
|
ecd40de7ec | ||
|
|
e2bd6c06ec | ||
|
|
a26be76d79 | ||
|
|
3b68c1eb69 | ||
|
|
f7879bdbf9 | ||
|
|
dbb0fc519f | ||
|
|
b931ccfabf | ||
|
|
eeab048f46 | ||
|
|
5da89ac05b | ||
|
|
e2b446be1c | ||
|
|
5f2e17a738 | ||
|
|
399d6d0045 | ||
|
|
f36f3ffd21 | ||
|
|
3c785ae7d8 | ||
|
|
00cd772cbc | ||
|
|
d434724a54 | ||
|
|
b6c9a3bb89 | ||
|
|
fc3bf45a7f | ||
|
|
cbf6c06e4b | ||
|
|
cff21124da | ||
|
|
ce7893c2e5 | ||
|
|
f7dcaa38ff | ||
|
|
8ce1013a26 | ||
|
|
114d67b408 | ||
|
|
56c2d16560 | ||
|
|
2238c42432 | ||
|
|
64f6cf6747 | ||
|
|
c0e9f1ca43 | ||
|
|
7f66087d8c | ||
|
|
18671b7cca | ||
|
|
233156c744 | ||
|
|
ac0ffab99c | ||
|
|
5e964d2105 | ||
|
|
59e7a5fa4b | ||
|
|
8452f577d2 | ||
|
|
726eb8d0e1 | ||
|
|
2c0d6b93ee | ||
|
|
98d06b2892 | ||
|
|
533f2734f1 | ||
|
|
856c19fa17 | ||
|
|
3237507bb2 | ||
|
|
ff5a248240 | ||
|
|
fc3bdc9037 | ||
|
|
533c0bf0b1 | ||
|
|
afb6862035 | ||
|
|
92d9d04a78 | ||
|
|
0704e5ee67 | ||
|
|
7dfcd89a04 | ||
|
|
2859bee4c0 | ||
|
|
724c49f5c4 | ||
|
|
48c4a473df | ||
|
|
3d39272536 | ||
|
|
0a63966cbd | ||
|
|
89e959c2e3 | ||
|
|
c56dd2cdbd | ||
|
|
363f28a46b | ||
|
|
5549f51a13 | ||
|
|
972f857c71 | ||
|
|
d2eb1488fd | ||
|
|
82486ee22e | ||
|
|
29cc57f52a | ||
|
|
508e4eac61 | ||
|
|
fd1d807012 | ||
|
|
906c4c7f39 | ||
|
|
668e43f57c | ||
|
|
6d260c195f | ||
|
|
fdfecbb3f7 |
4
.github/ISSUE_TEMPLATE/bug_report.md
vendored
4
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,6 +1,10 @@
|
|||||||
---
|
---
|
||||||
name: Bug report
|
name: Bug report
|
||||||
about: Create a report to help us improve
|
about: Create a report to help us improve
|
||||||
|
title: ''
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<!-- Please search existing issues to avoid creating duplicates. -->
|
<!-- Please search existing issues to avoid creating duplicates. -->
|
||||||
|
|||||||
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest an idea for this project
|
||||||
|
title: ''
|
||||||
|
labels: feature request
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Is your feature request related to a problem? Please describe.**
|
||||||
|
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||||
|
|
||||||
|
**Describe the solution or feature you'd like**
|
||||||
|
A clear and concise description of what you want to happen.
|
||||||
|
|
||||||
|
**Describe alternatives you've considered**
|
||||||
|
A clear and concise description of any alternative solutions or features you've considered.
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context or screenshots about the feature request here.
|
||||||
58
.travis.yml
58
.travis.yml
@@ -1,58 +0,0 @@
|
|||||||
sudo: false
|
|
||||||
language: cpp
|
|
||||||
|
|
||||||
os:
|
|
||||||
- linux
|
|
||||||
- osx
|
|
||||||
|
|
||||||
cache:
|
|
||||||
directories:
|
|
||||||
- $HOME/.cache/yarn
|
|
||||||
|
|
||||||
notifications:
|
|
||||||
email: false
|
|
||||||
webhooks:
|
|
||||||
- http://vscode-probot.westus.cloudapp.azure.com:3450/travis/notifications
|
|
||||||
- http://vscode-test-probot.westus.cloudapp.azure.com:3450/travis/notifications
|
|
||||||
|
|
||||||
addons:
|
|
||||||
apt:
|
|
||||||
sources:
|
|
||||||
- ubuntu-toolchain-r-test
|
|
||||||
packages:
|
|
||||||
- gcc-4.9
|
|
||||||
- g++-4.9
|
|
||||||
- gcc-4.9-multilib
|
|
||||||
- g++-4.9-multilib
|
|
||||||
- zip
|
|
||||||
- libgtk2.0-0
|
|
||||||
- libx11-dev
|
|
||||||
- libxkbfile-dev
|
|
||||||
- libsecret-1-dev
|
|
||||||
|
|
||||||
before_install:
|
|
||||||
- git submodule update --init --recursive
|
|
||||||
- nvm install 8.9.1
|
|
||||||
- nvm use 8.9.1
|
|
||||||
- npm i -g yarn
|
|
||||||
# - npm config set python `which python`
|
|
||||||
- if [ $TRAVIS_OS_NAME == "linux" ]; then
|
|
||||||
export CXX="g++-4.9" CC="gcc-4.9" DISPLAY=:99.0;
|
|
||||||
sh -e /etc/init.d/xvfb start;
|
|
||||||
sleep 3;
|
|
||||||
fi
|
|
||||||
# Make npm logs less verbose
|
|
||||||
# - npm config set depth 0
|
|
||||||
# - npm config set loglevel warn
|
|
||||||
|
|
||||||
install:
|
|
||||||
- yarn
|
|
||||||
|
|
||||||
script:
|
|
||||||
- node_modules/.bin/gulp electron --silent
|
|
||||||
- node_modules/.bin/gulp compile --silent --max_old_space_size=4096
|
|
||||||
- node_modules/.bin/gulp optimize-vscode --silent --max_old_space_size=4096
|
|
||||||
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then ./scripts/test.sh --coverage --reporter dot; else ./scripts/test.sh --reporter dot; fi
|
|
||||||
|
|
||||||
after_success:
|
|
||||||
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then node_modules/.bin/coveralls < .build/coverage/lcov.info; fi
|
|
||||||
34
.vscode/launch.json
vendored
34
.vscode/launch.json
vendored
@@ -92,6 +92,30 @@
|
|||||||
"webRoot": "${workspaceFolder}",
|
"webRoot": "${workspaceFolder}",
|
||||||
"timeout": 45000
|
"timeout": 45000
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "chrome",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Launch azuredatastudio with new notebook command",
|
||||||
|
"windows": {
|
||||||
|
"runtimeExecutable": "${workspaceFolder}/scripts/sql.bat"
|
||||||
|
},
|
||||||
|
"osx": {
|
||||||
|
"runtimeExecutable": "${workspaceFolder}/scripts/sql.sh"
|
||||||
|
},
|
||||||
|
"linux": {
|
||||||
|
"runtimeExecutable": "${workspaceFolder}/scripts/sql.sh"
|
||||||
|
},
|
||||||
|
"urlFilter": "*index.html*",
|
||||||
|
"runtimeArgs": [
|
||||||
|
"--inspect=5875",
|
||||||
|
"--command=notebook.command.new"
|
||||||
|
],
|
||||||
|
"skipFiles": [
|
||||||
|
"**/winjs*.js"
|
||||||
|
],
|
||||||
|
"webRoot": "${workspaceFolder}",
|
||||||
|
"timeout": 45000
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "node",
|
"type": "node",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
@@ -128,6 +152,16 @@
|
|||||||
"args": [
|
"args": [
|
||||||
"--extensionDevelopmentPath=${workspaceRoot}/extensions/debug-auto-launch"
|
"--extensionDevelopmentPath=${workspaceRoot}/extensions/debug-auto-launch"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Launch Smoke Test",
|
||||||
|
"program": "${workspaceFolder}/test/smoke/test/index.js",
|
||||||
|
"cwd": "${workspaceFolder}/test/smoke",
|
||||||
|
"env": {
|
||||||
|
"BUILD_ARTIFACTSTAGINGDIRECTORY": "${workspaceFolder}"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"compounds": [
|
"compounds": [
|
||||||
|
|||||||
45
CHANGELOG.md
45
CHANGELOG.md
@@ -1,5 +1,50 @@
|
|||||||
# Change Log
|
# Change Log
|
||||||
|
|
||||||
|
## Version 1.3.8
|
||||||
|
* Release date: January 9, 2019
|
||||||
|
* Release status: General Availability
|
||||||
|
|
||||||
|
## What's new in this version
|
||||||
|
* #13 Feature Request: Azure Active Directory Authentication
|
||||||
|
* #1040 Stream initial query results as they become available
|
||||||
|
* #3298 Сan't add an azure account.
|
||||||
|
* #2387 Support Per-User Installer
|
||||||
|
* SQL Server Import updates for DACPAC\BACPAC
|
||||||
|
* SQL Server Profiler UI and UX improvements
|
||||||
|
* Updates to [SQL Server 2019 extension](https://docs.microsoft.com/sql/azure-data-studio/sql-server-2019-extension?view=sql-server-ver15)
|
||||||
|
* **sp_executesql to SQL** and **New Database** extensions
|
||||||
|
|
||||||
|
## Contributions and "thank you"
|
||||||
|
We would like to thank all our users who raised issues, and in particular the following users who helped contribute fixes:
|
||||||
|
|
||||||
|
* Tarig0 for `Add Routine_Type to CreateStoredProc fixes #3257 (#3286)`
|
||||||
|
* oltruong for `typo fix #3025'`
|
||||||
|
* Thomas-S-B for `Removed unnecessary IErrorDetectionStrategy #749`
|
||||||
|
* Thomas-S-B for `Simplified code #750`
|
||||||
|
|
||||||
|
## Version 1.2.4
|
||||||
|
* Release date: November 6, 2018
|
||||||
|
* Release status: General Availability
|
||||||
|
|
||||||
|
## What's new in this version
|
||||||
|
* Update to the SQL Server 2019 Preview extension
|
||||||
|
* Introducing Paste the Plan extension
|
||||||
|
* Introducing High Color queries extension, including SSMS editor theme
|
||||||
|
* Fixes in SQL Server Agent, Profiler, and Import extensions
|
||||||
|
* Fix .Net Core Socket KeepAlive issue causing dropped inactive connections on macOS
|
||||||
|
* Upgrade SQL Tools Service to .Net Core 2.2 Preview 3 (for eventual AAD support)
|
||||||
|
* Fix customer reported GitHub issues
|
||||||
|
|
||||||
|
## Contributions and "thank you"
|
||||||
|
We would like to thank all our users who raised issues, and in particular the following users who helped contribute fixes:
|
||||||
|
|
||||||
|
* rdaniels6813 for `Add query plan theme support #3031`
|
||||||
|
* Ruturaj123 for `Fixed some typos and grammatical errors #3027`
|
||||||
|
* PromoFaux for `Use emoji shortcodes in CONTRIBUTING.md instead of <20> #3009`
|
||||||
|
* ckaczor for `Fix: DATETIMEOFFSET data types should be ISO formatted #714`
|
||||||
|
* hi-im-T0dd for `Fixed sync issue with my forked master so this commit is correct #2948`
|
||||||
|
* hi-im-T0dd for `Fixed when right clicking and selecting Manage-correct name displays #2794`
|
||||||
|
|
||||||
## Version 1.1.3
|
## Version 1.1.3
|
||||||
* Release date: October 18, 2018
|
* Release date: October 18, 2018
|
||||||
* Release status: General Availability
|
* Release status: General Availability
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ First, please do a search in [open issues](https://github.com/Microsoft/azuredat
|
|||||||
|
|
||||||
If you find your issue already exists, make relevant comments and add your [reaction](https://github.com/blog/2119-add-reactions-to-pull-requests-issues-and-comments). Use a reaction in place of a "+1" comment.
|
If you find your issue already exists, make relevant comments and add your [reaction](https://github.com/blog/2119-add-reactions-to-pull-requests-issues-and-comments). Use a reaction in place of a "+1" comment.
|
||||||
|
|
||||||
<EFBFBD> - upvote
|
:+1: - upvote
|
||||||
|
|
||||||
<EFBFBD> - downvote
|
:-1: - downvote
|
||||||
|
|
||||||
If you cannot find an existing issue that describes your bug or feature, submit an issue using the guidelines below.
|
If you cannot find an existing issue that describes your bug or feature, submit an issue using the guidelines below.
|
||||||
|
|
||||||
|
|||||||
41
README.md
41
README.md
@@ -1,6 +1,7 @@
|
|||||||
# Azure Data Studio
|
# Azure Data Studio
|
||||||
|
|
||||||
[](https://gitter.im/Microsoft/sqlopsstudio?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
[](https://gitter.im/Microsoft/sqlopsstudio?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||||
|
[](https://dev.azure.com/ms/azuredatastudio/_build/latest?definitionId=4)
|
||||||
|
|
||||||
Azure Data Studio is a data management tool that enables you to work with SQL Server, Azure SQL DB and SQL DW from Windows, macOS and Linux.
|
Azure Data Studio is a data management tool that enables you to work with SQL Server, Azure SQL DB and SQL DW from Windows, macOS and Linux.
|
||||||
|
|
||||||
@@ -8,12 +9,13 @@ Azure Data Studio is a data management tool that enables you to work with SQL Se
|
|||||||
|
|
||||||
Platform | Link
|
Platform | Link
|
||||||
-- | --
|
-- | --
|
||||||
Windows Setup Installer | https://go.microsoft.com/fwlink/?linkid=2030731
|
Windows User Installer | https://go.microsoft.com/fwlink/?linkid=2049972
|
||||||
Windows ZIP | https://go.microsoft.com/fwlink/?linkid=2030736
|
Windows System Installer | https://go.microsoft.com/fwlink/?linkid=2049975
|
||||||
macOS ZIP | https://go.microsoft.com/fwlink/?linkid=2030738
|
Windows ZIP | https://go.microsoft.com/fwlink/?linkid=2050146
|
||||||
Linux TAR.GZ | https://go.microsoft.com/fwlink/?linkid=2030741
|
macOS ZIP | https://go.microsoft.com/fwlink/?linkid=2049981
|
||||||
Linux RPM | https://go.microsoft.com/fwlink/?linkid=2030746
|
Linux TAR.GZ | https://go.microsoft.com/fwlink/?linkid=2049986
|
||||||
Linux DEB | https://go.microsoft.com/fwlink/?linkid=2030750
|
Linux RPM | https://go.microsoft.com/fwlink/?linkid=2049989
|
||||||
|
Linux DEB | https://go.microsoft.com/fwlink/?linkid=2050157
|
||||||
|
|
||||||
Go to our [download page](https://aka.ms/azuredatastudio) for more specific instructions.
|
Go to our [download page](https://aka.ms/azuredatastudio) for more specific instructions.
|
||||||
|
|
||||||
@@ -34,9 +36,9 @@ See the [change log](https://github.com/Microsoft/azuredatastudio/blob/master/CH
|
|||||||
- Task History window to view current task execution status, completion results with error messages and task T-SQL scripting
|
- Task History window to view current task execution status, completion results with error messages and task T-SQL scripting
|
||||||
- Scripting support to generate CREATE, SELECT, ALTER and DROP statements for database objects
|
- Scripting support to generate CREATE, SELECT, ALTER and DROP statements for database objects
|
||||||
- Workspaces with full Git integration and Find In Files support to managing T-SQL script libraries
|
- Workspaces with full Git integration and Find In Files support to managing T-SQL script libraries
|
||||||
- Modern light-weight shell with theming, user settings, full screen support, integrated terminal and numerous other features
|
- Modern light-weight shell with theming, user settings, full-screen support, integrated terminal and numerous other features
|
||||||
|
|
||||||
Here's some of these features in action.
|
Here are some of these features in action.
|
||||||
|
|
||||||
<img src='https://github.com/Microsoft/azuredatastudio/blob/master/docs/overview_screen.jpg' width='800px'>
|
<img src='https://github.com/Microsoft/azuredatastudio/blob/master/docs/overview_screen.jpg' width='800px'>
|
||||||
|
|
||||||
@@ -61,6 +63,16 @@ The [Microsoft Enterprise and Developer Privacy Statement](https://privacy.micro
|
|||||||
## Contributions and "Thank You"
|
## Contributions and "Thank You"
|
||||||
We would like to thank all our users who raised issues, and in particular the following users who helped contribute fixes:
|
We would like to thank all our users who raised issues, and in particular the following users who helped contribute fixes:
|
||||||
|
|
||||||
|
* Tarig0 for `Add Routine_Type to CreateStoredProc fixes #3257 (#3286)`
|
||||||
|
* oltruong for `typo fix #3025'`
|
||||||
|
* Thomas-S-B for `Removed unnecessary IErrorDetectionStrategy #749`
|
||||||
|
* Thomas-S-B for `Simplified code #750`
|
||||||
|
* rdaniels6813 for `Add query plan theme support #3031`
|
||||||
|
* Ruturaj123 for `Fixed some typos and grammatical errors #3027`
|
||||||
|
* PromoFaux for `Use emoji shortcodes in CONTRIBUTING.md instead of <20> #3009`
|
||||||
|
* ckaczor for `Fix: DATETIMEOFFSET data types should be ISO formatted #714`
|
||||||
|
* hi-im-T0dd for `Fixed sync issue with my forked master so this commit is correct #2948`
|
||||||
|
* hi-im-T0dd for `Fixed when right clicking and selecting Manage-correct name displays #2794`
|
||||||
* philoushka for `center the icon #2760`
|
* philoushka for `center the icon #2760`
|
||||||
* anthonypants for `Typo #2775`
|
* anthonypants for `Typo #2775`
|
||||||
* kstolte for `Fix Invalid Configuration in Launch.json #2789`
|
* kstolte for `Fix Invalid Configuration in Launch.json #2789`
|
||||||
@@ -69,25 +81,25 @@ We would like to thank all our users who raised issues, and in particular the fo
|
|||||||
* AlexFsmn `Disabled connection name input when connecting to a server. #2566`
|
* AlexFsmn `Disabled connection name input when connecting to a server. #2566`
|
||||||
* SebastianPfliegel `Added more saveAsCsv options #2099`
|
* SebastianPfliegel `Added more saveAsCsv options #2099`
|
||||||
* ianychoi `Fixes a typo: Mimunum -> Minimum #1994`
|
* ianychoi `Fixes a typo: Mimunum -> Minimum #1994`
|
||||||
* AlexFsmn `Fixed bug where proper file extension wasn't appended to filename. #2151`
|
* AlexFsmn `Fixed bug where proper file extension wasn't appended to the filename. #2151`
|
||||||
* AlexFsmn `Added functionality for adding any file to import wizard #2329`
|
* AlexFsmn `Added functionality for adding any file to import wizard #2329`
|
||||||
* AlexFsmn `Fixed background issue when copying a chart to clipboard #2215`
|
* AlexFsmn `Fixed background issue when copying a chart to clipboard #2215`
|
||||||
* AlexFsmn `Fixed problem where vertical charts didn't display labels correctly. #2263`
|
* AlexFsmn `Fixed problem where vertical charts didn't display labels correctly. #2263`
|
||||||
* AlexFsmn `Fixed Initial values for charts to match visuals #2266`
|
* AlexFsmn `Fixed Initial values for charts to match visuals #2266`
|
||||||
* AlexFsmn `Renamed chart option labels #2264`
|
* AlexFsmn `Renamed chart option labels #2264`
|
||||||
* AlexFsmn `Added feature for opening file after exporting to CSV/XLS/JSON & query files #2216`
|
* AlexFsmn `Added feature for the opening file after exporting to CSV/XLS/JSON & query files #2216`
|
||||||
* AlexFsmm `Get Connection String should copy to clipboard #2175`
|
* AlexFsmm `Get Connection String should copy to clipboard #2175`
|
||||||
* lanceklinger `Fix for double clicking column handle in results table #1504`
|
* lanceklinger `Fix for double-clicking column handle in results table #1504`
|
||||||
* westerncj for `Removed duplicate contribution from README.md (#753)`
|
* westerncj for `Removed duplicate contribution from README.md (#753)`
|
||||||
* ntovas for `Fix for duplicate extensions shown in "Save File" dialog. (#779)`
|
* ntovas for `Fix for duplicate extensions shown in "Save File" dialog. (#779)`
|
||||||
* SebastianPfliegel for `Add cursor snippet (#475)`
|
* SebastianPfliegel for `Add cursor snippet (#475)`
|
||||||
* mikaoelitiana for fix: `revert README and CONTRIBUTING after last VSCode merge (#574)`
|
* mikaoelitiana for the fix: `revert README and CONTRIBUTING after last VSCode merge (#574)`
|
||||||
* alextercete for `Reinstate menu item to install from VSIX (#682)`
|
* alextercete for `Reinstate menu item to install from VSIX (#682)`
|
||||||
* alextercete for `Fix "No extension gallery service configured" error (#427)`
|
* alextercete for `Fix "No extension gallery service configured" error (#427)`
|
||||||
* mwiedemeyer for `Fix #58: Default sort order for DB size widget (#111)`
|
* mwiedemeyer for `Fix #58: Default sort order for DB size widget (#111)`
|
||||||
* AlexTroshkin for `Show disconnect in context menu only when connectionProfile connected (#150)`
|
* AlexTroshkin for `Show disconnect in context menu only when connectionProfile connected (#150)`
|
||||||
* AlexTroshkin for `Fix #138: Invalid syntax color highlighting (identity not highlighting) (#140))`
|
* AlexTroshkin for `Fix #138: Invalid syntax color highlighting (identity not highlighting) (#140))`
|
||||||
* stebet for `Fix #153: Fixing sql snippets that failed on a DB with case-sensitive collation. (#152)`
|
* stebet for `Fix #153: Fixing sql snippets that failed on a DB with a case-sensitive collation. (#152)`
|
||||||
* SebastianPfliegel `Remove sqlExtensionHelp (#312)`
|
* SebastianPfliegel `Remove sqlExtensionHelp (#312)`
|
||||||
* olljanat for `Implemented npm version check (#314)`
|
* olljanat for `Implemented npm version check (#314)`
|
||||||
* Adam Machanic for helping with the `whoisactive` extension
|
* Adam Machanic for helping with the `whoisactive` extension
|
||||||
@@ -103,8 +115,7 @@ We would like to thank all our users who raised issues, and in particular the fo
|
|||||||
* Russian: Andrey Veselov, Anton Fontanov, Anton Savin, Elena Ostrovskaia, Igor Babichev, Maxim Zelensky, Rodion Fedechkin, Tasha T, Vladimir Zyryanov
|
* Russian: Andrey Veselov, Anton Fontanov, Anton Savin, Elena Ostrovskaia, Igor Babichev, Maxim Zelensky, Rodion Fedechkin, Tasha T, Vladimir Zyryanov
|
||||||
* Portuguese Brazil: Daniel de Sousa, Diogo Duarte, Douglas Correa, Douglas Eccker, José Emanuel Mendes, Marcelo Fernandes, Marcondes Alexandre, Roberto Fonseca, Rodrigo Crespi
|
* Portuguese Brazil: Daniel de Sousa, Diogo Duarte, Douglas Correa, Douglas Eccker, José Emanuel Mendes, Marcelo Fernandes, Marcondes Alexandre, Roberto Fonseca, Rodrigo Crespi
|
||||||
|
|
||||||
|
And of course, we'd like to thank the authors of all upstream dependencies. Please see a full list in the [ThirdPartyNotices.txt](https://raw.githubusercontent.com/Microsoft/azuredatastudio/master/ThirdPartyNotices.txt)
|
||||||
And of course we'd like to thank the authors of all upstream dependencies. Please see a full list in the [ThirdPartyNotices.txt](https://raw.githubusercontent.com/Microsoft/azuredatastudio/master/ThirdPartyNotices.txt)
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ expressly granted herein, whether by implication, estoppel or otherwise.
|
|||||||
jquery-ui: https://github.com/jquery/jquery-ui
|
jquery-ui: https://github.com/jquery/jquery-ui
|
||||||
jquery.event.drag: https://github.com/devongovett/jquery.event.drag
|
jquery.event.drag: https://github.com/devongovett/jquery.event.drag
|
||||||
jschardet: https://github.com/aadsm/jschardet
|
jschardet: https://github.com/aadsm/jschardet
|
||||||
|
JupyterLab: https://github.com/jupyterlab/jupyterlab
|
||||||
make-error: https://github.com/JsCommunity/make-error
|
make-error: https://github.com/JsCommunity/make-error
|
||||||
minimist: https://github.com/substack/minimist
|
minimist: https://github.com/substack/minimist
|
||||||
moment: https://github.com/moment/moment
|
moment: https://github.com/moment/moment
|
||||||
@@ -1166,6 +1167,43 @@ That's all there is to it!
|
|||||||
=========================================
|
=========================================
|
||||||
END OF jschardet NOTICES AND INFORMATION
|
END OF jschardet NOTICES AND INFORMATION
|
||||||
|
|
||||||
|
%% JupyterLab NOTICES AND INFORMATION BEGIN HERE
|
||||||
|
Copyright (c) 2015 Project Jupyter Contributors
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
3. Neither the name of the copyright holder nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
Semver File License
|
||||||
|
===================
|
||||||
|
|
||||||
|
The semver.py file is from https://github.com/podhmo/python-semver
|
||||||
|
which is licensed under the "MIT" license. See the semver.py file for details.
|
||||||
|
|
||||||
|
END OF JupyterLab NOTICES AND INFORMATION
|
||||||
|
|
||||||
%% make-error NOTICES AND INFORMATION BEGIN HERE
|
%% make-error NOTICES AND INFORMATION BEGIN HERE
|
||||||
=========================================
|
=========================================
|
||||||
ISC © Julien Fontanet
|
ISC © Julien Fontanet
|
||||||
|
|||||||
19
appveyor.yml
19
appveyor.yml
@@ -1,19 +0,0 @@
|
|||||||
environment:
|
|
||||||
ELECTRON_RUN_AS_NODE: 1
|
|
||||||
VSCODE_BUILD_VERBOSE: true
|
|
||||||
|
|
||||||
cache:
|
|
||||||
- '%LOCALAPPDATA%\Yarn\cache'
|
|
||||||
|
|
||||||
install:
|
|
||||||
- ps: Install-Product node 8.9.1 x64
|
|
||||||
|
|
||||||
build_script:
|
|
||||||
- yarn
|
|
||||||
- .\node_modules\.bin\gulp electron
|
|
||||||
- npm run compile
|
|
||||||
|
|
||||||
test_script:
|
|
||||||
- node --version
|
|
||||||
- .\scripts\test.bat
|
|
||||||
- .\scripts\test-integration.bat
|
|
||||||
42
azure-pipelines-linux-mac.yml
Normal file
42
azure-pipelines-linux-mac.yml
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
steps:
|
||||||
|
- task: NodeTool@0
|
||||||
|
inputs:
|
||||||
|
versionSpec: '8.x'
|
||||||
|
displayName: 'Install Node.js'
|
||||||
|
|
||||||
|
- script: |
|
||||||
|
git submodule update --init --recursive
|
||||||
|
nvm install 8.9.1
|
||||||
|
nvm use 8.9.1
|
||||||
|
npm i -g yarn
|
||||||
|
displayName: 'preinstall'
|
||||||
|
|
||||||
|
- script: |
|
||||||
|
export CXX="g++-4.9" CC="gcc-4.9" DISPLAY=:99.0
|
||||||
|
sh -e /etc/init.d/xvfb start
|
||||||
|
sleep 3
|
||||||
|
displayName: 'Linux preinstall'
|
||||||
|
condition: eq(variables['Agent.OS'], 'Linux')
|
||||||
|
|
||||||
|
- script: |
|
||||||
|
yarn
|
||||||
|
displayName: 'Install'
|
||||||
|
|
||||||
|
- script: |
|
||||||
|
node_modules/.bin/gulp electron --silent
|
||||||
|
node_modules/.bin/gulp compile --silent --max_old_space_size=4096
|
||||||
|
node_modules/.bin/gulp optimize-vscode --silent --max_old_space_size=4096
|
||||||
|
displayName: 'Scripts'
|
||||||
|
|
||||||
|
- script: |
|
||||||
|
./scripts/test.sh --reporter mocha-junit-reporter
|
||||||
|
displayName: 'Tests'
|
||||||
|
|
||||||
|
- task: PublishTestResults@2
|
||||||
|
inputs:
|
||||||
|
testResultsFiles: '**/test-results.xml'
|
||||||
|
condition: succeededOrFailed()
|
||||||
|
|
||||||
|
- script: |
|
||||||
|
yarn run tslint
|
||||||
|
displayName: 'Run TSLint'
|
||||||
30
azure-pipelines-windows.yml
Normal file
30
azure-pipelines-windows.yml
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
steps:
|
||||||
|
- task: NodeTool@0
|
||||||
|
inputs:
|
||||||
|
versionSpec: '8.9'
|
||||||
|
displayName: 'Install Node.js'
|
||||||
|
|
||||||
|
- script: |
|
||||||
|
yarn
|
||||||
|
displayName: 'Yarn Install'
|
||||||
|
|
||||||
|
- script: |
|
||||||
|
.\node_modules\.bin\gulp electron
|
||||||
|
displayName: 'Electron'
|
||||||
|
|
||||||
|
- script: |
|
||||||
|
npm run compile
|
||||||
|
displayName: 'Compile'
|
||||||
|
|
||||||
|
- script: |
|
||||||
|
.\scripts\test.bat --reporter mocha-junit-reporter
|
||||||
|
displayName: 'Test'
|
||||||
|
|
||||||
|
- task: PublishTestResults@2
|
||||||
|
inputs:
|
||||||
|
testResultsFiles: 'test-results.xml'
|
||||||
|
condition: succeededOrFailed()
|
||||||
|
|
||||||
|
- script: |
|
||||||
|
yarn run tslint
|
||||||
|
displayName: 'Run TSLint'
|
||||||
29
azure-pipelines.yml
Normal file
29
azure-pipelines.yml
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
trigger:
|
||||||
|
- master
|
||||||
|
- releases/*
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
|
||||||
|
# All tasks on Windows
|
||||||
|
- job: build_all_windows
|
||||||
|
displayName: Build all tasks (Windows)
|
||||||
|
pool:
|
||||||
|
vmImage: vs2017-win2016
|
||||||
|
steps:
|
||||||
|
- template: azure-pipelines-windows.yml
|
||||||
|
|
||||||
|
# All tasks on Linux
|
||||||
|
- job: build_all_linux
|
||||||
|
displayName: Build all tasks (Linux)
|
||||||
|
pool:
|
||||||
|
vmImage: 'Ubuntu 16.04'
|
||||||
|
steps:
|
||||||
|
- template: azure-pipelines-linux-mac.yml
|
||||||
|
|
||||||
|
# All tasks on macOS
|
||||||
|
- job: build_all_darwin
|
||||||
|
displayName: Build all tasks (macOS)
|
||||||
|
pool:
|
||||||
|
vmImage: macos-10.13
|
||||||
|
steps:
|
||||||
|
- template: azure-pipelines-linux-mac.yml
|
||||||
@@ -65,6 +65,8 @@ const excludedExtensions = [
|
|||||||
'vscode-colorize-tests',
|
'vscode-colorize-tests',
|
||||||
'ms-vscode.node-debug',
|
'ms-vscode.node-debug',
|
||||||
'ms-vscode.node-debug2',
|
'ms-vscode.node-debug2',
|
||||||
|
// {{SQL CARBON EDIT}}
|
||||||
|
'integration-tests',
|
||||||
];
|
];
|
||||||
|
|
||||||
// {{SQL CARBON EDIT}}
|
// {{SQL CARBON EDIT}}
|
||||||
@@ -129,6 +131,7 @@ const vscodeResources = [
|
|||||||
'out-build/sql/parts/jobManagement/common/media/*.svg',
|
'out-build/sql/parts/jobManagement/common/media/*.svg',
|
||||||
'out-build/sql/media/objectTypes/*.svg',
|
'out-build/sql/media/objectTypes/*.svg',
|
||||||
'out-build/sql/media/icons/*.svg',
|
'out-build/sql/media/icons/*.svg',
|
||||||
|
'out-build/sql/parts/notebook/media/**/*.svg',
|
||||||
'!**/test/**'
|
'!**/test/**'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -9,20 +9,21 @@
|
|||||||
"@types/mime": "0.0.29",
|
"@types/mime": "0.0.29",
|
||||||
"@types/minimatch": "^3.0.3",
|
"@types/minimatch": "^3.0.3",
|
||||||
"@types/node": "8.0.33",
|
"@types/node": "8.0.33",
|
||||||
"@types/xml2js": "0.0.33",
|
|
||||||
"@types/request": "^2.47.0",
|
"@types/request": "^2.47.0",
|
||||||
|
"@types/xml2js": "0.0.33",
|
||||||
"azure-storage": "^2.1.0",
|
"azure-storage": "^2.1.0",
|
||||||
"decompress": "^4.2.0",
|
"decompress": "^4.2.0",
|
||||||
|
"del": "^3.0.0",
|
||||||
"documentdb": "1.13.0",
|
"documentdb": "1.13.0",
|
||||||
"service-downloader": "github:anthonydresser/service-downloader#0.1.5",
|
|
||||||
"fs-extra-promise": "^1.0.1",
|
"fs-extra-promise": "^1.0.1",
|
||||||
|
"github-releases": "^0.4.1",
|
||||||
"mime": "^1.3.4",
|
"mime": "^1.3.4",
|
||||||
"minimist": "^1.2.0",
|
"minimist": "^1.2.0",
|
||||||
|
"request": "^2.85.0",
|
||||||
|
"service-downloader": "github:anthonydresser/service-downloader#0.1.5",
|
||||||
"typescript": "2.9.2",
|
"typescript": "2.9.2",
|
||||||
"vscode": "^1.0.1",
|
"vscode": "^1.0.1",
|
||||||
"xml2js": "^0.4.17",
|
"xml2js": "^0.4.17"
|
||||||
"github-releases": "^0.4.1",
|
|
||||||
"request": "^2.85.0"
|
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"compile": "tsc -p tsconfig.build.json",
|
"compile": "tsc -p tsconfig.build.json",
|
||||||
|
|||||||
@@ -91,17 +91,17 @@ Filename: "{app}\{#ExeBasename}.exe"; Description: "{cm:LaunchProgram,{#NameLong
|
|||||||
#else
|
#else
|
||||||
#define SoftwareClassesRootKey "HKLM"
|
#define SoftwareClassesRootKey "HKLM"
|
||||||
#endif
|
#endif
|
||||||
Root: HKCR; Subkey: "{#RegValueName}SourceFile"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,{#NameLong}}"; Flags: uninsdeletekey
|
Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}SourceFile"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,{#NameLong}}"; Flags: uninsdeletekey
|
||||||
Root: HKCR; Subkey: "{#RegValueName}SourceFile\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\code_file.ico"
|
Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}SourceFile\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\code_file.ico"
|
||||||
Root: HKCR; Subkey: "{#RegValueName}SourceFile\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""
|
Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}SourceFile\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""
|
||||||
Root: HKCU; Subkey: "Environment"; ValueType: expandsz; ValueName: "Path"; ValueData: "{olddata};{app}\bin"; Tasks: addtopath; Check: NeedsAddPath(ExpandConstant('{app}\bin'))
|
Root: HKCU; Subkey: "Environment"; ValueType: expandsz; ValueName: "Path"; ValueData: "{olddata};{app}\bin"; Tasks: addtopath; Check: NeedsAddPath(ExpandConstant('{app}\bin'))
|
||||||
|
|
||||||
Root: HKCU; Subkey: "Software\Classes\.sql\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles
|
Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.sql\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles
|
||||||
Root: HKCU; Subkey: "Software\Classes\.sql\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.sql"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles
|
Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.sql\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.sql"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles
|
||||||
Root: HKCU; Subkey: "Software\Classes\{#RegValueName}.sql"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,SQL}"; Flags: uninsdeletekey; Tasks: associatewithfiles
|
Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.sql"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,SQL}"; Flags: uninsdeletekey; Tasks: associatewithfiles
|
||||||
Root: HKCU; Subkey: "Software\Classes\{#RegValueName}.sql"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles
|
Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.sql"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles
|
||||||
Root: HKCU; Subkey: "Software\Classes\{#RegValueName}.sql\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\code_file.ico"; Tasks: associatewithfiles
|
Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.sql\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\code_file.ico"; Tasks: associatewithfiles
|
||||||
Root: HKCU; Subkey: "Software\Classes\{#RegValueName}.sql\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles
|
Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.sql\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles
|
||||||
; Environment
|
; Environment
|
||||||
#if "user" == InstallTarget
|
#if "user" == InstallTarget
|
||||||
#define EnvironmentRootKey "HKCU"
|
#define EnvironmentRootKey "HKCU"
|
||||||
|
|||||||
441
build/yarn.lock
441
build/yarn.lock
File diff suppressed because it is too large
Load Diff
1
docs/UX-Design-Guidelines.md
Normal file
1
docs/UX-Design-Guidelines.md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
"name": "agent",
|
"name": "agent",
|
||||||
"displayName": "SQL Server Agent",
|
"displayName": "SQL Server Agent",
|
||||||
"description": "Manage and troubleshoot SQL Server Agent jobs",
|
"description": "Manage and troubleshoot SQL Server Agent jobs",
|
||||||
"version": "0.34.0",
|
"version": "0.35.2",
|
||||||
"publisher": "Microsoft",
|
"publisher": "Microsoft",
|
||||||
"preview": true,
|
"preview": true,
|
||||||
"license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/master/LICENSE.txt",
|
"license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/master/LICENSE.txt",
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import * as vscode from 'vscode';
|
|||||||
import * as sqlops from 'sqlops';
|
import * as sqlops from 'sqlops';
|
||||||
import { AgentUtils } from '../agentUtils';
|
import { AgentUtils } from '../agentUtils';
|
||||||
import { IAgentDialogData, AgentDialogMode } from '../interfaces';
|
import { IAgentDialogData, AgentDialogMode } from '../interfaces';
|
||||||
|
import { JobData } from './jobData';
|
||||||
|
|
||||||
const localize = nls.loadMessageBundle();
|
const localize = nls.loadMessageBundle();
|
||||||
|
|
||||||
@@ -45,8 +46,19 @@ export class AlertData implements IAgentDialogData {
|
|||||||
wmiEventNamespace: string;
|
wmiEventNamespace: string;
|
||||||
wmiEventQuery: string;
|
wmiEventQuery: string;
|
||||||
|
|
||||||
constructor(ownerUri:string, alertInfo: sqlops.AgentAlertInfo) {
|
private viaJobDialog: boolean;
|
||||||
|
private jobModel: JobData;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
ownerUri:string,
|
||||||
|
alertInfo: sqlops.AgentAlertInfo,
|
||||||
|
jobModel?: JobData,
|
||||||
|
viaJobDialog: boolean = false
|
||||||
|
) {
|
||||||
this.ownerUri = ownerUri;
|
this.ownerUri = ownerUri;
|
||||||
|
this.viaJobDialog = viaJobDialog;
|
||||||
|
this.jobModel = jobModel;
|
||||||
|
this.jobName = this.jobName ? this.jobName : this.jobModel.name;
|
||||||
|
|
||||||
if (alertInfo) {
|
if (alertInfo) {
|
||||||
this.dialogMode = AgentDialogMode.EDIT;
|
this.dialogMode = AgentDialogMode.EDIT;
|
||||||
@@ -57,10 +69,9 @@ export class AlertData implements IAgentDialogData {
|
|||||||
this.eventDescriptionKeyword = alertInfo.eventDescriptionKeyword;
|
this.eventDescriptionKeyword = alertInfo.eventDescriptionKeyword;
|
||||||
this.eventSource = alertInfo.eventSource;
|
this.eventSource = alertInfo.eventSource;
|
||||||
this.hasNotification = alertInfo.hasNotification;
|
this.hasNotification = alertInfo.hasNotification;
|
||||||
this.includeEventDescription = alertInfo.includeEventDescription.toString();
|
this.includeEventDescription = alertInfo.includeEventDescription ? alertInfo.includeEventDescription.toString() : null;
|
||||||
this.isEnabled = alertInfo.isEnabled;
|
this.isEnabled = alertInfo.isEnabled;
|
||||||
this.jobId = alertInfo.jobId;
|
this.jobId = alertInfo.jobId;
|
||||||
this.jobName = alertInfo.jobName;
|
|
||||||
this.lastOccurrenceDate = alertInfo.lastOccurrenceDate;
|
this.lastOccurrenceDate = alertInfo.lastOccurrenceDate;
|
||||||
this.lastResponseDate = alertInfo.lastResponseDate;
|
this.lastResponseDate = alertInfo.lastResponseDate;
|
||||||
this.messageId = alertInfo.messageId;
|
this.messageId = alertInfo.messageId;
|
||||||
@@ -71,7 +82,7 @@ export class AlertData implements IAgentDialogData {
|
|||||||
this.databaseName = alertInfo.databaseName;
|
this.databaseName = alertInfo.databaseName;
|
||||||
this.countResetDate = alertInfo.countResetDate;
|
this.countResetDate = alertInfo.countResetDate;
|
||||||
this.categoryName = alertInfo.categoryName;
|
this.categoryName = alertInfo.categoryName;
|
||||||
this.alertType = alertInfo.alertType.toString();
|
this.alertType = alertInfo.alertType ? alertInfo.alertType.toString() : null;
|
||||||
this.wmiEventNamespace = alertInfo.wmiEventNamespace;
|
this.wmiEventNamespace = alertInfo.wmiEventNamespace;
|
||||||
this.wmiEventQuery = alertInfo.wmiEventQuery;
|
this.wmiEventQuery = alertInfo.wmiEventQuery;
|
||||||
}
|
}
|
||||||
@@ -82,10 +93,18 @@ export class AlertData implements IAgentDialogData {
|
|||||||
|
|
||||||
public async save() {
|
public async save() {
|
||||||
let agentService = await AgentUtils.getAgentService();
|
let agentService = await AgentUtils.getAgentService();
|
||||||
let result = this.dialogMode === AgentDialogMode.CREATE
|
let result: any;
|
||||||
? await agentService.createAlert(this.ownerUri, this.toAgentAlertInfo())
|
// if it's called via the job dialog, add it to the
|
||||||
: await agentService.updateAlert(this.ownerUri, this.originalName, this.toAgentAlertInfo());
|
// job model
|
||||||
|
if (this.viaJobDialog) {
|
||||||
|
if (this.jobModel) {
|
||||||
|
Promise.resolve(this);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// has to be a create alert
|
||||||
|
result = await agentService.createAlert(this.ownerUri, this.toAgentAlertInfo());
|
||||||
|
}
|
||||||
if (!result || !result.success) {
|
if (!result || !result.success) {
|
||||||
vscode.window.showErrorMessage(
|
vscode.window.showErrorMessage(
|
||||||
localize('alertData.saveErrorMessage', "Alert update failed '{0}'", result.errorMessage ? result.errorMessage : 'Unknown'));
|
localize('alertData.saveErrorMessage', "Alert update failed '{0}'", result.errorMessage ? result.errorMessage : 'Unknown'));
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ export class JobData implements IAgentDialogData {
|
|||||||
private _operators: string[];
|
private _operators: string[];
|
||||||
private _defaultOwner: string;
|
private _defaultOwner: string;
|
||||||
private _jobCompletionActionConditions: sqlops.CategoryValue[];
|
private _jobCompletionActionConditions: sqlops.CategoryValue[];
|
||||||
|
private _jobCategoryIdsMap: sqlops.AgentJobCategory[];
|
||||||
|
|
||||||
public dialogMode: AgentDialogMode = AgentDialogMode.CREATE;
|
public dialogMode: AgentDialogMode = AgentDialogMode.CREATE;
|
||||||
public name: string;
|
public name: string;
|
||||||
@@ -44,6 +45,9 @@ export class JobData implements IAgentDialogData {
|
|||||||
public jobSteps: sqlops.AgentJobStepInfo[];
|
public jobSteps: sqlops.AgentJobStepInfo[];
|
||||||
public jobSchedules: sqlops.AgentJobScheduleInfo[];
|
public jobSchedules: sqlops.AgentJobScheduleInfo[];
|
||||||
public alerts: sqlops.AgentAlertInfo[];
|
public alerts: sqlops.AgentAlertInfo[];
|
||||||
|
public jobId: string;
|
||||||
|
public startStepId: number;
|
||||||
|
public categoryType: number;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
ownerUri: string,
|
ownerUri: string,
|
||||||
@@ -59,9 +63,13 @@ export class JobData implements IAgentDialogData {
|
|||||||
this.category = jobInfo.category;
|
this.category = jobInfo.category;
|
||||||
this.description = jobInfo.description;
|
this.description = jobInfo.description;
|
||||||
this.enabled = jobInfo.enabled;
|
this.enabled = jobInfo.enabled;
|
||||||
this.jobSteps = jobInfo.JobSteps;
|
this.jobSteps = jobInfo.jobSteps;
|
||||||
this.jobSchedules = jobInfo.JobSchedules;
|
this.jobSchedules = jobInfo.jobSchedules;
|
||||||
this.alerts = jobInfo.Alerts;
|
this.alerts = jobInfo.alerts;
|
||||||
|
this.jobId = jobInfo.jobId;
|
||||||
|
this.startStepId = jobInfo.startStepId;
|
||||||
|
this.categoryId = jobInfo.categoryId;
|
||||||
|
this.categoryType = jobInfo.categoryType;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,6 +77,10 @@ export class JobData implements IAgentDialogData {
|
|||||||
return this._jobCategories;
|
return this._jobCategories;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get jobCategoryIdsMap(): sqlops.AgentJobCategory[] {
|
||||||
|
return this._jobCategoryIdsMap;
|
||||||
|
}
|
||||||
|
|
||||||
public get operators(): string[] {
|
public get operators(): string[] {
|
||||||
return this._operators;
|
return this._operators;
|
||||||
}
|
}
|
||||||
@@ -92,7 +104,7 @@ export class JobData implements IAgentDialogData {
|
|||||||
this._jobCategories = jobDefaults.categories.map((cat) => {
|
this._jobCategories = jobDefaults.categories.map((cat) => {
|
||||||
return cat.name;
|
return cat.name;
|
||||||
});
|
});
|
||||||
|
this._jobCategoryIdsMap = jobDefaults.categories;
|
||||||
this._defaultOwner = jobDefaults.owner;
|
this._defaultOwner = jobDefaults.owner;
|
||||||
|
|
||||||
this._operators = ['', this._defaultOwner];
|
this._operators = ['', this._defaultOwner];
|
||||||
@@ -115,7 +127,6 @@ export class JobData implements IAgentDialogData {
|
|||||||
let result = this.dialogMode === AgentDialogMode.CREATE
|
let result = this.dialogMode === AgentDialogMode.CREATE
|
||||||
? await this._agentService.createJob(this.ownerUri, jobInfo)
|
? await this._agentService.createJob(this.ownerUri, jobInfo)
|
||||||
: await this._agentService.updateJob(this.ownerUri, this.originalName, jobInfo);
|
: await this._agentService.updateJob(this.ownerUri, this.originalName, jobInfo);
|
||||||
|
|
||||||
if (!result || !result.success) {
|
if (!result || !result.success) {
|
||||||
vscode.window.showErrorMessage(
|
vscode.window.showErrorMessage(
|
||||||
localize('jobData.saveErrorMessage', "Job update failed '{0}'", result.errorMessage ? result.errorMessage : 'Unknown'));
|
localize('jobData.saveErrorMessage', "Job update failed '{0}'", result.errorMessage ? result.errorMessage : 'Unknown'));
|
||||||
@@ -135,34 +146,22 @@ export class JobData implements IAgentDialogData {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public addJobSchedule(schedule: sqlops.AgentJobScheduleInfo) {
|
|
||||||
if (this.jobSchedules) {
|
|
||||||
let existingSchedule = this.jobSchedules.find(item => item.name === schedule.name);
|
|
||||||
if (!existingSchedule) {
|
|
||||||
this.jobSchedules.push(schedule);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.jobSchedules = [];
|
|
||||||
this.jobSchedules.push(schedule);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public toAgentJobInfo(): sqlops.AgentJobInfo {
|
public toAgentJobInfo(): sqlops.AgentJobInfo {
|
||||||
return {
|
return {
|
||||||
name: this.name,
|
name: this.name,
|
||||||
owner: this.owner,
|
owner: this.owner,
|
||||||
description: this.description,
|
description: this.description,
|
||||||
EmailLevel: this.emailLevel,
|
emailLevel: this.emailLevel,
|
||||||
PageLevel: this.pageLevel,
|
pageLevel: this.pageLevel,
|
||||||
EventLogLevel: this.eventLogLevel,
|
eventLogLevel: this.eventLogLevel,
|
||||||
DeleteLevel: this.deleteLevel,
|
deleteLevel: this.deleteLevel,
|
||||||
OperatorToEmail: this.operatorToEmail,
|
operatorToEmail: this.operatorToEmail,
|
||||||
OperatorToPage: this.operatorToPage,
|
operatorToPage: this.operatorToPage,
|
||||||
enabled: this.enabled,
|
enabled: this.enabled,
|
||||||
category: this.category,
|
category: this.category,
|
||||||
Alerts: this.alerts,
|
alerts: this.alerts,
|
||||||
JobSchedules: this.jobSchedules,
|
jobSchedules: this.jobSchedules,
|
||||||
JobSteps: this.jobSteps,
|
jobSteps: this.jobSteps,
|
||||||
// The properties below are not collected from UI
|
// The properties below are not collected from UI
|
||||||
// We could consider using a seperate class for create job request
|
// We could consider using a seperate class for create job request
|
||||||
//
|
//
|
||||||
@@ -173,11 +172,12 @@ export class JobData implements IAgentDialogData {
|
|||||||
hasSchedule: false,
|
hasSchedule: false,
|
||||||
hasStep: false,
|
hasStep: false,
|
||||||
runnable: true,
|
runnable: true,
|
||||||
categoryId: 0,
|
categoryId: this.categoryId,
|
||||||
categoryType: 1, // LocalJob, hard-coding the value, corresponds to the target tab in SSMS
|
categoryType: this.categoryType,
|
||||||
lastRun: '',
|
lastRun: '',
|
||||||
nextRun: '',
|
nextRun: '',
|
||||||
jobId: ''
|
jobId: this.jobId,
|
||||||
|
startStepId: this.startStepId
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -46,11 +46,13 @@ export class JobStepData implements IAgentDialogData {
|
|||||||
public retryInterval: number;
|
public retryInterval: number;
|
||||||
public proxyName: string;
|
public proxyName: string;
|
||||||
private jobModel: JobData;
|
private jobModel: JobData;
|
||||||
|
private viaJobDialog: boolean;
|
||||||
|
|
||||||
constructor(ownerUri:string, jobModel?: JobData) {
|
constructor(ownerUri:string, jobModel?: JobData, viaJobDialog: boolean = false) {
|
||||||
this.ownerUri = ownerUri;
|
this.ownerUri = ownerUri;
|
||||||
this.jobName = jobModel.name;
|
this.jobName = jobModel.name;
|
||||||
this.jobModel = jobModel;
|
this.jobModel = jobModel;
|
||||||
|
this.viaJobDialog = viaJobDialog;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async initialize() {
|
public async initialize() {
|
||||||
@@ -59,18 +61,16 @@ export class JobStepData implements IAgentDialogData {
|
|||||||
public async save() {
|
public async save() {
|
||||||
let agentService = await AgentUtils.getAgentService();
|
let agentService = await AgentUtils.getAgentService();
|
||||||
let result: any;
|
let result: any;
|
||||||
if (this.dialogMode === AgentDialogMode.CREATE) {
|
// if it's called via the job dialog, add it to the
|
||||||
if (this.jobModel && this.jobModel.dialogMode === AgentDialogMode.CREATE) {
|
// job model
|
||||||
// create job -> create step
|
if (this.viaJobDialog) {
|
||||||
|
if (this.jobModel) {
|
||||||
Promise.resolve(this);
|
Promise.resolve(this);
|
||||||
return;
|
return;
|
||||||
} else {
|
|
||||||
// edit job -> create step
|
|
||||||
result = await agentService.createJobStep(this.ownerUri, JobStepData.convertToAgentJobStepInfo(this));
|
|
||||||
}
|
}
|
||||||
} else if (this.jobModel && this.jobModel.dialogMode === AgentDialogMode.EDIT) {
|
} else {
|
||||||
// edit job -> edit step
|
// has to be a create step
|
||||||
result = await agentService.updateJobStep(this.ownerUri, this.stepName, JobStepData.convertToAgentJobStepInfo(this));
|
result = await agentService.createJobStep(this.ownerUri, JobStepData.convertToAgentJobStepInfo(this));
|
||||||
}
|
}
|
||||||
if (!result || !result.success) {
|
if (!result || !result.success) {
|
||||||
vscode.window.showErrorMessage(
|
vscode.window.showErrorMessage(
|
||||||
@@ -123,6 +123,7 @@ export class JobStepData implements IAgentDialogData {
|
|||||||
stepData.retryInterval = jobStepInfo.retryInterval,
|
stepData.retryInterval = jobStepInfo.retryInterval,
|
||||||
stepData.proxyName = jobStepInfo.proxyName;
|
stepData.proxyName = jobStepInfo.proxyName;
|
||||||
stepData.dialogMode = AgentDialogMode.EDIT;
|
stepData.dialogMode = AgentDialogMode.EDIT;
|
||||||
|
stepData.viaJobDialog = true;
|
||||||
return stepData;
|
return stepData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,8 +29,6 @@ export class PickScheduleData implements IAgentDialogData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async save() {
|
public async save() {
|
||||||
let agentService = await AgentUtils.getAgentService();
|
|
||||||
this.selectedSchedule.jobName = this.jobName;
|
this.selectedSchedule.jobName = this.jobName;
|
||||||
let result = await agentService.createJobSchedule(this.ownerUri, this.selectedSchedule);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,9 @@ export abstract class AgentDialog<T extends IAgentDialogData> {
|
|||||||
public readonly onSuccess: vscode.Event<T> = this._onSuccess.event;
|
public readonly onSuccess: vscode.Event<T> = this._onSuccess.event;
|
||||||
public dialog: sqlops.window.modelviewdialog.Dialog;
|
public dialog: sqlops.window.modelviewdialog.Dialog;
|
||||||
|
|
||||||
|
// Dialog Name for Telemetry
|
||||||
|
public dialogName: string;
|
||||||
|
|
||||||
constructor(public ownerUri: string, public model: T, public title: string) {
|
constructor(public ownerUri: string, public model: T, public title: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,8 +34,9 @@ export abstract class AgentDialog<T extends IAgentDialogData> {
|
|||||||
|
|
||||||
protected abstract async initializeDialog(dialog: sqlops.window.modelviewdialog.Dialog);
|
protected abstract async initializeDialog(dialog: sqlops.window.modelviewdialog.Dialog);
|
||||||
|
|
||||||
public async openDialog() {
|
public async openDialog(dialogName?: string) {
|
||||||
this.dialog = sqlops.window.modelviewdialog.createDialog(this.title);
|
let event = dialogName ? dialogName : null;
|
||||||
|
this.dialog = sqlops.window.modelviewdialog.createDialog(this.title, event);
|
||||||
|
|
||||||
await this.model.initialize();
|
await this.model.initialize();
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { AgentUtils } from '../agentUtils';
|
|||||||
import { AlertData } from '../data/alertData';
|
import { AlertData } from '../data/alertData';
|
||||||
import { OperatorDialog } from './operatorDialog';
|
import { OperatorDialog } from './operatorDialog';
|
||||||
import { JobDialog } from './jobDialog';
|
import { JobDialog } from './jobDialog';
|
||||||
|
import { JobData } from '../data/jobData';
|
||||||
|
|
||||||
const localize = nls.loadMessageBundle();
|
const localize = nls.loadMessageBundle();
|
||||||
|
|
||||||
@@ -115,6 +116,10 @@ export class AlertDialog extends AgentDialog<AlertData> {
|
|||||||
private static readonly DelayMinutesTextBoxLabel: string = localize('alertDialog.DelayMinutes', 'Delay Minutes');
|
private static readonly DelayMinutesTextBoxLabel: string = localize('alertDialog.DelayMinutes', 'Delay Minutes');
|
||||||
private static readonly DelaySecondsTextBoxLabel: string = localize('alertDialog.DelaySeconds', 'Delay Seconds');
|
private static readonly DelaySecondsTextBoxLabel: string = localize('alertDialog.DelaySeconds', 'Delay Seconds');
|
||||||
|
|
||||||
|
// Event Name strings
|
||||||
|
private readonly NewAlertDialog = 'NewAlertDialogOpen';
|
||||||
|
private readonly EditAlertDialog = 'EditAlertDialogOpened';
|
||||||
|
|
||||||
// UI Components
|
// UI Components
|
||||||
private generalTab: sqlops.window.modelviewdialog.DialogTab;
|
private generalTab: sqlops.window.modelviewdialog.DialogTab;
|
||||||
private responseTab: sqlops.window.modelviewdialog.DialogTab;
|
private responseTab: sqlops.window.modelviewdialog.DialogTab;
|
||||||
@@ -148,14 +153,26 @@ export class AlertDialog extends AgentDialog<AlertData> {
|
|||||||
private delayMinutesTextBox: sqlops.InputBoxComponent;
|
private delayMinutesTextBox: sqlops.InputBoxComponent;
|
||||||
private delaySecondsTextBox: sqlops.InputBoxComponent;
|
private delaySecondsTextBox: sqlops.InputBoxComponent;
|
||||||
|
|
||||||
private jobs: string[];
|
private isEdit: boolean = false;
|
||||||
private databases: string[];
|
private databases: string[];
|
||||||
|
private jobModel: JobData;
|
||||||
|
public jobId: string;
|
||||||
|
public jobName: string;
|
||||||
|
|
||||||
constructor(ownerUri: string, alertInfo: sqlops.AgentAlertInfo = undefined, jobs: string[]) {
|
constructor(
|
||||||
|
ownerUri: string,
|
||||||
|
jobModel: JobData,
|
||||||
|
alertInfo: sqlops.AgentAlertInfo = undefined,
|
||||||
|
viaJobDialog: boolean = false
|
||||||
|
) {
|
||||||
super(ownerUri,
|
super(ownerUri,
|
||||||
new AlertData(ownerUri, alertInfo),
|
new AlertData(ownerUri, alertInfo, jobModel, viaJobDialog),
|
||||||
alertInfo ? AlertDialog.EditDialogTitle : AlertDialog.CreateDialogTitle);
|
alertInfo ? AlertDialog.EditDialogTitle : AlertDialog.CreateDialogTitle);
|
||||||
this.jobs = jobs;
|
this.jobModel = jobModel;
|
||||||
|
this.jobId = this.jobId ? this.jobId : this.jobModel.jobId;
|
||||||
|
this.jobName = this.jobName ? this.jobName : this.jobModel.name;
|
||||||
|
this.isEdit = alertInfo ? true : false;
|
||||||
|
this.dialogName = this.isEdit ? this.EditAlertDialog : this.NewAlertDialog;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async initializeDialog(dialog: sqlops.window.modelviewdialog.Dialog) {
|
protected async initializeDialog(dialog: sqlops.window.modelviewdialog.Dialog) {
|
||||||
@@ -512,7 +529,8 @@ export class AlertDialog extends AgentDialog<AlertData> {
|
|||||||
protected updateModel() {
|
protected updateModel() {
|
||||||
this.model.name = this.nameTextBox.value;
|
this.model.name = this.nameTextBox.value;
|
||||||
this.model.isEnabled = this.enabledCheckBox.checked;
|
this.model.isEnabled = this.enabledCheckBox.checked;
|
||||||
|
this.model.jobId = this.jobId;
|
||||||
|
this.model.jobName = this.jobName;
|
||||||
this.model.alertType = this.getDropdownValue(this.typeDropDown);
|
this.model.alertType = this.getDropdownValue(this.typeDropDown);
|
||||||
let databaseName = this.getDropdownValue(this.databaseDropDown);
|
let databaseName = this.getDropdownValue(this.databaseDropDown);
|
||||||
this.model.databaseName = (databaseName !== AlertDialog.AllDatabases) ? databaseName : undefined;
|
this.model.databaseName = (databaseName !== AlertDialog.AllDatabases) ? databaseName : undefined;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { PickScheduleDialog } from './pickScheduleDialog';
|
|||||||
import { AlertDialog } from './alertDialog';
|
import { AlertDialog } from './alertDialog';
|
||||||
import { AgentDialog } from './agentDialog';
|
import { AgentDialog } from './agentDialog';
|
||||||
import { AgentUtils } from '../agentUtils';
|
import { AgentUtils } from '../agentUtils';
|
||||||
|
import { JobStepData } from '../data/jobStepData';
|
||||||
|
|
||||||
const localize = nls.loadMessageBundle();
|
const localize = nls.loadMessageBundle();
|
||||||
|
|
||||||
@@ -41,11 +42,12 @@ export class JobDialog extends AgentDialog<JobData> {
|
|||||||
private readonly StepsTable_TypeColumnString: string = localize('jobDialog.type', 'Type');
|
private readonly StepsTable_TypeColumnString: string = localize('jobDialog.type', 'Type');
|
||||||
private readonly StepsTable_SuccessColumnString: string = localize('jobDialog.onSuccess', 'On Success');
|
private readonly StepsTable_SuccessColumnString: string = localize('jobDialog.onSuccess', 'On Success');
|
||||||
private readonly StepsTable_FailureColumnString: string = localize('jobDialog.onFailure', 'On Failure');
|
private readonly StepsTable_FailureColumnString: string = localize('jobDialog.onFailure', 'On Failure');
|
||||||
private readonly NewStepButtonString: string = localize('jobDialog.new', 'New...');
|
private readonly NewStepButtonString: string = localize('jobDialog.new', 'New Step');
|
||||||
private readonly EditStepButtonString: string = localize('jobDialog.edit', 'Edit');
|
private readonly EditStepButtonString: string = localize('jobDialog.edit', 'Edit Step');
|
||||||
private readonly DeleteStepButtonString: string = localize('jobDialog.delete', 'Delete');
|
private readonly DeleteStepButtonString: string = localize('jobDialog.delete', 'Delete Step');
|
||||||
private readonly MoveStepUpButtonString: string = localize('jobDialog.moveUp', 'Move Step Up');
|
private readonly MoveStepUpButtonString: string = localize('jobDialog.moveUp', 'Move Step Up');
|
||||||
private readonly MoveStepDownButtonString: string = localize('jobDialog.moveDown', 'Move Step Up');
|
private readonly MoveStepDownButtonString: string = localize('jobDialog.moveDown', 'Move Step Down');
|
||||||
|
private readonly StartStepDropdownString: string = localize('jobDialog.startStepAt', 'Start step');
|
||||||
|
|
||||||
// Notifications tab strings
|
// Notifications tab strings
|
||||||
private readonly NotificationsTabTopLabelString: string = localize('jobDialog.notificationsTabTop', 'Actions to perform when the job completes');
|
private readonly NotificationsTabTopLabelString: string = localize('jobDialog.notificationsTabTop', 'Actions to perform when the job completes');
|
||||||
@@ -66,6 +68,10 @@ export class JobDialog extends AgentDialog<JobData> {
|
|||||||
private readonly AlertEnabledLabelString: string = localize('jobDialog.alertEnabledLabel', 'Enabled');
|
private readonly AlertEnabledLabelString: string = localize('jobDialog.alertEnabledLabel', 'Enabled');
|
||||||
private readonly AlertTypeLabelString: string = localize('jobDialog.alertTypeLabel', 'Type');
|
private readonly AlertTypeLabelString: string = localize('jobDialog.alertTypeLabel', 'Type');
|
||||||
|
|
||||||
|
// Event Name strings
|
||||||
|
private readonly NewJobDialogEvent: string = 'NewJobDialogOpened';
|
||||||
|
private readonly EditJobDialogEvent: string = 'EditJobDialogOpened';
|
||||||
|
|
||||||
// UI Components
|
// UI Components
|
||||||
private generalTab: sqlops.window.modelviewdialog.DialogTab;
|
private generalTab: sqlops.window.modelviewdialog.DialogTab;
|
||||||
private stepsTab: sqlops.window.modelviewdialog.DialogTab;
|
private stepsTab: sqlops.window.modelviewdialog.DialogTab;
|
||||||
@@ -88,6 +94,9 @@ export class JobDialog extends AgentDialog<JobData> {
|
|||||||
private editStepButton: sqlops.ButtonComponent;
|
private editStepButton: sqlops.ButtonComponent;
|
||||||
private deleteStepButton: sqlops.ButtonComponent;
|
private deleteStepButton: sqlops.ButtonComponent;
|
||||||
|
|
||||||
|
// Schedule tab controls
|
||||||
|
private removeScheduleButton: sqlops.ButtonComponent;
|
||||||
|
|
||||||
// Notifications tab controls
|
// Notifications tab controls
|
||||||
private notificationsTabTopLabel: sqlops.TextComponent;
|
private notificationsTabTopLabel: sqlops.TextComponent;
|
||||||
private emailCheckBox: sqlops.CheckBoxComponent;
|
private emailCheckBox: sqlops.CheckBoxComponent;
|
||||||
@@ -100,6 +109,7 @@ export class JobDialog extends AgentDialog<JobData> {
|
|||||||
private eventLogConditionDropdown: sqlops.DropDownComponent;
|
private eventLogConditionDropdown: sqlops.DropDownComponent;
|
||||||
private deleteJobCheckBox: sqlops.CheckBoxComponent;
|
private deleteJobCheckBox: sqlops.CheckBoxComponent;
|
||||||
private deleteJobConditionDropdown: sqlops.DropDownComponent;
|
private deleteJobConditionDropdown: sqlops.DropDownComponent;
|
||||||
|
private startStepDropdown: sqlops.DropDownComponent;
|
||||||
|
|
||||||
// Schedule tab controls
|
// Schedule tab controls
|
||||||
private schedulesTable: sqlops.TableComponent;
|
private schedulesTable: sqlops.TableComponent;
|
||||||
@@ -110,12 +120,22 @@ export class JobDialog extends AgentDialog<JobData> {
|
|||||||
private newAlertButton: sqlops.ButtonComponent;
|
private newAlertButton: sqlops.ButtonComponent;
|
||||||
private isEdit: boolean = false;
|
private isEdit: boolean = false;
|
||||||
|
|
||||||
|
// Job objects
|
||||||
|
private steps: sqlops.AgentJobStepInfo[];
|
||||||
|
private schedules: sqlops.AgentJobScheduleInfo[];
|
||||||
|
private alerts: sqlops.AgentAlertInfo[] = [];
|
||||||
|
private startStepDropdownValues: sqlops.CategoryValue[] = [];
|
||||||
|
|
||||||
constructor(ownerUri: string, jobInfo: sqlops.AgentJobInfo = undefined) {
|
constructor(ownerUri: string, jobInfo: sqlops.AgentJobInfo = undefined) {
|
||||||
super(
|
super(
|
||||||
ownerUri,
|
ownerUri,
|
||||||
new JobData(ownerUri, jobInfo),
|
new JobData(ownerUri, jobInfo),
|
||||||
jobInfo ? JobDialog.EditDialogTitle : JobDialog.CreateDialogTitle);
|
jobInfo ? JobDialog.EditDialogTitle : JobDialog.CreateDialogTitle);
|
||||||
|
this.steps = this.model.jobSteps ? this.model.jobSteps : [];
|
||||||
|
this.schedules = this.model.jobSchedules ? this.model.jobSchedules : [];
|
||||||
|
this.alerts = this.model.alerts ? this.model.alerts : [];
|
||||||
this.isEdit = jobInfo ? true : false;
|
this.isEdit = jobInfo ? true : false;
|
||||||
|
this.dialogName = this.isEdit ? this.EditJobDialogEvent : this.NewJobDialogEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async initializeDialog() {
|
protected async initializeDialog() {
|
||||||
@@ -198,12 +218,7 @@ export class JobDialog extends AgentDialog<JobData> {
|
|||||||
|
|
||||||
private initializeStepsTab() {
|
private initializeStepsTab() {
|
||||||
this.stepsTab.registerContent(async view => {
|
this.stepsTab.registerContent(async view => {
|
||||||
let previewTag = view.modelBuilder.text()
|
let data = this.steps ? this.convertStepsToData(this.steps) : [];
|
||||||
.withProperties({
|
|
||||||
value: 'Feature Preview'
|
|
||||||
}).component();
|
|
||||||
let steps = this.model.jobSteps ? this.model.jobSteps : [];
|
|
||||||
let data = this.convertStepsToData(steps);
|
|
||||||
this.stepsTable = view.modelBuilder.table()
|
this.stepsTable = view.modelBuilder.table()
|
||||||
.withProperties({
|
.withProperties({
|
||||||
columns: [
|
columns: [
|
||||||
@@ -214,19 +229,26 @@ export class JobDialog extends AgentDialog<JobData> {
|
|||||||
this.StepsTable_FailureColumnString
|
this.StepsTable_FailureColumnString
|
||||||
],
|
],
|
||||||
data: data,
|
data: data,
|
||||||
height: 750
|
height: 650
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
|
this.startStepDropdown = view.modelBuilder.dropDown().withProperties({ width: 180 }).component();
|
||||||
|
this.startStepDropdown.enabled = this.steps.length > 1 ? true : false;
|
||||||
|
this.steps.forEach((step) => {
|
||||||
|
this.startStepDropdownValues.push({ displayName: step.id + ': ' + step.stepName, name: step.id.toString() });
|
||||||
|
});
|
||||||
|
this.startStepDropdown.values = this.startStepDropdownValues;
|
||||||
|
|
||||||
this.moveStepUpButton = view.modelBuilder.button()
|
this.moveStepUpButton = view.modelBuilder.button()
|
||||||
.withProperties({
|
.withProperties({
|
||||||
label: this.MoveStepUpButtonString,
|
label: this.MoveStepUpButtonString,
|
||||||
width: 80
|
width: 120
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
this.moveStepDownButton = view.modelBuilder.button()
|
this.moveStepDownButton = view.modelBuilder.button()
|
||||||
.withProperties({
|
.withProperties({
|
||||||
label: this.MoveStepDownButtonString,
|
label: this.MoveStepDownButtonString,
|
||||||
width: 80
|
width: 120
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
this.moveStepUpButton.enabled = false;
|
this.moveStepUpButton.enabled = false;
|
||||||
@@ -234,16 +256,19 @@ export class JobDialog extends AgentDialog<JobData> {
|
|||||||
|
|
||||||
this.newStepButton = view.modelBuilder.button().withProperties({
|
this.newStepButton = view.modelBuilder.button().withProperties({
|
||||||
label: this.NewStepButtonString,
|
label: this.NewStepButtonString,
|
||||||
width: 80
|
width: 140
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
let stepDialog = new JobStepDialog(this.model.ownerUri, '' , this.model);
|
let stepDialog = new JobStepDialog(this.model.ownerUri, '' , this.model, null, true);
|
||||||
stepDialog.onSuccess((step) => {
|
stepDialog.onSuccess((step) => {
|
||||||
if (!this.model.jobSteps) {
|
let stepInfo = JobStepData.convertToAgentJobStepInfo(step);
|
||||||
this.model.jobSteps = [];
|
this.steps.push(stepInfo);
|
||||||
}
|
this.stepsTable.data = this.convertStepsToData(this.steps);
|
||||||
this.model.jobSteps.push(step);
|
this.startStepDropdownValues = [];
|
||||||
this.stepsTable.data = this.convertStepsToData(this.model.jobSteps);
|
this.steps.forEach((step) => {
|
||||||
|
this.startStepDropdownValues.push({ displayName: step.id + ': ' + step.stepName, name: step.id.toString() });
|
||||||
|
});
|
||||||
|
this.startStepDropdown.values = this.startStepDropdownValues;
|
||||||
});
|
});
|
||||||
this.newStepButton.onDidClick((e)=>{
|
this.newStepButton.onDidClick((e)=>{
|
||||||
if (this.nameTextBox.value && this.nameTextBox.value.length > 0) {
|
if (this.nameTextBox.value && this.nameTextBox.value.length > 0) {
|
||||||
@@ -256,67 +281,144 @@ export class JobDialog extends AgentDialog<JobData> {
|
|||||||
|
|
||||||
this.editStepButton = view.modelBuilder.button().withProperties({
|
this.editStepButton = view.modelBuilder.button().withProperties({
|
||||||
label: this.EditStepButtonString,
|
label: this.EditStepButtonString,
|
||||||
width: 80
|
width: 140
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
this.deleteStepButton = view.modelBuilder.button().withProperties({
|
this.deleteStepButton = view.modelBuilder.button().withProperties({
|
||||||
label: this.DeleteStepButtonString,
|
label: this.DeleteStepButtonString,
|
||||||
width: 80
|
width: 140
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
this.stepsTable.enabled = false;
|
this.stepsTable.enabled = false;
|
||||||
this.editStepButton.enabled = false;
|
this.editStepButton.enabled = false;
|
||||||
this.deleteStepButton.enabled = false;
|
this.deleteStepButton.enabled = false;
|
||||||
|
|
||||||
this.stepsTable.onRowSelected(() => {
|
this.moveStepUpButton.onDidClick(() => {
|
||||||
|
let rowNumber = this.stepsTable.selectedRows[0];
|
||||||
|
let previousRow = rowNumber - 1;
|
||||||
|
let previousStep = this.steps[previousRow];
|
||||||
|
let previousStepId = this.steps[previousRow].id;
|
||||||
|
let currentStep = this.steps[rowNumber];
|
||||||
|
let currentStepId = this.steps[rowNumber].id;
|
||||||
|
this.steps[previousRow] = currentStep;
|
||||||
|
this.steps[rowNumber] = previousStep;
|
||||||
|
this.stepsTable.data = this.convertStepsToData(this.steps);
|
||||||
|
this.steps[previousRow].id = previousStepId;
|
||||||
|
this.steps[rowNumber].id = currentStepId;
|
||||||
|
this.stepsTable.selectedRows = [previousRow];
|
||||||
|
});
|
||||||
|
|
||||||
|
this.moveStepDownButton.onDidClick(() => {
|
||||||
|
let rowNumber = this.stepsTable.selectedRows[0];
|
||||||
|
let nextRow = rowNumber + 1;
|
||||||
|
let nextStep = this.steps[nextRow];
|
||||||
|
let nextStepId = this.steps[nextRow].id;
|
||||||
|
let currentStep = this.steps[rowNumber];
|
||||||
|
let currentStepId = this.steps[rowNumber].id;
|
||||||
|
this.steps[nextRow] = currentStep;
|
||||||
|
this.steps[rowNumber] = nextStep;
|
||||||
|
this.stepsTable.data = this.convertStepsToData(this.steps);
|
||||||
|
this.steps[nextRow].id = nextStepId;
|
||||||
|
this.steps[rowNumber].id = currentStepId;
|
||||||
|
this.stepsTable.selectedRows = [nextRow];
|
||||||
|
});
|
||||||
|
|
||||||
|
this.editStepButton.onDidClick(() => {
|
||||||
|
if (this.stepsTable.selectedRows.length === 1) {
|
||||||
|
let rowNumber = this.stepsTable.selectedRows[0];
|
||||||
|
let stepData = this.model.jobSteps[rowNumber];
|
||||||
|
let editStepDialog = new JobStepDialog(this.model.ownerUri, '' , this.model, stepData, true);
|
||||||
|
editStepDialog.onSuccess((step) => {
|
||||||
|
let stepInfo = JobStepData.convertToAgentJobStepInfo(step);
|
||||||
|
for (let i = 0; i < this.steps.length; i++) {
|
||||||
|
if (this.steps[i].id === stepInfo.id) {
|
||||||
|
this.steps[i] = stepInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.stepsTable.data = this.convertStepsToData(this.steps);
|
||||||
|
this.startStepDropdownValues = [];
|
||||||
|
this.steps.forEach((step) => {
|
||||||
|
this.startStepDropdownValues.push({ displayName: step.id + ': ' + step.stepName, name: step.id.toString() });
|
||||||
|
});
|
||||||
|
this.startStepDropdown.values = this.startStepDropdownValues;
|
||||||
|
|
||||||
|
});
|
||||||
|
editStepDialog.openDialog();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.deleteStepButton.onDidClick(() => {
|
||||||
|
if (this.stepsTable.selectedRows.length === 1) {
|
||||||
|
let rowNumber = this.stepsTable.selectedRows[0];
|
||||||
|
AgentUtils.getAgentService().then((agentService) => {
|
||||||
|
let stepData = this.steps[rowNumber];
|
||||||
|
if (stepData.jobId) {
|
||||||
|
agentService.deleteJobStep(this.ownerUri, stepData).then((result) => {
|
||||||
|
if (result && result.success) {
|
||||||
|
this.steps.splice(rowNumber, 1);
|
||||||
|
let data = this.convertStepsToData(this.steps);
|
||||||
|
this.stepsTable.data = data;
|
||||||
|
this.startStepDropdownValues = [];
|
||||||
|
this.steps.forEach((step) => {
|
||||||
|
this.startStepDropdownValues.push({ displayName: step.id + ': ' + step.stepName, name: step.id.toString() });
|
||||||
|
});
|
||||||
|
this.startStepDropdown.values = this.startStepDropdownValues;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.steps.splice(rowNumber, 1);
|
||||||
|
let data = this.convertStepsToData(this.steps);
|
||||||
|
this.stepsTable.data = data;
|
||||||
|
this.startStepDropdownValues = [];
|
||||||
|
this.steps.forEach((step) => {
|
||||||
|
this.startStepDropdownValues.push({ displayName: step.id + ': ' + step.stepName, name: step.id.toString() });
|
||||||
|
});
|
||||||
|
this.startStepDropdown.values = this.startStepDropdownValues;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.stepsTable.onRowSelected((row) => {
|
||||||
// only let edit or delete steps if there's
|
// only let edit or delete steps if there's
|
||||||
// one step selection
|
// one step selection
|
||||||
if (this.stepsTable.selectedRows.length === 1) {
|
if (this.stepsTable.selectedRows.length === 1) {
|
||||||
let rowNumber = this.stepsTable.selectedRows[0];
|
let rowNumber = this.stepsTable.selectedRows[0];
|
||||||
let stepData = this.model.jobSteps[rowNumber];
|
// if it's not the last step
|
||||||
|
if (this.steps.length !== rowNumber + 1) {
|
||||||
|
this.moveStepDownButton.enabled = true;
|
||||||
|
}
|
||||||
|
// if it's not the first step
|
||||||
|
if (rowNumber !== 0) {
|
||||||
|
this.moveStepUpButton.enabled = true;
|
||||||
|
}
|
||||||
this.deleteStepButton.enabled = true;
|
this.deleteStepButton.enabled = true;
|
||||||
this.editStepButton.enabled = true;
|
this.editStepButton.enabled = true;
|
||||||
this.editStepButton.onDidClick(() => {
|
|
||||||
let stepDialog = new JobStepDialog(this.model.ownerUri, '' , this.model, stepData);
|
|
||||||
stepDialog.openDialog();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.deleteStepButton.onDidClick(() => {
|
|
||||||
AgentUtils.getAgentService().then((agentService) => {
|
|
||||||
let steps = this.model.jobSteps ? this.model.jobSteps : [];
|
|
||||||
agentService.deleteJobStep(this.ownerUri, stepData).then((result) => {
|
|
||||||
if (result && result.success) {
|
|
||||||
delete steps[rowNumber];
|
|
||||||
this.model.jobSteps = steps;
|
|
||||||
let data = this.convertStepsToData(steps);
|
|
||||||
this.stepsTable.data = data;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
let stepMoveContainer = this.createRowContainer(view).withItems([this.startStepDropdown, this.moveStepUpButton, this.moveStepDownButton]).component();
|
||||||
});
|
let stepsDialogContainer = this.createRowContainer(view).withItems([this.newStepButton, this.editStepButton, this.deleteStepButton]).component();
|
||||||
}
|
let formModel = view.modelBuilder.formContainer().withFormItems([
|
||||||
});
|
|
||||||
|
|
||||||
let formModel = view.modelBuilder.formContainer()
|
|
||||||
.withFormItems([{
|
|
||||||
component: previewTag,
|
|
||||||
title: ''
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
component: this.stepsTable,
|
component: this.stepsTable,
|
||||||
title: this.JobStepsTopLabelString,
|
title: this.JobStepsTopLabelString
|
||||||
actions: [this.moveStepUpButton, this.moveStepDownButton, this.newStepButton, this.editStepButton, this.deleteStepButton]
|
},
|
||||||
}]).withLayout({ width: '100%' }).component();
|
{
|
||||||
|
component: stepMoveContainer,
|
||||||
|
title: this.StartStepDropdownString
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: stepsDialogContainer,
|
||||||
|
title: ''
|
||||||
|
}
|
||||||
|
]).withLayout({ width: '100%' }).component();
|
||||||
await view.initializeModel(formModel);
|
await view.initializeModel(formModel);
|
||||||
|
this.setConditionDropdownSelectedValue(this.startStepDropdown, this.model.startStepId);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private initializeAlertsTab() {
|
private initializeAlertsTab() {
|
||||||
this.alertsTab.registerContent(async view => {
|
this.alertsTab.registerContent(async view => {
|
||||||
let previewTag = view.modelBuilder.text()
|
|
||||||
.withProperties({
|
|
||||||
value: 'Feature Preview'
|
|
||||||
}).component();
|
|
||||||
let alerts = this.model.alerts ? this.model.alerts : [];
|
let alerts = this.model.alerts ? this.model.alerts : [];
|
||||||
let data = this.convertAlertsToData(alerts);
|
let data = this.convertAlertsToData(alerts);
|
||||||
this.alertsTable = view.modelBuilder.table()
|
this.alertsTable = view.modelBuilder.table()
|
||||||
@@ -327,7 +429,7 @@ export class JobDialog extends AgentDialog<JobData> {
|
|||||||
this.AlertTypeLabelString
|
this.AlertTypeLabelString
|
||||||
],
|
],
|
||||||
data: data,
|
data: data,
|
||||||
height: 430,
|
height: 750,
|
||||||
width: 400
|
width: 400
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
@@ -336,18 +438,24 @@ export class JobDialog extends AgentDialog<JobData> {
|
|||||||
width: 80
|
width: 80
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
this.newAlertButton.onDidClick((e)=>{
|
let alertDialog = new AlertDialog(this.model.ownerUri, this.model, null, true);
|
||||||
let alertDialog = new AlertDialog(this.model.ownerUri, null, []);
|
alertDialog.onSuccess((alert) => {
|
||||||
alertDialog.onSuccess((dialogModel) => {
|
let alertInfo = alert.toAgentAlertInfo();
|
||||||
|
this.alerts.push(alertInfo);
|
||||||
|
this.alertsTable.data = this.convertAlertsToData(this.alerts);
|
||||||
});
|
});
|
||||||
|
this.newAlertButton.onDidClick(()=>{
|
||||||
|
if (this.nameTextBox.value && this.nameTextBox.value.length > 0) {
|
||||||
|
alertDialog.jobId = this.model.jobId;
|
||||||
|
alertDialog.jobName = this.model.name ? this.model.name : this.nameTextBox.value;
|
||||||
alertDialog.openDialog();
|
alertDialog.openDialog();
|
||||||
|
} else {
|
||||||
|
this.dialog.message = { text: this.BlankJobNameErrorText };
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let formModel = view.modelBuilder.formContainer()
|
let formModel = view.modelBuilder.formContainer()
|
||||||
.withFormItems([{
|
.withFormItems([{
|
||||||
component: previewTag,
|
|
||||||
title: ''
|
|
||||||
}, {
|
|
||||||
component: this.alertsTable,
|
component: this.alertsTable,
|
||||||
title: this.AlertsTopLabelString,
|
title: this.AlertsTopLabelString,
|
||||||
actions: [this.newAlertButton]
|
actions: [this.newAlertButton]
|
||||||
@@ -375,24 +483,42 @@ export class JobDialog extends AgentDialog<JobData> {
|
|||||||
label: this.PickScheduleButtonString,
|
label: this.PickScheduleButtonString,
|
||||||
width: 80
|
width: 80
|
||||||
}).component();
|
}).component();
|
||||||
this.pickScheduleButton.onDidClick((e)=>{
|
this.removeScheduleButton = view.modelBuilder.button().withProperties({
|
||||||
|
label: 'Remove schedule',
|
||||||
|
width: 100
|
||||||
|
}).component();
|
||||||
|
this.pickScheduleButton.onDidClick(()=>{
|
||||||
let pickScheduleDialog = new PickScheduleDialog(this.model.ownerUri, this.model.name);
|
let pickScheduleDialog = new PickScheduleDialog(this.model.ownerUri, this.model.name);
|
||||||
pickScheduleDialog.onSuccess((dialogModel) => {
|
pickScheduleDialog.onSuccess((dialogModel) => {
|
||||||
let selectedSchedule = dialogModel.selectedSchedule;
|
let selectedSchedule = dialogModel.selectedSchedule;
|
||||||
if (selectedSchedule) {
|
if (selectedSchedule) {
|
||||||
selectedSchedule.jobName = this.model.name;
|
let existingSchedule = this.schedules.find(item => item.name === selectedSchedule.name);
|
||||||
this.model.addJobSchedule(selectedSchedule);
|
if (!existingSchedule) {
|
||||||
|
selectedSchedule.jobName = this.model.name ? this.model.name : this.nameTextBox.value;
|
||||||
|
this.schedules.push(selectedSchedule);
|
||||||
|
}
|
||||||
this.populateScheduleTable();
|
this.populateScheduleTable();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
pickScheduleDialog.showDialog();
|
pickScheduleDialog.showDialog();
|
||||||
});
|
});
|
||||||
|
this.removeScheduleButton.onDidClick(() => {
|
||||||
|
if (this.schedulesTable.selectedRows.length === 1) {
|
||||||
|
let selectedRow = this.schedulesTable.selectedRows[0];
|
||||||
|
let selectedScheduleName = this.schedulesTable.data[selectedRow][1];
|
||||||
|
for (let i = 0; i < this.schedules.length; i++) {
|
||||||
|
if (this.schedules[i].name === selectedScheduleName) {
|
||||||
|
this.schedules.splice(i, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.populateScheduleTable();
|
||||||
|
}
|
||||||
|
});
|
||||||
let formModel = view.modelBuilder.formContainer()
|
let formModel = view.modelBuilder.formContainer()
|
||||||
.withFormItems([{
|
.withFormItems([{
|
||||||
component: this.schedulesTable,
|
component: this.schedulesTable,
|
||||||
title: this.SchedulesTopLabelString,
|
title: this.SchedulesTopLabelString,
|
||||||
actions: [this.pickScheduleButton]
|
actions: [this.pickScheduleButton, this.removeScheduleButton]
|
||||||
}]).withLayout({ width: '100%' }).component();
|
}]).withLayout({ width: '100%' }).component();
|
||||||
|
|
||||||
await view.initializeModel(formModel);
|
await view.initializeModel(formModel);
|
||||||
@@ -402,12 +528,10 @@ export class JobDialog extends AgentDialog<JobData> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private populateScheduleTable() {
|
private populateScheduleTable() {
|
||||||
let schedules = this.model.jobSchedules ? this.model.jobSchedules : [];
|
let data = this.convertSchedulesToData(this.schedules);
|
||||||
let data = this.convertSchedulesToData(schedules);
|
|
||||||
if (data.length > 0) {
|
|
||||||
this.schedulesTable.data = data;
|
this.schedulesTable.data = data;
|
||||||
this.schedulesTable.height = 750;
|
this.schedulesTable.height = 750;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private initializeNotificationsTab() {
|
private initializeNotificationsTab() {
|
||||||
@@ -566,5 +690,19 @@ export class JobDialog extends AgentDialog<JobData> {
|
|||||||
this.model.pageLevel = this.getActualConditionValue(this.pagerCheckBox, this.pagerConditionDropdown);
|
this.model.pageLevel = this.getActualConditionValue(this.pagerCheckBox, this.pagerConditionDropdown);
|
||||||
this.model.eventLogLevel = this.getActualConditionValue(this.eventLogCheckBox, this.eventLogConditionDropdown);
|
this.model.eventLogLevel = this.getActualConditionValue(this.eventLogCheckBox, this.eventLogConditionDropdown);
|
||||||
this.model.deleteLevel = this.getActualConditionValue(this.deleteJobCheckBox, this.deleteJobConditionDropdown);
|
this.model.deleteLevel = this.getActualConditionValue(this.deleteJobCheckBox, this.deleteJobConditionDropdown);
|
||||||
|
this.model.startStepId = +this.getDropdownValue(this.startStepDropdown);
|
||||||
|
if (!this.model.jobSteps) {
|
||||||
|
this.model.jobSteps = [];
|
||||||
|
}
|
||||||
|
this.model.jobSteps = this.steps;
|
||||||
|
if (!this.model.jobSchedules) {
|
||||||
|
this.model.jobSchedules = [];
|
||||||
|
}
|
||||||
|
this.model.jobSchedules = this.schedules;
|
||||||
|
if (!this.model.alerts) {
|
||||||
|
this.model.alerts = [];
|
||||||
|
}
|
||||||
|
this.model.alerts = this.alerts;
|
||||||
|
this.model.categoryId = +this.model.jobCategoryIdsMap.find(cat => cat.name === this.model.category).id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -29,11 +29,10 @@ export class JobStepDialog extends AgentDialog<JobStepData> {
|
|||||||
private readonly AdvancedTabText: string = localize('jobStepDialog.advanced', 'Advanced');
|
private readonly AdvancedTabText: string = localize('jobStepDialog.advanced', 'Advanced');
|
||||||
private readonly OpenCommandText: string = localize('jobStepDialog.open', 'Open...');
|
private readonly OpenCommandText: string = localize('jobStepDialog.open', 'Open...');
|
||||||
private readonly ParseCommandText: string = localize('jobStepDialog.parse','Parse');
|
private readonly ParseCommandText: string = localize('jobStepDialog.parse','Parse');
|
||||||
private readonly NextButtonText: string = localize('jobStepDialog.next', 'Next');
|
|
||||||
private readonly PreviousButtonText: string = localize('jobStepDialog.previous','Previous');
|
|
||||||
private readonly SuccessfulParseText: string = localize('jobStepDialog.successParse', 'The command was successfully parsed.');
|
private readonly SuccessfulParseText: string = localize('jobStepDialog.successParse', 'The command was successfully parsed.');
|
||||||
private readonly FailureParseText: string = localize('jobStepDialog.failParse', 'The command failed.');
|
private readonly FailureParseText: string = localize('jobStepDialog.failParse', 'The command failed.');
|
||||||
private readonly BlankStepNameErrorText: string = localize('jobStepDialog.blankStepName', 'The step name cannot be left blank');
|
private readonly BlankStepNameErrorText: string = localize('jobStepDialog.blankStepName', 'The step name cannot be left blank');
|
||||||
|
private readonly ProcessExitCodeText: string = localize('jobStepDialog.processExitCode', 'Process exit code of a successful command:');
|
||||||
|
|
||||||
// General Control Titles
|
// General Control Titles
|
||||||
private readonly StepNameLabelString: string = localize('jobStepDialog.stepNameLabel', 'Step Name');
|
private readonly StepNameLabelString: string = localize('jobStepDialog.stepNameLabel', 'Step Name');
|
||||||
@@ -62,11 +61,16 @@ export class JobStepDialog extends AgentDialog<JobStepData> {
|
|||||||
|
|
||||||
// Dropdown options
|
// Dropdown options
|
||||||
private readonly TSQLScript: string = localize('jobStepDialog.TSQL', 'Transact-SQL script (T-SQL)');
|
private readonly TSQLScript: string = localize('jobStepDialog.TSQL', 'Transact-SQL script (T-SQL)');
|
||||||
|
private readonly Powershell: string = localize('jobStepDialog.powershell', 'PowerShell');
|
||||||
|
private readonly CmdExec: string = localize('jobStepDialog.CmdExec', 'Operating system (CmdExec)');
|
||||||
private readonly AgentServiceAccount: string = localize('jobStepDialog.agentServiceAccount', 'SQL Server Agent Service Account');
|
private readonly AgentServiceAccount: string = localize('jobStepDialog.agentServiceAccount', 'SQL Server Agent Service Account');
|
||||||
private readonly NextStep: string = localize('jobStepDialog.nextStep', 'Go to the next step');
|
private readonly NextStep: string = localize('jobStepDialog.nextStep', 'Go to the next step');
|
||||||
private readonly QuitJobReportingSuccess: string = localize('jobStepDialog.quitJobSuccess', 'Quit the job reporting success');
|
private readonly QuitJobReportingSuccess: string = localize('jobStepDialog.quitJobSuccess', 'Quit the job reporting success');
|
||||||
private readonly QuitJobReportingFailure: string = localize('jobStepDialog.quitJobFailure', 'Quit the job reporting failure');
|
private readonly QuitJobReportingFailure: string = localize('jobStepDialog.quitJobFailure', 'Quit the job reporting failure');
|
||||||
|
|
||||||
|
// Event Name strings
|
||||||
|
private readonly NewStepDialog = 'NewStepDialogOpened';
|
||||||
|
private readonly EditStepDialog = 'EditStepDialogOpened';
|
||||||
// UI Components
|
// UI Components
|
||||||
|
|
||||||
// Dialogs
|
// Dialogs
|
||||||
@@ -85,6 +89,7 @@ export class JobStepDialog extends AgentDialog<JobStepData> {
|
|||||||
private outputFileNameBox: sqlops.InputBoxComponent;
|
private outputFileNameBox: sqlops.InputBoxComponent;
|
||||||
private fileBrowserNameBox: sqlops.InputBoxComponent;
|
private fileBrowserNameBox: sqlops.InputBoxComponent;
|
||||||
private userInputBox: sqlops.InputBoxComponent;
|
private userInputBox: sqlops.InputBoxComponent;
|
||||||
|
private processExitCodeBox: sqlops.InputBoxComponent;
|
||||||
|
|
||||||
// Dropdowns
|
// Dropdowns
|
||||||
private typeDropdown: sqlops.DropDownComponent;
|
private typeDropdown: sqlops.DropDownComponent;
|
||||||
@@ -97,8 +102,6 @@ export class JobStepDialog extends AgentDialog<JobStepData> {
|
|||||||
// Buttons
|
// Buttons
|
||||||
private openButton: sqlops.ButtonComponent;
|
private openButton: sqlops.ButtonComponent;
|
||||||
private parseButton: sqlops.ButtonComponent;
|
private parseButton: sqlops.ButtonComponent;
|
||||||
private nextButton: sqlops.ButtonComponent;
|
|
||||||
private previousButton: sqlops.ButtonComponent;
|
|
||||||
private outputFileBrowserButton: sqlops.ButtonComponent;
|
private outputFileBrowserButton: sqlops.ButtonComponent;
|
||||||
|
|
||||||
// Checkbox
|
// Checkbox
|
||||||
@@ -118,9 +121,10 @@ export class JobStepDialog extends AgentDialog<JobStepData> {
|
|||||||
server: string,
|
server: string,
|
||||||
jobModel: JobData,
|
jobModel: JobData,
|
||||||
jobStepInfo?: sqlops.AgentJobStepInfo,
|
jobStepInfo?: sqlops.AgentJobStepInfo,
|
||||||
|
viaJobDialog: boolean = false
|
||||||
) {
|
) {
|
||||||
super(ownerUri,
|
super(ownerUri,
|
||||||
jobStepInfo ? JobStepData.convertToJobStepData(jobStepInfo, jobModel) : new JobStepData(ownerUri, jobModel),
|
jobStepInfo ? JobStepData.convertToJobStepData(jobStepInfo, jobModel) : new JobStepData(ownerUri, jobModel, viaJobDialog),
|
||||||
jobStepInfo ? JobStepDialog.EditDialogTitle : JobStepDialog.NewDialogTitle);
|
jobStepInfo ? JobStepDialog.EditDialogTitle : JobStepDialog.NewDialogTitle);
|
||||||
this.stepId = jobStepInfo ?
|
this.stepId = jobStepInfo ?
|
||||||
jobStepInfo.id : jobModel.jobSteps ?
|
jobStepInfo.id : jobModel.jobSteps ?
|
||||||
@@ -130,6 +134,7 @@ export class JobStepDialog extends AgentDialog<JobStepData> {
|
|||||||
this.jobModel = jobModel;
|
this.jobModel = jobModel;
|
||||||
this.jobName = this.jobName ? this.jobName : this.jobModel.name;
|
this.jobName = this.jobName ? this.jobName : this.jobModel.name;
|
||||||
this.server = server;
|
this.server = server;
|
||||||
|
this.dialogName = this.isEdit ? this.EditStepDialog : this.NewStepDialog;
|
||||||
}
|
}
|
||||||
|
|
||||||
private initializeUIComponents() {
|
private initializeUIComponents() {
|
||||||
@@ -174,18 +179,6 @@ export class JobStepDialog extends AgentDialog<JobStepData> {
|
|||||||
inputType: 'text'
|
inputType: 'text'
|
||||||
})
|
})
|
||||||
.component();
|
.component();
|
||||||
this.nextButton = view.modelBuilder.button()
|
|
||||||
.withProperties({
|
|
||||||
label: this.NextButtonText,
|
|
||||||
enabled: false,
|
|
||||||
width: '80px'
|
|
||||||
}).component();
|
|
||||||
this.previousButton = view.modelBuilder.button()
|
|
||||||
.withProperties({
|
|
||||||
label: this.PreviousButtonText,
|
|
||||||
enabled: false,
|
|
||||||
width: '80px'
|
|
||||||
}).component();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private createGeneralTab(databases: string[], queryProvider: sqlops.QueryProvider) {
|
private createGeneralTab(databases: string[], queryProvider: sqlops.QueryProvider) {
|
||||||
@@ -203,7 +196,7 @@ export class JobStepDialog extends AgentDialog<JobStepData> {
|
|||||||
this.typeDropdown = view.modelBuilder.dropDown()
|
this.typeDropdown = view.modelBuilder.dropDown()
|
||||||
.withProperties({
|
.withProperties({
|
||||||
value: this.TSQLScript,
|
value: this.TSQLScript,
|
||||||
values: [this.TSQLScript]
|
values: [this.TSQLScript, this.CmdExec, this.Powershell]
|
||||||
})
|
})
|
||||||
.component();
|
.component();
|
||||||
this.runAsDropdown = view.modelBuilder.dropDown()
|
this.runAsDropdown = view.modelBuilder.dropDown()
|
||||||
@@ -213,33 +206,20 @@ export class JobStepDialog extends AgentDialog<JobStepData> {
|
|||||||
})
|
})
|
||||||
.component();
|
.component();
|
||||||
this.runAsDropdown.enabled = false;
|
this.runAsDropdown.enabled = false;
|
||||||
this.typeDropdown.onValueChanged((type) => {
|
|
||||||
if (type.selected !== this.TSQLScript) {
|
|
||||||
this.runAsDropdown.value = this.AgentServiceAccount;
|
|
||||||
this.runAsDropdown.values = [this.runAsDropdown.value];
|
|
||||||
} else {
|
|
||||||
this.runAsDropdown.value = '';
|
|
||||||
this.runAsDropdown.values = [''];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.databaseDropdown = view.modelBuilder.dropDown()
|
this.databaseDropdown = view.modelBuilder.dropDown()
|
||||||
.withProperties({
|
.withProperties({
|
||||||
value: databases[0],
|
value: databases[0],
|
||||||
values: databases
|
values: databases
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
|
this.processExitCodeBox = view.modelBuilder.inputBox()
|
||||||
|
.withProperties({
|
||||||
|
}).component();
|
||||||
|
this.processExitCodeBox.enabled = false;
|
||||||
|
|
||||||
// create the commands section
|
// create the commands section
|
||||||
this.createCommands(view, queryProvider);
|
this.createCommands(view, queryProvider);
|
||||||
|
|
||||||
let buttonContainer = view.modelBuilder.flexContainer()
|
|
||||||
.withLayout({
|
|
||||||
flexFlow: 'row',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
width: 420
|
|
||||||
}).withItems([this.openButton, this.parseButton, this.previousButton, this.nextButton], {
|
|
||||||
flex: '1 1 50%'
|
|
||||||
}).component();
|
|
||||||
|
|
||||||
let formModel = view.modelBuilder.formContainer()
|
let formModel = view.modelBuilder.formContainer()
|
||||||
.withFormItems([{
|
.withFormItems([{
|
||||||
component: this.nameTextBox,
|
component: this.nameTextBox,
|
||||||
@@ -253,14 +233,52 @@ export class JobStepDialog extends AgentDialog<JobStepData> {
|
|||||||
}, {
|
}, {
|
||||||
component: this.databaseDropdown,
|
component: this.databaseDropdown,
|
||||||
title: this.DatabaseLabelString
|
title: this.DatabaseLabelString
|
||||||
|
}, {
|
||||||
|
component: this.processExitCodeBox,
|
||||||
|
title: this.ProcessExitCodeText
|
||||||
}, {
|
}, {
|
||||||
component: this.commandTextBox,
|
component: this.commandTextBox,
|
||||||
title: this.CommandLabelString,
|
title: this.CommandLabelString,
|
||||||
actions: [buttonContainer]
|
actions: [this.openButton, this.parseButton]
|
||||||
}], {
|
}], {
|
||||||
horizontal: false,
|
horizontal: false,
|
||||||
componentWidth: 420
|
componentWidth: 420
|
||||||
}).component();
|
}).component();
|
||||||
|
this.typeDropdown.onValueChanged((type) => {
|
||||||
|
switch (type.selected) {
|
||||||
|
case(this.TSQLScript):
|
||||||
|
this.runAsDropdown.value = '';
|
||||||
|
this.runAsDropdown.values = [''];
|
||||||
|
this.runAsDropdown.enabled = false;
|
||||||
|
this.databaseDropdown.enabled = true;
|
||||||
|
this.databaseDropdown.values = databases;
|
||||||
|
this.databaseDropdown.value = databases[0];
|
||||||
|
this.processExitCodeBox.value = '';
|
||||||
|
this.processExitCodeBox.enabled = false;
|
||||||
|
break;
|
||||||
|
case(this.Powershell):
|
||||||
|
this.runAsDropdown.value = this.AgentServiceAccount;
|
||||||
|
this.runAsDropdown.values = [this.runAsDropdown.value];
|
||||||
|
this.runAsDropdown.enabled = true;
|
||||||
|
this.databaseDropdown.enabled = false;
|
||||||
|
this.databaseDropdown.values = [''];
|
||||||
|
this.databaseDropdown.value = '';
|
||||||
|
this.processExitCodeBox.value = '';
|
||||||
|
this.processExitCodeBox.enabled = false;
|
||||||
|
break;
|
||||||
|
case(this.CmdExec):
|
||||||
|
this.databaseDropdown.enabled = false;
|
||||||
|
this.databaseDropdown.values = [''];
|
||||||
|
this.databaseDropdown.value = '';
|
||||||
|
this.runAsDropdown.value = this.AgentServiceAccount;
|
||||||
|
this.runAsDropdown.values = [this.runAsDropdown.value];
|
||||||
|
this.runAsDropdown.enabled = true;
|
||||||
|
this.processExitCodeBox.enabled = true;
|
||||||
|
this.processExitCodeBox.value = '0';
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
let formWrapper = view.modelBuilder.loadingComponent().withItem(formModel).component();
|
let formWrapper = view.modelBuilder.loadingComponent().withItem(formModel).component();
|
||||||
formWrapper.loading = false;
|
formWrapper.loading = false;
|
||||||
await view.initializeModel(formWrapper);
|
await view.initializeModel(formWrapper);
|
||||||
@@ -518,6 +536,8 @@ export class JobStepDialog extends AgentDialog<JobStepData> {
|
|||||||
this.model.failureAction = this.failureActionDropdown.value as string;
|
this.model.failureAction = this.failureActionDropdown.value as string;
|
||||||
this.model.outputFileName = this.outputFileNameBox.value;
|
this.model.outputFileName = this.outputFileNameBox.value;
|
||||||
this.model.appendToLogFile = this.appendToExistingFileCheckbox.checked;
|
this.model.appendToLogFile = this.appendToExistingFileCheckbox.checked;
|
||||||
|
this.model.command = this.commandTextBox.value ? this.commandTextBox.value : '';
|
||||||
|
this.model.commandExecutionSuccessCode = this.processExitCodeBox.value ? +this.processExitCodeBox.value : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async initializeDialog() {
|
public async initializeDialog() {
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export class OperatorDialog extends AgentDialog<OperatorData> {
|
|||||||
private static readonly PagerSundayCheckBoxLabel: string = localize('createOperator.PagerSundayCheckBox', 'Sunday');
|
private static readonly PagerSundayCheckBoxLabel: string = localize('createOperator.PagerSundayCheckBox', 'Sunday');
|
||||||
private static readonly WorkdayBeginLabel: string = localize('createOperator.workdayBegin', 'Workday begin');
|
private static readonly WorkdayBeginLabel: string = localize('createOperator.workdayBegin', 'Workday begin');
|
||||||
private static readonly WorkdayEndLabel: string = localize('createOperator.workdayEnd', 'Workday end');
|
private static readonly WorkdayEndLabel: string = localize('createOperator.workdayEnd', 'Workday end');
|
||||||
private static readonly PagerDutyScheduleLabel: string = localize('createOperator.PagerDutySchedule', 'Pager on duty schdule');
|
private static readonly PagerDutyScheduleLabel: string = localize('createOperator.PagerDutySchedule', 'Pager on duty schedule');
|
||||||
|
|
||||||
// Notifications tab strings
|
// Notifications tab strings
|
||||||
private static readonly AlertsTableLabel: string = localize('createOperator.AlertListHeading', 'Alert list');
|
private static readonly AlertsTableLabel: string = localize('createOperator.AlertListHeading', 'Alert list');
|
||||||
@@ -43,6 +43,10 @@ export class OperatorDialog extends AgentDialog<OperatorData> {
|
|||||||
private static readonly AlertEmailColumnLabel: string = localize('createOperator.AlertEmailColumnLabel', 'E-mail');
|
private static readonly AlertEmailColumnLabel: string = localize('createOperator.AlertEmailColumnLabel', 'E-mail');
|
||||||
private static readonly AlertPagerColumnLabel: string = localize('createOperator.AlertPagerColumnLabel', 'Pager');
|
private static readonly AlertPagerColumnLabel: string = localize('createOperator.AlertPagerColumnLabel', 'Pager');
|
||||||
|
|
||||||
|
// Event strings
|
||||||
|
private readonly NewOperatorDialog = 'NewOperatorDialogOpened';
|
||||||
|
private readonly EditOperatorDialog = 'EditOperatorDialogOpened';
|
||||||
|
|
||||||
// UI Components
|
// UI Components
|
||||||
private generalTab: sqlops.window.modelviewdialog.DialogTab;
|
private generalTab: sqlops.window.modelviewdialog.DialogTab;
|
||||||
private notificationsTab: sqlops.window.modelviewdialog.DialogTab;
|
private notificationsTab: sqlops.window.modelviewdialog.DialogTab;
|
||||||
@@ -68,12 +72,15 @@ export class OperatorDialog extends AgentDialog<OperatorData> {
|
|||||||
|
|
||||||
// Notification tab controls
|
// Notification tab controls
|
||||||
private alertsTable: sqlops.TableComponent;
|
private alertsTable: sqlops.TableComponent;
|
||||||
|
private isEdit: boolean = false;
|
||||||
|
|
||||||
constructor(ownerUri: string, operatorInfo: sqlops.AgentOperatorInfo = undefined) {
|
constructor(ownerUri: string, operatorInfo: sqlops.AgentOperatorInfo = undefined) {
|
||||||
super(
|
super(
|
||||||
ownerUri,
|
ownerUri,
|
||||||
new OperatorData(ownerUri, operatorInfo),
|
new OperatorData(ownerUri, operatorInfo),
|
||||||
operatorInfo ? OperatorDialog.EditDialogTitle : OperatorDialog.CreateDialogTitle);
|
operatorInfo ? OperatorDialog.EditDialogTitle : OperatorDialog.CreateDialogTitle);
|
||||||
|
this.isEdit = operatorInfo ? true : false;
|
||||||
|
this.dialogName = this.isEdit ? this.EditOperatorDialog : this.NewOperatorDialog;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async initializeDialog(dialog: sqlops.window.modelviewdialog.Dialog) {
|
protected async initializeDialog(dialog: sqlops.window.modelviewdialog.Dialog) {
|
||||||
|
|||||||
@@ -36,6 +36,9 @@ export class ProxyDialog extends AgentDialog<ProxyData> {
|
|||||||
private static readonly PowerShellLabel: string = localize('createProxy.PowerShell', 'PowerShell');
|
private static readonly PowerShellLabel: string = localize('createProxy.PowerShell', 'PowerShell');
|
||||||
private static readonly SubSystemHeadingLabel: string = localize('createProxy.subSystemHeading', 'Active to the following subsytems');
|
private static readonly SubSystemHeadingLabel: string = localize('createProxy.subSystemHeading', 'Active to the following subsytems');
|
||||||
|
|
||||||
|
private readonly NewProxyDialog = 'NewProxyDialogOpened';
|
||||||
|
private readonly EditProxyDialog = 'EditProxyDialogOpened';
|
||||||
|
|
||||||
// UI Components
|
// UI Components
|
||||||
private generalTab: sqlops.window.modelviewdialog.DialogTab;
|
private generalTab: sqlops.window.modelviewdialog.DialogTab;
|
||||||
|
|
||||||
@@ -56,6 +59,7 @@ export class ProxyDialog extends AgentDialog<ProxyData> {
|
|||||||
private powershellCheckBox: sqlops.CheckBoxComponent;
|
private powershellCheckBox: sqlops.CheckBoxComponent;
|
||||||
|
|
||||||
private credentials: sqlops.CredentialInfo[];
|
private credentials: sqlops.CredentialInfo[];
|
||||||
|
private isEdit: boolean = false;
|
||||||
|
|
||||||
constructor(ownerUri: string, proxyInfo: sqlops.AgentProxyInfo = undefined, credentials: sqlops.CredentialInfo[]) {
|
constructor(ownerUri: string, proxyInfo: sqlops.AgentProxyInfo = undefined, credentials: sqlops.CredentialInfo[]) {
|
||||||
super(
|
super(
|
||||||
@@ -63,6 +67,8 @@ export class ProxyDialog extends AgentDialog<ProxyData> {
|
|||||||
new ProxyData(ownerUri, proxyInfo),
|
new ProxyData(ownerUri, proxyInfo),
|
||||||
proxyInfo ? ProxyDialog.EditDialogTitle : ProxyDialog.CreateDialogTitle);
|
proxyInfo ? ProxyDialog.EditDialogTitle : ProxyDialog.CreateDialogTitle);
|
||||||
this.credentials = credentials;
|
this.credentials = credentials;
|
||||||
|
this.isEdit = proxyInfo ? true : false;
|
||||||
|
this.dialogName = this.isEdit ? this.EditProxyDialog : this.NewProxyDialog;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async initializeDialog(dialog: sqlops.window.modelviewdialog.Dialog) {
|
protected async initializeDialog(dialog: sqlops.window.modelviewdialog.Dialog) {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { ProxyDialog } from './dialogs/proxyDialog';
|
|||||||
import { JobStepDialog } from './dialogs/jobStepDialog';
|
import { JobStepDialog } from './dialogs/jobStepDialog';
|
||||||
import { PickScheduleDialog } from './dialogs/pickScheduleDialog';
|
import { PickScheduleDialog } from './dialogs/pickScheduleDialog';
|
||||||
import { JobData } from './data/jobData';
|
import { JobData } from './data/jobData';
|
||||||
|
import { AgentUtils } from './agentUtils';
|
||||||
|
|
||||||
const localize = nls.loadMessageBundle();
|
const localize = nls.loadMessageBundle();
|
||||||
|
|
||||||
@@ -39,28 +40,33 @@ export class MainController {
|
|||||||
public activate(): void {
|
public activate(): void {
|
||||||
vscode.commands.registerCommand('agent.openJobDialog', (ownerUri: string, jobInfo: sqlops.AgentJobInfo) => {
|
vscode.commands.registerCommand('agent.openJobDialog', (ownerUri: string, jobInfo: sqlops.AgentJobInfo) => {
|
||||||
let dialog = new JobDialog(ownerUri, jobInfo);
|
let dialog = new JobDialog(ownerUri, jobInfo);
|
||||||
dialog.openDialog();
|
dialog.dialogName ? dialog.openDialog(dialog.dialogName) : dialog.openDialog();
|
||||||
|
});
|
||||||
|
vscode.commands.registerCommand('agent.openNewStepDialog', (ownerUri: string, server: string, jobInfo: sqlops.AgentJobInfo, jobStepInfo: sqlops.AgentJobStepInfo) => {
|
||||||
|
AgentUtils.getAgentService().then((agentService) => {
|
||||||
|
let jobData: JobData = new JobData(ownerUri, jobInfo, agentService);
|
||||||
|
let dialog = new JobStepDialog(ownerUri, server, jobData, jobStepInfo, false);
|
||||||
|
dialog.dialogName ? dialog.openDialog(dialog.dialogName) : dialog.openDialog();
|
||||||
});
|
});
|
||||||
vscode.commands.registerCommand('agent.openNewStepDialog', (ownerUri: string, server: string, jobData: JobData, jobStepInfo: sqlops.AgentJobStepInfo) => {
|
|
||||||
let dialog = new JobStepDialog(ownerUri, server, jobData, jobStepInfo);
|
|
||||||
dialog.openDialog();
|
|
||||||
});
|
});
|
||||||
vscode.commands.registerCommand('agent.openPickScheduleDialog', (ownerUri: string, jobName: string) => {
|
vscode.commands.registerCommand('agent.openPickScheduleDialog', (ownerUri: string, jobName: string) => {
|
||||||
let dialog = new PickScheduleDialog(ownerUri, jobName);
|
let dialog = new PickScheduleDialog(ownerUri, jobName);
|
||||||
dialog.showDialog();
|
dialog.showDialog();
|
||||||
});
|
});
|
||||||
vscode.commands.registerCommand('agent.openAlertDialog', (ownerUri: string, alertInfo: sqlops.AgentAlertInfo, jobs: string[]) => {
|
vscode.commands.registerCommand('agent.openAlertDialog', (ownerUri: string, jobInfo: sqlops.AgentJobInfo, alertInfo: sqlops.AgentAlertInfo) => {
|
||||||
let dialog = new AlertDialog(ownerUri, alertInfo, jobs);
|
AgentUtils.getAgentService().then((agentService) => {
|
||||||
dialog.openDialog();
|
let jobData: JobData = new JobData(ownerUri, jobInfo, agentService);
|
||||||
|
let dialog = new AlertDialog(ownerUri, jobData, alertInfo, false);
|
||||||
|
dialog.dialogName ? dialog.openDialog(dialog.dialogName) : dialog.openDialog();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
vscode.commands.registerCommand('agent.openOperatorDialog', (ownerUri: string, operatorInfo: sqlops.AgentOperatorInfo) => {
|
vscode.commands.registerCommand('agent.openOperatorDialog', (ownerUri: string, operatorInfo: sqlops.AgentOperatorInfo) => {
|
||||||
let dialog = new OperatorDialog(ownerUri, operatorInfo);
|
let dialog = new OperatorDialog(ownerUri, operatorInfo);
|
||||||
dialog.openDialog();
|
dialog.dialogName ? dialog.openDialog(dialog.dialogName) : dialog.openDialog();
|
||||||
});
|
});
|
||||||
vscode.commands.registerCommand('agent.openProxyDialog', (ownerUri: string, proxyInfo: sqlops.AgentProxyInfo, credentials: sqlops.CredentialInfo[]) => {
|
vscode.commands.registerCommand('agent.openProxyDialog', (ownerUri: string, proxyInfo: sqlops.AgentProxyInfo, credentials: sqlops.CredentialInfo[]) => {
|
||||||
let dialog = new ProxyDialog(ownerUri, proxyInfo, credentials);
|
let dialog = new ProxyDialog(ownerUri, proxyInfo, credentials);
|
||||||
dialog.openDialog();
|
dialog.dialogName ? dialog.openDialog(dialog.dialogName) : dialog.openDialog();
|
||||||
MainController.showNotYetImplemented();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,38 +5,46 @@
|
|||||||
ansi-regex@^3.0.0:
|
ansi-regex@^3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998"
|
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998"
|
||||||
|
integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=
|
||||||
|
|
||||||
charenc@~0.0.1:
|
charenc@~0.0.1:
|
||||||
version "0.0.2"
|
version "0.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667"
|
resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667"
|
||||||
|
integrity sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=
|
||||||
|
|
||||||
crypt@~0.0.1:
|
crypt@~0.0.1:
|
||||||
version "0.0.2"
|
version "0.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b"
|
resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b"
|
||||||
|
integrity sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=
|
||||||
|
|
||||||
debug@^2.2.0:
|
debug@^2.2.0:
|
||||||
version "2.6.9"
|
version "2.6.9"
|
||||||
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
|
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
|
||||||
|
integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
|
||||||
dependencies:
|
dependencies:
|
||||||
ms "2.0.0"
|
ms "2.0.0"
|
||||||
|
|
||||||
debug@^3.1.0:
|
debug@^3.1.0:
|
||||||
version "3.1.0"
|
version "3.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
|
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
|
||||||
|
integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
|
||||||
dependencies:
|
dependencies:
|
||||||
ms "2.0.0"
|
ms "2.0.0"
|
||||||
|
|
||||||
is-buffer@~1.1.1:
|
is-buffer@~1.1.1:
|
||||||
version "1.1.6"
|
version "1.1.6"
|
||||||
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
|
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
|
||||||
|
integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==
|
||||||
|
|
||||||
lodash@^4.16.4:
|
lodash@^4.16.4:
|
||||||
version "4.17.10"
|
version "4.17.10"
|
||||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7"
|
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7"
|
||||||
|
integrity sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==
|
||||||
|
|
||||||
md5@^2.1.0:
|
md5@^2.1.0:
|
||||||
version "2.2.1"
|
version "2.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/md5/-/md5-2.2.1.tgz#53ab38d5fe3c8891ba465329ea23fac0540126f9"
|
resolved "https://registry.yarnpkg.com/md5/-/md5-2.2.1.tgz#53ab38d5fe3c8891ba465329ea23fac0540126f9"
|
||||||
|
integrity sha1-U6s41f48iJG6RlMp6iP6wFQBJvk=
|
||||||
dependencies:
|
dependencies:
|
||||||
charenc "~0.0.1"
|
charenc "~0.0.1"
|
||||||
crypt "~0.0.1"
|
crypt "~0.0.1"
|
||||||
@@ -45,16 +53,19 @@ md5@^2.1.0:
|
|||||||
minimist@0.0.8:
|
minimist@0.0.8:
|
||||||
version "0.0.8"
|
version "0.0.8"
|
||||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
|
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
|
||||||
|
integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=
|
||||||
|
|
||||||
mkdirp@~0.5.1:
|
mkdirp@~0.5.1:
|
||||||
version "0.5.1"
|
version "0.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
|
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
|
||||||
|
integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=
|
||||||
dependencies:
|
dependencies:
|
||||||
minimist "0.0.8"
|
minimist "0.0.8"
|
||||||
|
|
||||||
mocha-junit-reporter@^1.17.0:
|
mocha-junit-reporter@^1.17.0:
|
||||||
version "1.17.0"
|
version "1.17.0"
|
||||||
resolved "https://registry.yarnpkg.com/mocha-junit-reporter/-/mocha-junit-reporter-1.17.0.tgz#2e5149ed40fc5d2e3ca71e42db5ab1fec9c6d85c"
|
resolved "https://registry.yarnpkg.com/mocha-junit-reporter/-/mocha-junit-reporter-1.17.0.tgz#2e5149ed40fc5d2e3ca71e42db5ab1fec9c6d85c"
|
||||||
|
integrity sha1-LlFJ7UD8XS48px5C21qx/snG2Fw=
|
||||||
dependencies:
|
dependencies:
|
||||||
debug "^2.2.0"
|
debug "^2.2.0"
|
||||||
md5 "^2.1.0"
|
md5 "^2.1.0"
|
||||||
@@ -65,6 +76,7 @@ mocha-junit-reporter@^1.17.0:
|
|||||||
mocha-multi-reporters@^1.1.7:
|
mocha-multi-reporters@^1.1.7:
|
||||||
version "1.1.7"
|
version "1.1.7"
|
||||||
resolved "https://registry.yarnpkg.com/mocha-multi-reporters/-/mocha-multi-reporters-1.1.7.tgz#cc7f3f4d32f478520941d852abb64d9988587d82"
|
resolved "https://registry.yarnpkg.com/mocha-multi-reporters/-/mocha-multi-reporters-1.1.7.tgz#cc7f3f4d32f478520941d852abb64d9988587d82"
|
||||||
|
integrity sha1-zH8/TTL0eFIJQdhSq7ZNmYhYfYI=
|
||||||
dependencies:
|
dependencies:
|
||||||
debug "^3.1.0"
|
debug "^3.1.0"
|
||||||
lodash "^4.16.4"
|
lodash "^4.16.4"
|
||||||
@@ -72,17 +84,21 @@ mocha-multi-reporters@^1.1.7:
|
|||||||
ms@2.0.0:
|
ms@2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
||||||
|
integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
|
||||||
|
|
||||||
strip-ansi@^4.0.0:
|
strip-ansi@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f"
|
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f"
|
||||||
|
integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8=
|
||||||
dependencies:
|
dependencies:
|
||||||
ansi-regex "^3.0.0"
|
ansi-regex "^3.0.0"
|
||||||
|
|
||||||
vscode-nls@^3.2.1:
|
vscode-nls@^3.2.1:
|
||||||
version "3.2.2"
|
version "3.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.2.tgz#3817eca5b985c2393de325197cf4e15eb2aa5350"
|
resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.2.tgz#3817eca5b985c2393de325197cf4e15eb2aa5350"
|
||||||
|
integrity sha512-/Ur1+tgazwd51+ncRyoy0UIu4dvMdVXS9XMUULQlZIBoNGEwOhwEx9x+hHWoUjldMrOQ32t2CGKo0u6D4R6/hg==
|
||||||
|
|
||||||
xml@^1.0.0:
|
xml@^1.0.0:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/xml/-/xml-1.0.1.tgz#78ba72020029c5bc87b8a81a3cfcd74b4a2fc1e5"
|
resolved "https://registry.yarnpkg.com/xml/-/xml-1.0.1.tgz#78ba72020029c5bc87b8a81a3cfcd74b4a2fc1e5"
|
||||||
|
integrity sha1-eLpyAgApxbyHuKgaPPzXS0ovweU=
|
||||||
|
|||||||
@@ -17,12 +17,12 @@
|
|||||||
"configuration": [
|
"configuration": [
|
||||||
{
|
{
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"title": "%azure.config.title%",
|
"title": "%azure.resource.config.title%",
|
||||||
"properties": {
|
"properties": {
|
||||||
"azureResource.resourceFilter": {
|
"azure.resource.config.filter": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"default": null,
|
"default": null,
|
||||||
"description": "%azure.resourceFilter.description%"
|
"description": "%azure.resource.config.filter.description%"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -61,39 +61,47 @@
|
|||||||
"category": "Azure Accounts"
|
"category": "Azure Accounts"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "azureresource.refreshall",
|
"command": "azure.resource.refreshall",
|
||||||
"title": "%azureresource.refreshall%",
|
"title": "%azure.resource.refreshall.title%",
|
||||||
"icon": {
|
"icon": {
|
||||||
"dark": "resources/dark/refresh_inverse.svg",
|
"dark": "resources/dark/refresh_inverse.svg",
|
||||||
"light": "resources/light/refresh.svg"
|
"light": "resources/light/refresh.svg"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "azureresource.refresh",
|
"command": "azure.resource.refresh",
|
||||||
"title": "%azureresource.refresh%",
|
"title": "%azure.resource.refresh.title%",
|
||||||
"icon": {
|
"icon": {
|
||||||
"dark": "resources/dark/refresh_inverse.svg",
|
"dark": "resources/dark/refresh_inverse.svg",
|
||||||
"light": "resources/light/refresh.svg"
|
"light": "resources/light/refresh.svg"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "azureresource.signin",
|
"command": "azure.resource.signin",
|
||||||
"title": "%azureresource.signin%"
|
"title": "%azure.resource.signin.title%"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "azureresource.connectsqldb",
|
"command": "azure.resource.selectsubscriptions",
|
||||||
"title": "%azureresource.connectsqldb%",
|
"title": "%azure.resource.selectsubscriptions.title%",
|
||||||
|
"icon": {
|
||||||
|
"dark": "resources/dark/filter_inverse.svg",
|
||||||
|
"light": "resources/light/filter.svg"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "azure.resource.connectsqlserver",
|
||||||
|
"title": "%azure.resource.connectsqlserver.title%",
|
||||||
"icon": {
|
"icon": {
|
||||||
"dark": "resources/dark/connect_to_inverse.svg",
|
"dark": "resources/dark/connect_to_inverse.svg",
|
||||||
"light": "resources/light/connect_to.svg"
|
"light": "resources/light/connect_to.svg"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "azureresource.selectsubscriptions",
|
"command": "azure.resource.connectsqldb",
|
||||||
"title": "%azureresource.selectsubscriptions%",
|
"title": "%azure.resource.connectsqldb.title%",
|
||||||
"icon": {
|
"icon": {
|
||||||
"dark": "resources/dark/filter_inverse.svg",
|
"dark": "resources/dark/connect_to_inverse.svg",
|
||||||
"light": "resources/light/filter.svg"
|
"light": "resources/light/connect_to.svg"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -110,46 +118,47 @@
|
|||||||
"azureResource": [
|
"azureResource": [
|
||||||
{
|
{
|
||||||
"id": "azureResourceExplorer",
|
"id": "azureResourceExplorer",
|
||||||
"name": "%azure.resourceExplorer.title%"
|
"name": "%azure.resource.explorer.title%"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"menus": {
|
"menus": {
|
||||||
"view/title": [
|
"view/title": [
|
||||||
{
|
{
|
||||||
"command": "azureresource.refreshall",
|
"command": "azure.resource.refreshall",
|
||||||
"when": "view == azureResourceExplorer",
|
"when": "view == azureResourceExplorer",
|
||||||
"group": "navigation@1"
|
"group": "navigation@1"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"view/item/context": [
|
"view/item/context": [
|
||||||
{
|
{
|
||||||
"command": "azureresource.connectsqldb",
|
"command": "azure.resource.selectsubscriptions",
|
||||||
"when": "viewItem =~ /^azureResource.itemType.database/ && viewItem != azureResource.itemType.databaseContainer && viewItem != azureResource.itemType.databaseServerContainer",
|
"when": "viewItem == azure.resource.itemType.account",
|
||||||
"group": "1azureresource@1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "azureresource.connectsqldb",
|
|
||||||
"when": "viewItem =~ /^azureResource.itemType.database/ && viewItem != azureResource.itemType.databaseContainer && viewItem != azureResource.itemType.databaseServerContainer",
|
|
||||||
"group": "inline"
|
"group": "inline"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "azureresource.selectsubscriptions",
|
"command": "azure.resource.refresh",
|
||||||
"when": "viewItem == azureResource.itemType.account",
|
"when": "viewItem =~ /^azure\\.resource\\.itemType\\.(?:account|subscription|databaseContainer|databaseServerContainer)$/",
|
||||||
"group": "inline"
|
"group": "inline"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "azureresource.refresh",
|
"command": "azure.resource.connectsqlserver",
|
||||||
"when": "viewItem != azureResource.itemType.database && viewItem != azureResource.itemType.databaseServer && viewItem != azureResource.itemType.message",
|
"when": "viewItem == azure.resource.itemType.databaseServer",
|
||||||
|
"group": "inline"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "azure.resource.connectsqldb",
|
||||||
|
"when": "viewItem == azure.resource.itemType.database",
|
||||||
"group": "inline"
|
"group": "inline"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
},
|
||||||
|
"hasAzureResourceProviders": true
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"request": "2.63.0",
|
|
||||||
"azure-arm-resource": "^7.0.0",
|
"azure-arm-resource": "^7.0.0",
|
||||||
"azure-arm-sql": "^5.0.1",
|
"azure-arm-sql": "^5.0.1",
|
||||||
|
"request": "2.88.0",
|
||||||
"vscode-nls": "^4.0.0"
|
"vscode-nls": "^4.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -157,6 +166,7 @@
|
|||||||
"@types/node": "^8.0.24",
|
"@types/node": "^8.0.24",
|
||||||
"mocha": "^5.2.0",
|
"mocha": "^5.2.0",
|
||||||
"should": "^13.2.1",
|
"should": "^13.2.1",
|
||||||
|
"vscode": "^1.1.26",
|
||||||
"typemoq": "^2.1.0"
|
"typemoq": "^2.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,16 +1,20 @@
|
|||||||
{
|
{
|
||||||
"azure.displayName": "Azure (Core)",
|
"azure.displayName": "Azure (Core)",
|
||||||
"azure.description": "Browse and work with Azure resources",
|
"azure.description": "Browse and work with Azure resources",
|
||||||
"azure.config.title": "Azure Resource Configuration",
|
|
||||||
"azure.resourceFilter.description": "The resource filter, each element is an account id, a subscription id and name separated by a slash",
|
|
||||||
"azureresource.refreshall": "Refresh All",
|
|
||||||
"azureresource.refresh": "Refresh",
|
|
||||||
"azureresource.signin": "Sign In",
|
|
||||||
"azureresource.connectsqldb": "Connect",
|
|
||||||
"azureresource.selectsubscriptions": "Select Subscriptions",
|
|
||||||
"azure.title": "Azure",
|
"azure.title": "Azure",
|
||||||
"azure.resourceExplorer.title": "Resource Explorer",
|
|
||||||
|
"azure.resource.config.title": "Azure Resource Configuration",
|
||||||
|
"azure.resource.config.filter.description": "The resource filter, each element is an account id, a subscription id and name separated by a slash",
|
||||||
|
"azure.resource.explorer.title": "Resource Explorer",
|
||||||
|
"azure.resource.refreshall.title": "Refresh All",
|
||||||
|
"azure.resource.refresh.title": "Refresh",
|
||||||
|
"azure.resource.signin.title": "Sign In",
|
||||||
|
"azure.resource.selectsubscriptions.title": "Select Subscriptions",
|
||||||
|
"azure.resource.connectsqlserver.title": "Connect",
|
||||||
|
"azure.resource.connectsqldb.title": "Connect",
|
||||||
|
|
||||||
"accounts.clearTokenCache": "Clear Azure Account Token Cache",
|
"accounts.clearTokenCache": "Clear Azure Account Token Cache",
|
||||||
|
|
||||||
"config.enablePublicCloudDescription": "Should Azure public cloud integration be enabled",
|
"config.enablePublicCloudDescription": "Should Azure public cloud integration be enabled",
|
||||||
"config.enableUsGovCloudDescription": "Should US Government Azure cloud (Fairfax) integration be enabled",
|
"config.enableUsGovCloudDescription": "Should US Government Azure cloud (Fairfax) integration be enabled",
|
||||||
"config.enableChinaCloudDescription": "Should Azure China integration be enabled",
|
"config.enableChinaCloudDescription": "Should Azure China integration be enabled",
|
||||||
|
|||||||
@@ -69,8 +69,8 @@ export class AzureAccountProvider implements sqlops.AccountProvider {
|
|||||||
return this._tokenCache.clear();
|
return this._tokenCache.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public getSecurityToken(account: AzureAccount): Thenable<AzureAccountSecurityTokenCollection> {
|
public getSecurityToken(account: AzureAccount, resource: sqlops.AzureResource): Thenable<AzureAccountSecurityTokenCollection> {
|
||||||
return this.doIfInitialized(() => this.getAccessTokens(account));
|
return this.doIfInitialized(() => this.getAccessTokens(account, resource));
|
||||||
}
|
}
|
||||||
|
|
||||||
public initialize(restoredAccounts: sqlops.Account[]): Thenable<sqlops.Account[]> {
|
public initialize(restoredAccounts: sqlops.Account[]): Thenable<sqlops.Account[]> {
|
||||||
@@ -90,7 +90,7 @@ export class AzureAccountProvider implements sqlops.AccountProvider {
|
|||||||
|
|
||||||
// Attempt to get fresh tokens. If this fails then the account is stale.
|
// Attempt to get fresh tokens. If this fails then the account is stale.
|
||||||
// NOTE: Based on ADAL implementation, getting tokens should use the refresh token if necessary
|
// NOTE: Based on ADAL implementation, getting tokens should use the refresh token if necessary
|
||||||
let task = this.getAccessTokens(account)
|
let task = this.getAccessTokens(account, sqlops.AzureResource.ResourceManagement)
|
||||||
.then(
|
.then(
|
||||||
() => {
|
() => {
|
||||||
return account;
|
return account;
|
||||||
@@ -161,9 +161,14 @@ export class AzureAccountProvider implements sqlops.AccountProvider {
|
|||||||
: Promise.reject(localize('accountProviderNotInitialized', 'Account provider not initialized, cannot perform action'));
|
: Promise.reject(localize('accountProviderNotInitialized', 'Account provider not initialized, cannot perform action'));
|
||||||
}
|
}
|
||||||
|
|
||||||
private getAccessTokens(account: AzureAccount): Thenable<AzureAccountSecurityTokenCollection> {
|
private getAccessTokens(account: AzureAccount, resource: sqlops.AzureResource): Thenable<AzureAccountSecurityTokenCollection> {
|
||||||
let self = this;
|
let self = this;
|
||||||
|
|
||||||
|
const resourceIdMap = new Map<sqlops.AzureResource, string>([
|
||||||
|
[sqlops.AzureResource.ResourceManagement, self._metadata.settings.armResource.id],
|
||||||
|
[sqlops.AzureResource.Sql, self._metadata.settings.sqlResource.id]
|
||||||
|
]);
|
||||||
|
|
||||||
let accessTokenPromises: Thenable<void>[] = [];
|
let accessTokenPromises: Thenable<void>[] = [];
|
||||||
let tokenCollection: AzureAccountSecurityTokenCollection = {};
|
let tokenCollection: AzureAccountSecurityTokenCollection = {};
|
||||||
for (let tenant of account.properties.tenants) {
|
for (let tenant of account.properties.tenants) {
|
||||||
@@ -172,7 +177,7 @@ export class AzureAccountProvider implements sqlops.AccountProvider {
|
|||||||
let context = new adal.AuthenticationContext(authorityUrl, null, self._tokenCache);
|
let context = new adal.AuthenticationContext(authorityUrl, null, self._tokenCache);
|
||||||
|
|
||||||
context.acquireToken(
|
context.acquireToken(
|
||||||
self._metadata.settings.armResource.id,
|
resourceIdMap.get(resource),
|
||||||
tenant.userId,
|
tenant.userId,
|
||||||
self._metadata.settings.clientId,
|
self._metadata.settings.clientId,
|
||||||
(error: Error, response: adal.TokenResponse | adal.ErrorResponse) => {
|
(error: Error, response: adal.TokenResponse | adal.ErrorResponse) => {
|
||||||
|
|||||||
@@ -81,6 +81,11 @@ export interface Settings {
|
|||||||
*/
|
*/
|
||||||
armResource?: Resource;
|
armResource?: Resource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Information that describes the SQL Azure resource
|
||||||
|
*/
|
||||||
|
sqlResource?: Resource;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A list of tenant IDs to authenticate against. If defined, then these IDs will be used
|
* A list of tenant IDs to authenticate against. If defined, then these IDs will be used
|
||||||
* instead of querying the tenants endpoint of the armResource
|
* instead of querying the tenants endpoint of the armResource
|
||||||
|
|||||||
@@ -27,6 +27,10 @@ const publicAzureSettings: ProviderSettings = {
|
|||||||
id: 'https://management.core.windows.net/',
|
id: 'https://management.core.windows.net/',
|
||||||
endpoint: 'https://management.azure.com'
|
endpoint: 'https://management.azure.com'
|
||||||
},
|
},
|
||||||
|
sqlResource: {
|
||||||
|
id: 'https://database.windows.net/',
|
||||||
|
endpoint: 'https://database.windows.net'
|
||||||
|
},
|
||||||
redirectUri: 'http://localhost/redirect'
|
redirectUri: 'http://localhost/redirect'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -223,11 +223,30 @@ export default class TokenCache implements adal.TokenCache {
|
|||||||
return this.getOrCreateEncryptionParams()
|
return this.getOrCreateEncryptionParams()
|
||||||
.then(encryptionParams => {
|
.then(encryptionParams => {
|
||||||
try {
|
try {
|
||||||
let cacheCipher = fs.readFileSync(self._cacheSerializationPath, TokenCache.FsOptions);
|
return self.decryptCache('utf8', encryptionParams);
|
||||||
|
} catch (e) {
|
||||||
|
try {
|
||||||
|
// try to parse using 'binary' encoding and rewrite cache as UTF8
|
||||||
|
let response = self.decryptCache('binary', encryptionParams);
|
||||||
|
self.writeCache(response);
|
||||||
|
return response;
|
||||||
|
} catch (e) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(null, err => {
|
||||||
|
// If reading the token cache fails, we'll just assume the tokens are garbage
|
||||||
|
console.warn(`Failed to read token cache: ${err}`);
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private decryptCache(encoding: crypto.Utf8AsciiBinaryEncoding, encryptionParams: EncryptionParams): adal.TokenResponse[] {
|
||||||
|
let cacheCipher = fs.readFileSync(this._cacheSerializationPath, TokenCache.FsOptions);
|
||||||
let decipher = crypto.createDecipheriv(TokenCache.CipherAlgorithm, encryptionParams.key, encryptionParams.initializationVector);
|
let decipher = crypto.createDecipheriv(TokenCache.CipherAlgorithm, encryptionParams.key, encryptionParams.initializationVector);
|
||||||
let cacheJson = decipher.update(cacheCipher, 'hex', 'binary');
|
let cacheJson = decipher.update(cacheCipher, 'hex', encoding);
|
||||||
cacheJson += decipher.final('binary');
|
cacheJson += decipher.final(encoding);
|
||||||
|
|
||||||
// Deserialize the JSON into the array of tokens
|
// Deserialize the JSON into the array of tokens
|
||||||
let cacheObj = <adal.TokenResponse[]>JSON.parse(cacheJson);
|
let cacheObj = <adal.TokenResponse[]>JSON.parse(cacheJson);
|
||||||
@@ -237,15 +256,6 @@ export default class TokenCache implements adal.TokenCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return cacheObj;
|
return cacheObj;
|
||||||
} catch (e) {
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then(null, err => {
|
|
||||||
// If reading the token cache fails, we'll just assume the tokens are garbage
|
|
||||||
console.warn(`Failed to read token cache: ${err}`);
|
|
||||||
return [];
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private removeFromCache(cache: adal.TokenResponse[], entries: adal.TokenResponse[]): adal.TokenResponse[] {
|
private removeFromCache(cache: adal.TokenResponse[], entries: adal.TokenResponse[]): adal.TokenResponse[] {
|
||||||
@@ -274,7 +284,7 @@ export default class TokenCache implements adal.TokenCache {
|
|||||||
let cacheJson = JSON.stringify(cache);
|
let cacheJson = JSON.stringify(cache);
|
||||||
|
|
||||||
let cipher = crypto.createCipheriv(TokenCache.CipherAlgorithm, encryptionParams.key, encryptionParams.initializationVector);
|
let cipher = crypto.createCipheriv(TokenCache.CipherAlgorithm, encryptionParams.key, encryptionParams.initializationVector);
|
||||||
let cacheCipher = cipher.update(cacheJson, 'binary', 'hex');
|
let cacheCipher = cipher.update(cacheJson, 'utf8', 'hex');
|
||||||
cacheCipher += cipher.final('hex');
|
cacheCipher += cipher.final('hex');
|
||||||
|
|
||||||
fs.writeFileSync(self._cacheSerializationPath, cacheCipher, TokenCache.FsOptions);
|
fs.writeFileSync(self._cacheSerializationPath, cacheCipher, TokenCache.FsOptions);
|
||||||
|
|||||||
@@ -212,8 +212,8 @@ export class ApiWrapper {
|
|||||||
return sqlops.accounts.getAllAccounts();
|
return sqlops.accounts.getAllAccounts();
|
||||||
}
|
}
|
||||||
|
|
||||||
public getSecurityToken(account: sqlops.Account): Thenable<{}> {
|
public getSecurityToken(account: sqlops.Account, resource: sqlops.AzureResource): Thenable<{}> {
|
||||||
return sqlops.accounts.getSecurityToken(account);
|
return sqlops.accounts.getSecurityToken(account, resource);
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly onDidChangeAccounts = sqlops.accounts.onDidChangeAccounts;
|
public readonly onDidChangeAccounts = sqlops.accounts.onDidChangeAccounts;
|
||||||
|
|||||||
28
extensions/azurecore/src/azureResource/azure-resource.d.ts
vendored
Normal file
28
extensions/azurecore/src/azureResource/azure-resource.d.ts
vendored
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import { TreeDataProvider, TreeItem } from 'vscode';
|
||||||
|
import { DataProvider, Account } from 'sqlops';
|
||||||
|
|
||||||
|
export namespace azureResource {
|
||||||
|
export interface IAzureResourceProvider extends DataProvider {
|
||||||
|
getTreeDataProvider(): IAzureResourceTreeDataProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAzureResourceTreeDataProvider extends TreeDataProvider<IAzureResourceNode> {
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAzureResourceNode {
|
||||||
|
readonly account: Account;
|
||||||
|
readonly subscription: AzureResourceSubscription;
|
||||||
|
readonly tenantId: string;
|
||||||
|
readonly treeItem: TreeItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AzureResourceSubscription {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,35 +6,48 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import { window, QuickPickItem } from 'vscode';
|
import { window, QuickPickItem } from 'vscode';
|
||||||
import { IConnectionProfile } from 'sqlops';
|
import { AzureResource } from 'sqlops';
|
||||||
import { generateGuid } from './utils';
|
import { TokenCredentials } from 'ms-rest';
|
||||||
import { ApiWrapper } from '../apiWrapper';
|
import { AppContext } from '../appContext';
|
||||||
import { TreeNode } from '../treeNodes';
|
import * as nls from 'vscode-nls';
|
||||||
|
const localize = nls.loadMessageBundle();
|
||||||
|
|
||||||
|
import { azureResource } from './azure-resource';
|
||||||
|
import { TreeNode } from './treeNode';
|
||||||
|
import { AzureResourceCredentialError } from './errors';
|
||||||
import { AzureResourceTreeProvider } from './tree/treeProvider';
|
import { AzureResourceTreeProvider } from './tree/treeProvider';
|
||||||
import { AzureResourceDatabaseServerTreeNode } from './tree/databaseServerTreeNode';
|
|
||||||
import { AzureResourceDatabaseTreeNode } from './tree/databaseTreeNode';
|
|
||||||
import { AzureResourceAccountTreeNode } from './tree/accountTreeNode';
|
import { AzureResourceAccountTreeNode } from './tree/accountTreeNode';
|
||||||
import { AzureResourceServicePool } from './servicePool';
|
import { IAzureResourceSubscriptionService, IAzureResourceSubscriptionFilterService } from '../azureResource/interfaces';
|
||||||
import { AzureResourceSubscription } from './models';
|
import { AzureResourceServiceNames } from './constants';
|
||||||
|
|
||||||
export function registerAzureResourceCommands(apiWrapper: ApiWrapper, tree: AzureResourceTreeProvider): void {
|
export function registerAzureResourceCommands(appContext: AppContext, tree: AzureResourceTreeProvider): void {
|
||||||
apiWrapper.registerCommand('azureresource.selectsubscriptions', async (node?: TreeNode) => {
|
appContext.apiWrapper.registerCommand('azure.resource.selectsubscriptions', async (node?: TreeNode) => {
|
||||||
if (!(node instanceof AzureResourceAccountTreeNode)) {
|
if (!(node instanceof AzureResourceAccountTreeNode)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const subscriptionService = appContext.getService<IAzureResourceSubscriptionService>(AzureResourceServiceNames.subscriptionService);
|
||||||
|
const subscriptionFilterService = appContext.getService<IAzureResourceSubscriptionFilterService>(AzureResourceServiceNames.subscriptionFilterService);
|
||||||
|
|
||||||
const accountNode = node as AzureResourceAccountTreeNode;
|
const accountNode = node as AzureResourceAccountTreeNode;
|
||||||
|
|
||||||
const servicePool = AzureResourceServicePool.getInstance();
|
const subscriptions = (await accountNode.getCachedSubscriptions()) || <azureResource.AzureResourceSubscription[]>[];
|
||||||
|
if (subscriptions.length === 0) {
|
||||||
|
try {
|
||||||
|
const tokens = await this.servicePool.apiWrapper.getSecurityToken(this.account, AzureResource.ResourceManagement);
|
||||||
|
|
||||||
let subscriptions = await accountNode.getCachedSubscriptions();
|
for (const tenant of this.account.properties.tenants) {
|
||||||
if (!subscriptions || subscriptions.length === 0) {
|
const token = tokens[tenant.id].token;
|
||||||
const credentials = await servicePool.credentialService.getCredentials(accountNode.account);
|
const tokenType = tokens[tenant.id].tokenType;
|
||||||
subscriptions = await servicePool.subscriptionService.getSubscriptions(accountNode.account, credentials);
|
|
||||||
|
subscriptions.push(...await subscriptionService.getSubscriptions(accountNode.account, new TokenCredentials(token, tokenType)));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
throw new AzureResourceCredentialError(localize('azure.resource.selectsubscriptions.credentialError', 'Failed to get credential for account {0}. Please refresh the account.', this.account.key.accountId), error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectedSubscriptions = (await servicePool.subscriptionFilterService.getSelectedSubscriptions(accountNode.account)) || <AzureResourceSubscription[]>[];
|
let selectedSubscriptions = (await subscriptionFilterService.getSelectedSubscriptions(accountNode.account)) || <azureResource.AzureResourceSubscription[]>[];
|
||||||
const selectedSubscriptionIds: string[] = [];
|
const selectedSubscriptionIds: string[] = [];
|
||||||
if (selectedSubscriptions.length > 0) {
|
if (selectedSubscriptions.length > 0) {
|
||||||
selectedSubscriptionIds.push(...selectedSubscriptions.map((subscription) => subscription.id));
|
selectedSubscriptionIds.push(...selectedSubscriptions.map((subscription) => subscription.id));
|
||||||
@@ -43,11 +56,11 @@ export function registerAzureResourceCommands(apiWrapper: ApiWrapper, tree: Azur
|
|||||||
selectedSubscriptionIds.push(...subscriptions.map((subscription) => subscription.id));
|
selectedSubscriptionIds.push(...subscriptions.map((subscription) => subscription.id));
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SubscriptionQuickPickItem extends QuickPickItem {
|
interface AzureResourceSubscriptionQuickPickItem extends QuickPickItem {
|
||||||
subscription: AzureResourceSubscription;
|
subscription: azureResource.AzureResourceSubscription;
|
||||||
}
|
}
|
||||||
|
|
||||||
const subscriptionItems: SubscriptionQuickPickItem[] = subscriptions.map((subscription) => {
|
const subscriptionQuickPickItems: AzureResourceSubscriptionQuickPickItem[] = subscriptions.map((subscription) => {
|
||||||
return {
|
return {
|
||||||
label: subscription.name,
|
label: subscription.name,
|
||||||
picked: selectedSubscriptionIds.indexOf(subscription.id) !== -1,
|
picked: selectedSubscriptionIds.indexOf(subscription.id) !== -1,
|
||||||
@@ -55,66 +68,22 @@ export function registerAzureResourceCommands(apiWrapper: ApiWrapper, tree: Azur
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const pickedSubscriptionItems = (await window.showQuickPick(subscriptionItems, { canPickMany: true }));
|
const selectedSubscriptionQuickPickItems = (await window.showQuickPick(subscriptionQuickPickItems, { canPickMany: true }));
|
||||||
if (pickedSubscriptionItems && pickedSubscriptionItems.length > 0) {
|
if (selectedSubscriptionQuickPickItems && selectedSubscriptionQuickPickItems.length > 0) {
|
||||||
tree.refresh(node, false);
|
tree.refresh(node, false);
|
||||||
|
|
||||||
const pickedSubscriptions = pickedSubscriptionItems.map((subscriptionItem) => subscriptionItem.subscription);
|
selectedSubscriptions = selectedSubscriptionQuickPickItems.map((subscriptionItem) => subscriptionItem.subscription);
|
||||||
await servicePool.subscriptionFilterService.saveSelectedSubscriptions(accountNode.account, pickedSubscriptions);
|
await subscriptionFilterService.saveSelectedSubscriptions(accountNode.account, selectedSubscriptions);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
apiWrapper.registerCommand('azureresource.refreshall', () => tree.notifyNodeChanged(undefined));
|
appContext.apiWrapper.registerCommand('azure.resource.refreshall', () => tree.notifyNodeChanged(undefined));
|
||||||
|
|
||||||
apiWrapper.registerCommand('azureresource.refresh', async (node?: TreeNode) => {
|
appContext.apiWrapper.registerCommand('azure.resource.refresh', async (node?: TreeNode) => {
|
||||||
tree.refresh(node, true);
|
tree.refresh(node, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
apiWrapper.registerCommand('azureresource.connectsqldb', async (node?: TreeNode) => {
|
appContext.apiWrapper.registerCommand('azure.resource.signin', async (node?: TreeNode) => {
|
||||||
let connectionProfile: IConnectionProfile = {
|
appContext.apiWrapper.executeCommand('sql.action.accounts.manageLinkedAccount');
|
||||||
id: generateGuid(),
|
|
||||||
connectionName: undefined,
|
|
||||||
serverName: undefined,
|
|
||||||
databaseName: undefined,
|
|
||||||
userName: undefined,
|
|
||||||
password: '',
|
|
||||||
authenticationType: undefined,
|
|
||||||
savePassword: true,
|
|
||||||
groupFullName: '',
|
|
||||||
groupId: '',
|
|
||||||
providerName: undefined,
|
|
||||||
saveProfile: true,
|
|
||||||
options: {
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (node instanceof AzureResourceDatabaseServerTreeNode) {
|
|
||||||
let databaseServer = node.databaseServer;
|
|
||||||
connectionProfile.connectionName = `connection to '${databaseServer.defaultDatabaseName}' on '${databaseServer.fullName}'`;
|
|
||||||
connectionProfile.serverName = databaseServer.fullName;
|
|
||||||
connectionProfile.databaseName = databaseServer.defaultDatabaseName;
|
|
||||||
connectionProfile.userName = databaseServer.loginName;
|
|
||||||
connectionProfile.authenticationType = 'SqlLogin';
|
|
||||||
connectionProfile.providerName = 'MSSQL';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node instanceof AzureResourceDatabaseTreeNode) {
|
|
||||||
let database = node.database;
|
|
||||||
connectionProfile.connectionName = `connection to '${database.name}' on '${database.serverFullName}'`;
|
|
||||||
connectionProfile.serverName = database.serverFullName;
|
|
||||||
connectionProfile.databaseName = database.name;
|
|
||||||
connectionProfile.userName = database.loginName;
|
|
||||||
connectionProfile.authenticationType = 'SqlLogin';
|
|
||||||
connectionProfile.providerName = 'MSSQL';
|
|
||||||
}
|
|
||||||
|
|
||||||
const conn = await apiWrapper.openConnectionDialog(undefined, connectionProfile, { saveConnection: true, showDashboard: true });
|
|
||||||
if (conn) {
|
|
||||||
apiWrapper.executeCommand('workbench.view.connections');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
apiWrapper.registerCommand('azureresource.signin', async (node?: TreeNode) => {
|
|
||||||
apiWrapper.executeCommand('sql.action.accounts.manageLinkedAccount');
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,11 +6,19 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
export enum AzureResourceItemType {
|
export enum AzureResourceItemType {
|
||||||
account = 'azureResource.itemType.account',
|
account = 'azure.resource.itemType.account',
|
||||||
subscription = 'azureResource.itemType.subscription',
|
subscription = 'azure.resource.itemType.subscription',
|
||||||
databaseContainer = 'azureResource.itemType.databaseContainer',
|
databaseContainer = 'azure.resource.itemType.databaseContainer',
|
||||||
database = 'azureResource.itemType.database',
|
database = 'azure.resource.itemType.database',
|
||||||
databaseServerContainer = 'azureResource.itemType.databaseServerContainer',
|
databaseServerContainer = 'azure.resource.itemType.databaseServerContainer',
|
||||||
databaseServer = 'azureResource.itemType.databaseServer',
|
databaseServer = 'azure.resource.itemType.databaseServer',
|
||||||
message = 'azureResource.itemType.message'
|
message = 'azure.resource.itemType.message'
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum AzureResourceServiceNames {
|
||||||
|
cacheService = 'AzureResourceCacheService',
|
||||||
|
accountService = 'AzureResourceAccountService',
|
||||||
|
subscriptionService = 'AzureResourceSubscriptionService',
|
||||||
|
subscriptionFilterService = 'AzureResourceSubscriptionFilterService',
|
||||||
|
tenantService = 'AzureResourceTenantService'
|
||||||
}
|
}
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
export class AzureResourceCredentialError extends Error {
|
export class AzureResourceCredentialError extends Error {
|
||||||
constructor(
|
constructor(
|
||||||
message: string,
|
message: string,
|
||||||
public innerError: Error
|
public readonly innerError: Error
|
||||||
) {
|
) {
|
||||||
super(message);
|
super(message);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { ServiceClientCredentials } from 'ms-rest';
|
|||||||
import { Account, DidChangeAccountsParams } from 'sqlops';
|
import { Account, DidChangeAccountsParams } from 'sqlops';
|
||||||
import { Event } from 'vscode';
|
import { Event } from 'vscode';
|
||||||
|
|
||||||
import { AzureResourceSubscription, AzureResourceDatabaseServer, AzureResourceDatabase } from './models';
|
import { azureResource } from './azure-resource';
|
||||||
|
|
||||||
export interface IAzureResourceAccountService {
|
export interface IAzureResourceAccountService {
|
||||||
getAccounts(): Promise<Account[]>;
|
getAccounts(): Promise<Account[]>;
|
||||||
@@ -17,38 +17,29 @@ export interface IAzureResourceAccountService {
|
|||||||
readonly onDidChangeAccounts: Event<DidChangeAccountsParams>;
|
readonly onDidChangeAccounts: Event<DidChangeAccountsParams>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IAzureResourceCredentialService {
|
|
||||||
getCredentials(account: Account): Promise<ServiceClientCredentials[]>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IAzureResourceSubscriptionService {
|
export interface IAzureResourceSubscriptionService {
|
||||||
getSubscriptions(account: Account, credentials: ServiceClientCredentials[]): Promise<AzureResourceSubscription[]>;
|
getSubscriptions(account: Account, credential: ServiceClientCredentials): Promise<azureResource.AzureResourceSubscription[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IAzureResourceSubscriptionFilterService {
|
export interface IAzureResourceSubscriptionFilterService {
|
||||||
getSelectedSubscriptions(account: Account): Promise<AzureResourceSubscription[]>;
|
getSelectedSubscriptions(account: Account): Promise<azureResource.AzureResourceSubscription[]>;
|
||||||
|
|
||||||
saveSelectedSubscriptions(account: Account, selectedSubscriptions: AzureResourceSubscription[]): Promise<void>;
|
saveSelectedSubscriptions(account: Account, selectedSubscriptions: azureResource.AzureResourceSubscription[]): Promise<void>;
|
||||||
}
|
|
||||||
|
|
||||||
export interface IAzureResourceDatabaseServerService {
|
|
||||||
getDatabaseServers(subscription: AzureResourceSubscription, credentials: ServiceClientCredentials[]): Promise<AzureResourceDatabaseServer[]>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IAzureResourceDatabaseService {
|
|
||||||
getDatabases(subscription: AzureResourceSubscription, credentials: ServiceClientCredentials[]): Promise<AzureResourceDatabase[]>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IAzureResourceCacheService {
|
export interface IAzureResourceCacheService {
|
||||||
|
generateKey(id: string): string;
|
||||||
|
|
||||||
get<T>(key: string): T | undefined;
|
get<T>(key: string): T | undefined;
|
||||||
|
|
||||||
update<T>(key: string, value: T): void;
|
update<T>(key: string, value: T): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IAzureResourceContextService {
|
export interface IAzureResourceTenantService {
|
||||||
getAbsolutePath(relativePath: string): string;
|
getTenantId(subscription: azureResource.AzureResourceSubscription): Promise<string>;
|
||||||
|
}
|
||||||
executeCommand(commandId: string, ...args: any[]): void;
|
|
||||||
|
export interface IAzureResourceNodeWithProviderId {
|
||||||
showErrorMessage(errorMessage: string): void;
|
resourceProviderId: string;
|
||||||
|
resourceNode: azureResource.IAzureResourceNode;
|
||||||
}
|
}
|
||||||
@@ -7,9 +7,9 @@
|
|||||||
|
|
||||||
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
|
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
|
||||||
import { NodeInfo } from 'sqlops';
|
import { NodeInfo } from 'sqlops';
|
||||||
import { TreeNode } from '../../treeNodes';
|
|
||||||
|
|
||||||
import { AzureResourceItemType } from '../constants';
|
import { TreeNode } from './treeNode';
|
||||||
|
import { AzureResourceItemType } from './constants';
|
||||||
|
|
||||||
export class AzureResourceMessageTreeNode extends TreeNode {
|
export class AzureResourceMessageTreeNode extends TreeNode {
|
||||||
public constructor(
|
public constructor(
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import { IConnectionProfile } from 'sqlops';
|
||||||
|
import { AppContext } from '../../../appContext';
|
||||||
|
|
||||||
|
import { TreeNode } from '../../treeNode';
|
||||||
|
import { generateGuid } from '../../utils';
|
||||||
|
import { AzureResourceItemType } from '../../constants';
|
||||||
|
import { IAzureResourceDatabaseNode } from './interfaces';
|
||||||
|
import { AzureResourceResourceTreeNode } from '../../resourceTreeNode';
|
||||||
|
|
||||||
|
export function registerAzureResourceDatabaseCommands(appContext: AppContext): void {
|
||||||
|
appContext.apiWrapper.registerCommand('azure.resource.connectsqldb', async (node?: TreeNode) => {
|
||||||
|
if (!node)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const treeItem = await node.getTreeItem();
|
||||||
|
if (treeItem.contextValue !== AzureResourceItemType.database) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const resourceNode = (node as AzureResourceResourceTreeNode).resourceNodeWithProviderId.resourceNode;
|
||||||
|
const database = (resourceNode as IAzureResourceDatabaseNode).database;
|
||||||
|
|
||||||
|
let connectionProfile: IConnectionProfile = {
|
||||||
|
id: generateGuid(),
|
||||||
|
connectionName: undefined,
|
||||||
|
serverName: database.serverFullName,
|
||||||
|
databaseName: database.name,
|
||||||
|
userName: database.loginName,
|
||||||
|
password: '',
|
||||||
|
authenticationType: 'SqlLogin',
|
||||||
|
savePassword: true,
|
||||||
|
groupFullName: '',
|
||||||
|
groupId: '',
|
||||||
|
providerName: 'MSSQL',
|
||||||
|
saveProfile: true,
|
||||||
|
options: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
const conn = await appContext.apiWrapper.openConnectionDialog(undefined, connectionProfile, { saveConnection: true, showDashboard: true });
|
||||||
|
if (conn) {
|
||||||
|
appContext.apiWrapper.executeCommand('workbench.view.connections');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import { ExtensionContext } from 'vscode';
|
||||||
|
import { ApiWrapper } from '../../../apiWrapper';
|
||||||
|
|
||||||
|
import { azureResource } from '../../azure-resource';
|
||||||
|
import { IAzureResourceDatabaseService } from './interfaces';
|
||||||
|
import { AzureResourceDatabaseTreeDataProvider } from './databaseTreeDataProvider';
|
||||||
|
|
||||||
|
export class AzureResourceDatabaseProvider implements azureResource.IAzureResourceProvider {
|
||||||
|
public constructor(
|
||||||
|
databaseService: IAzureResourceDatabaseService,
|
||||||
|
apiWrapper: ApiWrapper,
|
||||||
|
extensionContext: ExtensionContext
|
||||||
|
) {
|
||||||
|
this._databaseService = databaseService;
|
||||||
|
this._apiWrapper = apiWrapper;
|
||||||
|
this._extensionContext = extensionContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getTreeDataProvider(): azureResource.IAzureResourceTreeDataProvider {
|
||||||
|
return new AzureResourceDatabaseTreeDataProvider(this._databaseService, this._apiWrapper, this._extensionContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
public get providerId(): string {
|
||||||
|
return 'azure.resource.providers.database';
|
||||||
|
}
|
||||||
|
|
||||||
|
private _databaseService: IAzureResourceDatabaseService = undefined;
|
||||||
|
private _apiWrapper: ApiWrapper = undefined;
|
||||||
|
private _extensionContext: ExtensionContext = undefined;
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import { ServiceClientCredentials } from 'ms-rest';
|
||||||
|
import { SqlManagementClient } from 'azure-arm-sql';
|
||||||
|
|
||||||
|
import { azureResource } from '../../azure-resource';
|
||||||
|
import { IAzureResourceDatabaseService } from './interfaces';
|
||||||
|
import { AzureResourceDatabase } from './models';
|
||||||
|
|
||||||
|
export class AzureResourceDatabaseService implements IAzureResourceDatabaseService {
|
||||||
|
public async getDatabases(subscription: azureResource.AzureResourceSubscription, credential: ServiceClientCredentials): Promise<AzureResourceDatabase[]> {
|
||||||
|
const databases: AzureResourceDatabase[] = [];
|
||||||
|
const sqlManagementClient = new SqlManagementClient(credential, subscription.id);
|
||||||
|
const svrs = await sqlManagementClient.servers.list();
|
||||||
|
for (const svr of svrs) {
|
||||||
|
// Extract resource group name from svr.id
|
||||||
|
const svrIdRegExp = new RegExp(`\/subscriptions\/${subscription.id}\/resourceGroups\/(.+)\/providers\/Microsoft\.Sql\/servers\/${svr.name}`);
|
||||||
|
if (!svrIdRegExp.test(svr.id)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const founds = svrIdRegExp.exec(svr.id);
|
||||||
|
const resouceGroup = founds[1];
|
||||||
|
|
||||||
|
const dbs = await sqlManagementClient.databases.listByServer(resouceGroup, svr.name);
|
||||||
|
dbs.forEach((db) => databases.push({
|
||||||
|
name: db.name,
|
||||||
|
serverName: svr.name,
|
||||||
|
serverFullName: svr.fullyQualifiedDomainName,
|
||||||
|
loginName: svr.administratorLogin
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
return databases;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import { AzureResource } from 'sqlops';
|
||||||
|
import { TreeItem, TreeItemCollapsibleState, ExtensionContext } from 'vscode';
|
||||||
|
import { TokenCredentials } from 'ms-rest';
|
||||||
|
import * as nls from 'vscode-nls';
|
||||||
|
const localize = nls.loadMessageBundle();
|
||||||
|
|
||||||
|
import { azureResource } from '../../azure-resource';
|
||||||
|
import { IAzureResourceDatabaseService, IAzureResourceDatabaseNode } from './interfaces';
|
||||||
|
import { AzureResourceDatabase } from './models';
|
||||||
|
import { AzureResourceItemType } from '../../../azureResource/constants';
|
||||||
|
import { ApiWrapper } from '../../../apiWrapper';
|
||||||
|
|
||||||
|
export class AzureResourceDatabaseTreeDataProvider implements azureResource.IAzureResourceTreeDataProvider {
|
||||||
|
public constructor(
|
||||||
|
databaseService: IAzureResourceDatabaseService,
|
||||||
|
apiWrapper: ApiWrapper,
|
||||||
|
extensionContext: ExtensionContext
|
||||||
|
) {
|
||||||
|
this._databaseService = databaseService;
|
||||||
|
this._apiWrapper = apiWrapper;
|
||||||
|
this._extensionContext = extensionContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getTreeItem(element: azureResource.IAzureResourceNode): TreeItem | Thenable<TreeItem> {
|
||||||
|
return element.treeItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getChildren(element?: azureResource.IAzureResourceNode): Promise<azureResource.IAzureResourceNode[]> {
|
||||||
|
if (!element) {
|
||||||
|
return [this.createContainerNode()];
|
||||||
|
}
|
||||||
|
|
||||||
|
const tokens = await this._apiWrapper.getSecurityToken(element.account, AzureResource.ResourceManagement);
|
||||||
|
const credential = new TokenCredentials(tokens[element.tenantId].token, tokens[element.tenantId].tokenType);
|
||||||
|
|
||||||
|
const databases: AzureResourceDatabase[] = (await this._databaseService.getDatabases(element.subscription, credential)) || <AzureResourceDatabase[]>[];
|
||||||
|
|
||||||
|
return databases.map((database) => <IAzureResourceDatabaseNode>{
|
||||||
|
account: element.account,
|
||||||
|
subscription: element.subscription,
|
||||||
|
tenantId: element.tenantId,
|
||||||
|
database: database,
|
||||||
|
treeItem: {
|
||||||
|
id: `databaseServer_${database.serverFullName}.database_${database.name}`,
|
||||||
|
label: `${database.name} (${database.serverName})`,
|
||||||
|
iconPath: {
|
||||||
|
dark: this._extensionContext.asAbsolutePath('resources/dark/sql_database_inverse.svg'),
|
||||||
|
light: this._extensionContext.asAbsolutePath('resources/light/sql_database.svg')
|
||||||
|
},
|
||||||
|
collapsibleState: TreeItemCollapsibleState.None,
|
||||||
|
contextValue: AzureResourceItemType.database
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private createContainerNode(): azureResource.IAzureResourceNode {
|
||||||
|
return {
|
||||||
|
account: undefined,
|
||||||
|
subscription: undefined,
|
||||||
|
tenantId: undefined,
|
||||||
|
treeItem: {
|
||||||
|
id: AzureResourceDatabaseTreeDataProvider.containerId,
|
||||||
|
label: AzureResourceDatabaseTreeDataProvider.containerLabel,
|
||||||
|
iconPath: {
|
||||||
|
dark: this._extensionContext.asAbsolutePath('resources/dark/folder_inverse.svg'),
|
||||||
|
light: this._extensionContext.asAbsolutePath('resources/light/folder.svg')
|
||||||
|
},
|
||||||
|
collapsibleState: TreeItemCollapsibleState.Collapsed,
|
||||||
|
contextValue: AzureResourceItemType.databaseContainer
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private _databaseService: IAzureResourceDatabaseService = undefined;
|
||||||
|
private _apiWrapper: ApiWrapper = undefined;
|
||||||
|
private _extensionContext: ExtensionContext = undefined;
|
||||||
|
|
||||||
|
private static readonly containerId = 'azure.resource.providers.database.treeDataProvider.databaseContainer';
|
||||||
|
private static readonly containerLabel = localize('azure.resource.providers.database.treeDataProvider.databaseContainerLabel', 'SQL Databases');
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import { ServiceClientCredentials } from 'ms-rest';
|
||||||
|
|
||||||
|
import { azureResource } from '../../azure-resource';
|
||||||
|
import { AzureResourceDatabase } from './models';
|
||||||
|
|
||||||
|
export interface IAzureResourceDatabaseService {
|
||||||
|
getDatabases(subscription: azureResource.AzureResourceSubscription, credential: ServiceClientCredentials): Promise<AzureResourceDatabase[]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAzureResourceDatabaseNode extends azureResource.IAzureResourceNode {
|
||||||
|
readonly database: AzureResourceDatabase;
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
export interface AzureResourceDatabase {
|
||||||
|
name: string;
|
||||||
|
serverName: string;
|
||||||
|
serverFullName: string;
|
||||||
|
loginName: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import { IConnectionProfile } from 'sqlops';
|
||||||
|
import { AppContext } from '../../../appContext';
|
||||||
|
|
||||||
|
import { TreeNode } from '../../treeNode';
|
||||||
|
import { generateGuid } from '../../utils';
|
||||||
|
import { AzureResourceItemType } from '../../constants';
|
||||||
|
import { IAzureResourceDatabaseServerNode } from './interfaces';
|
||||||
|
import { AzureResourceResourceTreeNode } from '../../resourceTreeNode';
|
||||||
|
|
||||||
|
export function registerAzureResourceDatabaseServerCommands(appContext: AppContext): void {
|
||||||
|
appContext.apiWrapper.registerCommand('azure.resource.connectsqlserver', async (node?: TreeNode) => {
|
||||||
|
if (!node)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const treeItem = await node.getTreeItem();
|
||||||
|
if (treeItem.contextValue !== AzureResourceItemType.databaseServer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const resourceNode = (node as AzureResourceResourceTreeNode).resourceNodeWithProviderId.resourceNode;
|
||||||
|
const databaseServer = (resourceNode as IAzureResourceDatabaseServerNode).databaseServer;
|
||||||
|
|
||||||
|
let connectionProfile: IConnectionProfile = {
|
||||||
|
id: generateGuid(),
|
||||||
|
connectionName: undefined,
|
||||||
|
serverName: databaseServer.fullName,
|
||||||
|
databaseName: databaseServer.defaultDatabaseName,
|
||||||
|
userName: databaseServer.loginName,
|
||||||
|
password: '',
|
||||||
|
authenticationType: 'SqlLogin',
|
||||||
|
savePassword: true,
|
||||||
|
groupFullName: '',
|
||||||
|
groupId: '',
|
||||||
|
providerName: 'MSSQL',
|
||||||
|
saveProfile: true,
|
||||||
|
options: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
const conn = await appContext.apiWrapper.openConnectionDialog(undefined, connectionProfile, { saveConnection: true, showDashboard: true });
|
||||||
|
if (conn) {
|
||||||
|
appContext.apiWrapper.executeCommand('workbench.view.connections');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import { ExtensionContext } from 'vscode';
|
||||||
|
import { ApiWrapper } from '../../../apiWrapper';
|
||||||
|
|
||||||
|
import { azureResource } from '../../azure-resource';
|
||||||
|
import { IAzureResourceDatabaseServerService } from './interfaces';
|
||||||
|
import { AzureResourceDatabaseServerTreeDataProvider } from './databaseServerTreeDataProvider';
|
||||||
|
|
||||||
|
export class AzureResourceDatabaseServerProvider implements azureResource.IAzureResourceProvider {
|
||||||
|
public constructor(
|
||||||
|
databaseServerService: IAzureResourceDatabaseServerService,
|
||||||
|
apiWrapper: ApiWrapper,
|
||||||
|
extensionContext: ExtensionContext
|
||||||
|
) {
|
||||||
|
this._databaseServerService = databaseServerService;
|
||||||
|
this._apiWrapper = apiWrapper;
|
||||||
|
this._extensionContext = extensionContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getTreeDataProvider(): azureResource.IAzureResourceTreeDataProvider {
|
||||||
|
return new AzureResourceDatabaseServerTreeDataProvider(this._databaseServerService, this._apiWrapper, this._extensionContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
public get providerId(): string {
|
||||||
|
return 'azure.resource.providers.databaseServer';
|
||||||
|
}
|
||||||
|
|
||||||
|
private _databaseServerService: IAzureResourceDatabaseServerService = undefined;
|
||||||
|
private _apiWrapper: ApiWrapper = undefined;
|
||||||
|
private _extensionContext: ExtensionContext = undefined;
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import { ServiceClientCredentials } from 'ms-rest';
|
||||||
|
import { SqlManagementClient } from 'azure-arm-sql';
|
||||||
|
|
||||||
|
import { azureResource } from '../../azure-resource';
|
||||||
|
import { IAzureResourceDatabaseServerService } from './interfaces';
|
||||||
|
import { AzureResourceDatabaseServer } from './models';
|
||||||
|
|
||||||
|
export class AzureResourceDatabaseServerService implements IAzureResourceDatabaseServerService {
|
||||||
|
public async getDatabaseServers(subscription: azureResource.AzureResourceSubscription, credential: ServiceClientCredentials): Promise<AzureResourceDatabaseServer[]> {
|
||||||
|
const databaseServers: AzureResourceDatabaseServer[] = [];
|
||||||
|
|
||||||
|
const sqlManagementClient = new SqlManagementClient(credential, subscription.id);
|
||||||
|
const svrs = await sqlManagementClient.servers.list();
|
||||||
|
|
||||||
|
svrs.forEach((svr) => databaseServers.push({
|
||||||
|
name: svr.name,
|
||||||
|
fullName: svr.fullyQualifiedDomainName,
|
||||||
|
loginName: svr.administratorLogin,
|
||||||
|
defaultDatabaseName: 'master'
|
||||||
|
}));
|
||||||
|
|
||||||
|
return databaseServers;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import { AzureResource } from 'sqlops';
|
||||||
|
import { TreeItem, TreeItemCollapsibleState, ExtensionContext } from 'vscode';
|
||||||
|
import { TokenCredentials } from 'ms-rest';
|
||||||
|
import * as nls from 'vscode-nls';
|
||||||
|
const localize = nls.loadMessageBundle();
|
||||||
|
|
||||||
|
import { azureResource } from '../../azure-resource';
|
||||||
|
import { IAzureResourceDatabaseServerService, IAzureResourceDatabaseServerNode } from './interfaces';
|
||||||
|
import { AzureResourceDatabaseServer } from './models';
|
||||||
|
import { AzureResourceItemType } from '../../../azureResource/constants';
|
||||||
|
import { ApiWrapper } from '../../../apiWrapper';
|
||||||
|
|
||||||
|
export class AzureResourceDatabaseServerTreeDataProvider implements azureResource.IAzureResourceTreeDataProvider {
|
||||||
|
public constructor(
|
||||||
|
databaseServerService: IAzureResourceDatabaseServerService,
|
||||||
|
apiWrapper: ApiWrapper,
|
||||||
|
extensionContext: ExtensionContext
|
||||||
|
) {
|
||||||
|
this._databaseServerService = databaseServerService;
|
||||||
|
this._apiWrapper = apiWrapper;
|
||||||
|
this._extensionContext = extensionContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getTreeItem(element: azureResource.IAzureResourceNode): TreeItem | Thenable<TreeItem> {
|
||||||
|
return element.treeItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getChildren(element?: azureResource.IAzureResourceNode): Promise<azureResource.IAzureResourceNode[]> {
|
||||||
|
if (!element) {
|
||||||
|
return [this.createContainerNode()];
|
||||||
|
}
|
||||||
|
|
||||||
|
const tokens = await this._apiWrapper.getSecurityToken(element.account, AzureResource.ResourceManagement);
|
||||||
|
const credential = new TokenCredentials(tokens[element.tenantId].token, tokens[element.tenantId].tokenType);
|
||||||
|
|
||||||
|
const databaseServers: AzureResourceDatabaseServer[] = (await this._databaseServerService.getDatabaseServers(element.subscription, credential)) || <AzureResourceDatabaseServer[]>[];
|
||||||
|
|
||||||
|
return databaseServers.map((databaseServer) => <IAzureResourceDatabaseServerNode>{
|
||||||
|
account: element.account,
|
||||||
|
subscription: element.subscription,
|
||||||
|
tenantId: element.tenantId,
|
||||||
|
databaseServer: databaseServer,
|
||||||
|
treeItem: {
|
||||||
|
id: `databaseServer_${databaseServer.name}`,
|
||||||
|
label: databaseServer.name,
|
||||||
|
iconPath: {
|
||||||
|
dark: this._extensionContext.asAbsolutePath('resources/dark/sql_server_inverse.svg'),
|
||||||
|
light: this._extensionContext.asAbsolutePath('resources/light/sql_server.svg')
|
||||||
|
},
|
||||||
|
collapsibleState: TreeItemCollapsibleState.None,
|
||||||
|
contextValue: AzureResourceItemType.databaseServer
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private createContainerNode(): azureResource.IAzureResourceNode {
|
||||||
|
return {
|
||||||
|
account: undefined,
|
||||||
|
subscription: undefined,
|
||||||
|
tenantId: undefined,
|
||||||
|
treeItem: {
|
||||||
|
id: AzureResourceDatabaseServerTreeDataProvider.containerId,
|
||||||
|
label: AzureResourceDatabaseServerTreeDataProvider.containerLabel,
|
||||||
|
iconPath: {
|
||||||
|
dark: this._extensionContext.asAbsolutePath('resources/dark/folder_inverse.svg'),
|
||||||
|
light: this._extensionContext.asAbsolutePath('resources/light/folder.svg')
|
||||||
|
},
|
||||||
|
collapsibleState: TreeItemCollapsibleState.Collapsed,
|
||||||
|
contextValue: AzureResourceItemType.databaseServerContainer
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private _databaseServerService: IAzureResourceDatabaseServerService = undefined;
|
||||||
|
private _apiWrapper: ApiWrapper = undefined;
|
||||||
|
private _extensionContext: ExtensionContext = undefined;
|
||||||
|
|
||||||
|
private static readonly containerId = 'azure.resource.providers.databaseServer.treeDataProvider.databaseServerContainer';
|
||||||
|
private static readonly containerLabel = localize('azure.resource.providers.databaseServer.treeDataProvider.databaseServerContainerLabel', 'SQL Servers');
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import { ServiceClientCredentials } from 'ms-rest';
|
||||||
|
|
||||||
|
import { azureResource } from '../../azure-resource';
|
||||||
|
import { AzureResourceDatabaseServer } from './models';
|
||||||
|
|
||||||
|
export interface IAzureResourceDatabaseServerService {
|
||||||
|
getDatabaseServers(subscription: azureResource.AzureResourceSubscription, credentials: ServiceClientCredentials): Promise<AzureResourceDatabaseServer[]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAzureResourceDatabaseServerNode extends azureResource.IAzureResourceNode {
|
||||||
|
readonly databaseServer: AzureResourceDatabaseServer;
|
||||||
|
}
|
||||||
@@ -5,21 +5,9 @@
|
|||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
export interface AzureResourceSubscription {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AzureResourceDatabaseServer {
|
export interface AzureResourceDatabaseServer {
|
||||||
name: string;
|
name: string;
|
||||||
fullName: string;
|
fullName: string;
|
||||||
loginName: string;
|
loginName: string;
|
||||||
defaultDatabaseName: string;
|
defaultDatabaseName: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AzureResourceDatabase {
|
|
||||||
name: string;
|
|
||||||
serverName: string;
|
|
||||||
serverFullName: string;
|
|
||||||
loginName: string;
|
|
||||||
}
|
|
||||||
129
extensions/azurecore/src/azureResource/resourceService.ts
Normal file
129
extensions/azurecore/src/azureResource/resourceService.ts
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import { extensions, TreeItem } from 'vscode';
|
||||||
|
import { Account } from 'sqlops';
|
||||||
|
|
||||||
|
import { azureResource } from './azure-resource';
|
||||||
|
import { IAzureResourceNodeWithProviderId } from './interfaces';
|
||||||
|
|
||||||
|
export class AzureResourceService {
|
||||||
|
private constructor() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static getInstance(): AzureResourceService {
|
||||||
|
return AzureResourceService._instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async listResourceProviderIds(): Promise<string[]> {
|
||||||
|
await this.ensureResourceProvidersRegistered();
|
||||||
|
|
||||||
|
return Object.keys(this._resourceProviders);
|
||||||
|
}
|
||||||
|
|
||||||
|
public registerResourceProvider(resourceProvider: azureResource.IAzureResourceProvider): void {
|
||||||
|
this.doRegisterResourceProvider(resourceProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
public clearResourceProviders(): void {
|
||||||
|
this._resourceProviders = {};
|
||||||
|
this._treeDataProviders = {};
|
||||||
|
this._areResourceProvidersLoaded = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getRootChildren(resourceProviderId: string, account: Account, subscription: azureResource.AzureResourceSubscription, tenatId: string): Promise<IAzureResourceNodeWithProviderId[]> {
|
||||||
|
await this.ensureResourceProvidersRegistered();
|
||||||
|
|
||||||
|
if (!(resourceProviderId in this._resourceProviders)) {
|
||||||
|
throw new Error(`Azure resource provider doesn't exist. Id: ${resourceProviderId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const treeDataProvider = this._treeDataProviders[resourceProviderId];
|
||||||
|
const children = await treeDataProvider.getChildren();
|
||||||
|
|
||||||
|
return children.map((child) => <IAzureResourceNodeWithProviderId>{
|
||||||
|
resourceProviderId: resourceProviderId,
|
||||||
|
resourceNode: <azureResource.IAzureResourceNode>{
|
||||||
|
account: account,
|
||||||
|
subscription: subscription,
|
||||||
|
tenantId: tenatId,
|
||||||
|
treeItem: child.treeItem
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getChildren(resourceProviderId: string, element: azureResource.IAzureResourceNode): Promise<IAzureResourceNodeWithProviderId[]> {
|
||||||
|
await this.ensureResourceProvidersRegistered();
|
||||||
|
|
||||||
|
if (!(resourceProviderId in this._resourceProviders)) {
|
||||||
|
throw new Error(`Azure resource provider doesn't exist. Id: ${resourceProviderId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const treeDataProvider = this._treeDataProviders[resourceProviderId];
|
||||||
|
const children = await treeDataProvider.getChildren(element);
|
||||||
|
|
||||||
|
return children.map((child) => <IAzureResourceNodeWithProviderId>{
|
||||||
|
resourceProviderId: resourceProviderId,
|
||||||
|
resourceNode: child
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getTreeItem(resourceProviderId: string, element?: azureResource.IAzureResourceNode): Promise<TreeItem> {
|
||||||
|
await this.ensureResourceProvidersRegistered();
|
||||||
|
|
||||||
|
if (!(resourceProviderId in this._resourceProviders)) {
|
||||||
|
throw new Error(`Azure resource provider doesn't exist. Id: ${resourceProviderId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const treeDataProvider = this._treeDataProviders[resourceProviderId];
|
||||||
|
return treeDataProvider.getTreeItem(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
public get areResourceProvidersLoaded(): boolean {
|
||||||
|
return this._areResourceProvidersLoaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
public set areResourceProvidersLoaded(value: boolean) {
|
||||||
|
this._areResourceProvidersLoaded = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async ensureResourceProvidersRegistered(): Promise<void> {
|
||||||
|
if (this._areResourceProvidersLoaded) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const extension of extensions.all) {
|
||||||
|
const contributes = extension.packageJSON && extension.packageJSON.contributes;
|
||||||
|
if (!contributes) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contributes['hasAzureResourceProviders']) {
|
||||||
|
await extension.activate();
|
||||||
|
|
||||||
|
if (extension.exports && extension.exports.provideResources) {
|
||||||
|
for (const resourceProvider of <azureResource.IAzureResourceProvider[]>extension.exports.provideResources()) {
|
||||||
|
this.doRegisterResourceProvider(resourceProvider);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this._areResourceProvidersLoaded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private doRegisterResourceProvider(resourceProvider: azureResource.IAzureResourceProvider): void {
|
||||||
|
this._resourceProviders[resourceProvider.providerId] = resourceProvider;
|
||||||
|
this._treeDataProviders[resourceProvider.providerId] = resourceProvider.getTreeDataProvider();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _areResourceProvidersLoaded: boolean = false;
|
||||||
|
private _resourceProviders: { [resourceProviderId: string]: azureResource.IAzureResourceProvider } = {};
|
||||||
|
private _treeDataProviders: { [resourceProviderId: string]: azureResource.IAzureResourceTreeDataProvider } = {};
|
||||||
|
|
||||||
|
private static readonly _instance = new AzureResourceService();
|
||||||
|
}
|
||||||
79
extensions/azurecore/src/azureResource/resourceTreeNode.ts
Normal file
79
extensions/azurecore/src/azureResource/resourceTreeNode.ts
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import { NodeInfo } from 'sqlops';
|
||||||
|
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
|
||||||
|
import * as nls from 'vscode-nls';
|
||||||
|
const localize = nls.loadMessageBundle();
|
||||||
|
|
||||||
|
import { TreeNode } from './treeNode';
|
||||||
|
import { AzureResourceService } from './resourceService';
|
||||||
|
import { IAzureResourceNodeWithProviderId } from './interfaces';
|
||||||
|
import { AzureResourceMessageTreeNode } from './messageTreeNode';
|
||||||
|
import { AzureResourceErrorMessageUtil } from './utils';
|
||||||
|
|
||||||
|
export class AzureResourceResourceTreeNode extends TreeNode {
|
||||||
|
public constructor(
|
||||||
|
public readonly resourceNodeWithProviderId: IAzureResourceNodeWithProviderId,
|
||||||
|
parent: TreeNode
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.parent = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getChildren(): Promise<TreeNode[]> {
|
||||||
|
// It is a leaf node.
|
||||||
|
if (this.resourceNodeWithProviderId.resourceNode.treeItem.collapsibleState === TreeItemCollapsibleState.None) {
|
||||||
|
return <TreeNode[]>[];
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const children = await this._resourceService.getChildren(this.resourceNodeWithProviderId.resourceProviderId, this.resourceNodeWithProviderId.resourceNode);
|
||||||
|
|
||||||
|
if (children.length === 0) {
|
||||||
|
return [AzureResourceMessageTreeNode.create(AzureResourceResourceTreeNode.noResourcesLabel, this)];
|
||||||
|
} else {
|
||||||
|
return children.map((child) => {
|
||||||
|
// To make tree node's id unique, otherwise, treeModel.js would complain 'item already registered'
|
||||||
|
child.resourceNode.treeItem.id = `${this.resourceNodeWithProviderId.resourceNode.treeItem.id}.${child.resourceNode.treeItem.id}`;
|
||||||
|
return new AzureResourceResourceTreeNode(child, this);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return [AzureResourceMessageTreeNode.create(AzureResourceErrorMessageUtil.getErrorMessage(error), this)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public getTreeItem(): TreeItem | Promise<TreeItem> {
|
||||||
|
return this._resourceService.getTreeItem(this.resourceNodeWithProviderId.resourceProviderId, this.resourceNodeWithProviderId.resourceNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getNodeInfo(): NodeInfo {
|
||||||
|
const treeItem = this.resourceNodeWithProviderId.resourceNode.treeItem;
|
||||||
|
|
||||||
|
return {
|
||||||
|
label: treeItem.label,
|
||||||
|
isLeaf: treeItem.collapsibleState === TreeItemCollapsibleState.None ? true : false,
|
||||||
|
errorMessage: undefined,
|
||||||
|
metadata: undefined,
|
||||||
|
nodePath: this.generateNodePath(),
|
||||||
|
nodeStatus: undefined,
|
||||||
|
nodeType: treeItem.contextValue,
|
||||||
|
nodeSubType: undefined,
|
||||||
|
iconType: treeItem.contextValue
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public get nodePathValue(): string {
|
||||||
|
return this.resourceNodeWithProviderId.resourceNode.treeItem.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _resourceService = AzureResourceService.getInstance();
|
||||||
|
|
||||||
|
private static readonly noResourcesLabel = localize('azure.resource.resourceTreeNode.noResourcesLabel', 'No Resources found.');
|
||||||
|
}
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
/*---------------------------------------------------------------------------------------------
|
|
||||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
|
||||||
*--------------------------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
import {
|
|
||||||
IAzureResourceAccountService,
|
|
||||||
IAzureResourceCredentialService,
|
|
||||||
IAzureResourceSubscriptionService,
|
|
||||||
IAzureResourceSubscriptionFilterService,
|
|
||||||
IAzureResourceDatabaseService,
|
|
||||||
IAzureResourceDatabaseServerService,
|
|
||||||
IAzureResourceCacheService,
|
|
||||||
IAzureResourceContextService } from './interfaces';
|
|
||||||
|
|
||||||
export class AzureResourceServicePool {
|
|
||||||
private constructor() { }
|
|
||||||
|
|
||||||
public static getInstance(): AzureResourceServicePool {
|
|
||||||
return AzureResourceServicePool._instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public contextService: IAzureResourceContextService;
|
|
||||||
public cacheService: IAzureResourceCacheService;
|
|
||||||
public accountService: IAzureResourceAccountService;
|
|
||||||
public credentialService: IAzureResourceCredentialService;
|
|
||||||
public subscriptionService: IAzureResourceSubscriptionService;
|
|
||||||
public subscriptionFilterService: IAzureResourceSubscriptionFilterService;
|
|
||||||
public databaseService: IAzureResourceDatabaseService;
|
|
||||||
public databaseServerService: IAzureResourceDatabaseServerService;
|
|
||||||
|
|
||||||
private static readonly _instance = new AzureResourceServicePool();
|
|
||||||
}
|
|
||||||
@@ -5,21 +5,30 @@
|
|||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import { ExtensionContext } from "vscode";
|
import { ExtensionContext } from 'vscode';
|
||||||
|
|
||||||
import { IAzureResourceCacheService } from "../interfaces";
|
import { IAzureResourceCacheService } from '../interfaces';
|
||||||
|
|
||||||
export class AzureResourceCacheService implements IAzureResourceCacheService {
|
export class AzureResourceCacheService implements IAzureResourceCacheService {
|
||||||
public constructor(
|
public constructor(
|
||||||
public readonly context: ExtensionContext
|
context: ExtensionContext
|
||||||
) {
|
) {
|
||||||
|
this._context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public generateKey(id: string): string {
|
||||||
|
return `${AzureResourceCacheService.cacheKeyPrefix}.${id}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get<T>(key: string): T | undefined {
|
public get<T>(key: string): T | undefined {
|
||||||
return this.context.workspaceState.get(key);
|
return this._context.workspaceState.get(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
public update<T>(key: string, value: T): void {
|
public update<T>(key: string, value: T): void {
|
||||||
this.context.workspaceState.update(key, value);
|
this._context.workspaceState.update(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _context: ExtensionContext = undefined;
|
||||||
|
|
||||||
|
private static readonly cacheKeyPrefix = 'azure.resource.cache';
|
||||||
}
|
}
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
/*---------------------------------------------------------------------------------------------
|
|
||||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
|
||||||
*--------------------------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
import { ExtensionContext } from "vscode";
|
|
||||||
import { ApiWrapper } from "../../apiWrapper";
|
|
||||||
|
|
||||||
import { IAzureResourceContextService } from "../interfaces";
|
|
||||||
|
|
||||||
export class AzureResourceContextService implements IAzureResourceContextService {
|
|
||||||
public constructor(
|
|
||||||
context: ExtensionContext,
|
|
||||||
apiWrapper: ApiWrapper
|
|
||||||
) {
|
|
||||||
this._context = context;
|
|
||||||
this._apiWrapper = apiWrapper;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getAbsolutePath(relativePath: string): string {
|
|
||||||
return this._context.asAbsolutePath(relativePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
public executeCommand(commandId: string, ...args: any[]): void {
|
|
||||||
this._apiWrapper.executeCommand(commandId, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
public showErrorMessage(errorMessage: string): void {
|
|
||||||
this._apiWrapper.showErrorMessage(errorMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _context: ExtensionContext = undefined;
|
|
||||||
private _apiWrapper: ApiWrapper = undefined;
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
/*---------------------------------------------------------------------------------------------
|
|
||||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
|
||||||
*--------------------------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
import { Account } from 'sqlops';
|
|
||||||
import { TokenCredentials, ServiceClientCredentials } from 'ms-rest';
|
|
||||||
import { ApiWrapper } from '../../apiWrapper';
|
|
||||||
import * as nls from 'vscode-nls';
|
|
||||||
const localize = nls.loadMessageBundle();
|
|
||||||
|
|
||||||
import { IAzureResourceCredentialService } from '../interfaces';
|
|
||||||
import { AzureResourceCredentialError } from '../errors';
|
|
||||||
|
|
||||||
export class AzureResourceCredentialService implements IAzureResourceCredentialService {
|
|
||||||
public constructor(
|
|
||||||
apiWrapper: ApiWrapper
|
|
||||||
) {
|
|
||||||
this._apiWrapper = apiWrapper;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getCredentials(account: Account): Promise<ServiceClientCredentials[]> {
|
|
||||||
try {
|
|
||||||
let credentials: TokenCredentials[] = [];
|
|
||||||
let tokens = await this._apiWrapper.getSecurityToken(account);
|
|
||||||
|
|
||||||
for (let tenant of account.properties.tenants) {
|
|
||||||
let token = tokens[tenant.id].token;
|
|
||||||
let tokenType = tokens[tenant.id].tokenType;
|
|
||||||
|
|
||||||
credentials.push(new TokenCredentials(token, tokenType));
|
|
||||||
}
|
|
||||||
|
|
||||||
return credentials;
|
|
||||||
} catch (error) {
|
|
||||||
throw new AzureResourceCredentialError(localize('azureResource.services.credentialService.credentialError', 'Failed to get credential for account {0}. Please refresh the account.', account.key.accountId), error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _apiWrapper: ApiWrapper = undefined;
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
/*---------------------------------------------------------------------------------------------
|
|
||||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
|
||||||
*--------------------------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
import { ServiceClientCredentials } from 'ms-rest';
|
|
||||||
import { SqlManagementClient } from 'azure-arm-sql';
|
|
||||||
|
|
||||||
import { IAzureResourceDatabaseServerService } from '../interfaces';
|
|
||||||
import { AzureResourceSubscription, AzureResourceDatabaseServer } from '../models';
|
|
||||||
|
|
||||||
export class AzureResourceDatabaseServerService implements IAzureResourceDatabaseServerService {
|
|
||||||
public async getDatabaseServers(subscription: AzureResourceSubscription, credentials: ServiceClientCredentials[]): Promise<AzureResourceDatabaseServer[]> {
|
|
||||||
let databaseServers: AzureResourceDatabaseServer[] = [];
|
|
||||||
for (let cred of credentials) {
|
|
||||||
let sqlManagementClient = new SqlManagementClient(cred, subscription.id);
|
|
||||||
try {
|
|
||||||
let svrs = await sqlManagementClient.servers.list();
|
|
||||||
svrs.forEach((svr) => databaseServers.push({
|
|
||||||
name: svr.name,
|
|
||||||
fullName: svr.fullyQualifiedDomainName,
|
|
||||||
loginName: svr.administratorLogin,
|
|
||||||
defaultDatabaseName: 'master'
|
|
||||||
}));
|
|
||||||
} catch (error) {
|
|
||||||
if (error.code === 'InvalidAuthenticationTokenTenant' && error.statusCode === 401) {
|
|
||||||
/**
|
|
||||||
* There may be multiple tenants for an account and it may throw exceptions like following. Just swallow the exception here.
|
|
||||||
* The access token is from the wrong issuer. It must match one of the tenants associated with this subscription.
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return databaseServers;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
/*---------------------------------------------------------------------------------------------
|
|
||||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
|
||||||
*--------------------------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
import { ServiceClientCredentials } from 'ms-rest';
|
|
||||||
import { SqlManagementClient } from 'azure-arm-sql';
|
|
||||||
|
|
||||||
import { IAzureResourceDatabaseService } from '../interfaces';
|
|
||||||
import { AzureResourceSubscription, AzureResourceDatabase } from '../models';
|
|
||||||
|
|
||||||
export class AzureResourceDatabaseService implements IAzureResourceDatabaseService {
|
|
||||||
public async getDatabases(subscription: AzureResourceSubscription, credentials: ServiceClientCredentials[]): Promise<AzureResourceDatabase[]> {
|
|
||||||
let databases: AzureResourceDatabase[] = [];
|
|
||||||
for (let cred of credentials) {
|
|
||||||
let sqlManagementClient = new SqlManagementClient(cred, subscription.id);
|
|
||||||
try {
|
|
||||||
let svrs = await sqlManagementClient.servers.list();
|
|
||||||
for (let svr of svrs) {
|
|
||||||
// Extract resource group name from svr.id
|
|
||||||
let svrIdRegExp = new RegExp(`\/subscriptions\/${subscription.id}\/resourceGroups\/(.+)\/providers\/Microsoft\.Sql\/servers\/${svr.name}`);
|
|
||||||
if (!svrIdRegExp.test(svr.id)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let founds = svrIdRegExp.exec(svr.id);
|
|
||||||
let resouceGroup = founds[1];
|
|
||||||
|
|
||||||
let dbs = await sqlManagementClient.databases.listByServer(resouceGroup, svr.name);
|
|
||||||
dbs.forEach((db) => databases.push({
|
|
||||||
name: db.name,
|
|
||||||
serverName: svr.name,
|
|
||||||
serverFullName: svr.fullyQualifiedDomainName,
|
|
||||||
loginName: svr.administratorLogin
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
if (error.code === 'InvalidAuthenticationTokenTenant' && error.statusCode === 401) {
|
|
||||||
/**
|
|
||||||
* There may be multiple tenants for an account and it may throw exceptions like following. Just swallow the exception here.
|
|
||||||
* The access token is from the wrong issuer. It must match one of the tenants associated with this subscription.
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return databases;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -8,11 +8,11 @@
|
|||||||
import { WorkspaceConfiguration, ConfigurationTarget } from 'vscode';
|
import { WorkspaceConfiguration, ConfigurationTarget } from 'vscode';
|
||||||
import { Account } from 'sqlops';
|
import { Account } from 'sqlops';
|
||||||
|
|
||||||
|
import { azureResource } from '../azure-resource';
|
||||||
import { IAzureResourceSubscriptionFilterService, IAzureResourceCacheService } from '../interfaces';
|
import { IAzureResourceSubscriptionFilterService, IAzureResourceCacheService } from '../interfaces';
|
||||||
import { AzureResourceSubscription } from '../models';
|
|
||||||
|
|
||||||
interface AzureResourceSelectedSubscriptionsCache {
|
interface AzureResourceSelectedSubscriptionsCache {
|
||||||
selectedSubscriptions: { [accountId: string]: AzureResourceSubscription[]};
|
selectedSubscriptions: { [accountId: string]: azureResource.AzureResourceSubscription[]};
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AzureResourceSubscriptionFilterService implements IAzureResourceSubscriptionFilterService {
|
export class AzureResourceSubscriptionFilterService implements IAzureResourceSubscriptionFilterService {
|
||||||
@@ -20,12 +20,14 @@ export class AzureResourceSubscriptionFilterService implements IAzureResourceSub
|
|||||||
cacheService: IAzureResourceCacheService
|
cacheService: IAzureResourceCacheService
|
||||||
) {
|
) {
|
||||||
this._cacheService = cacheService;
|
this._cacheService = cacheService;
|
||||||
|
|
||||||
|
this._cacheKey = this._cacheService.generateKey('selectedSubscriptions');
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getSelectedSubscriptions(account: Account): Promise<AzureResourceSubscription[]> {
|
public async getSelectedSubscriptions(account: Account): Promise<azureResource.AzureResourceSubscription[]> {
|
||||||
let selectedSubscriptions: AzureResourceSubscription[] = [];
|
let selectedSubscriptions: azureResource.AzureResourceSubscription[] = [];
|
||||||
|
|
||||||
const cache = this._cacheService.get<AzureResourceSelectedSubscriptionsCache>(AzureResourceSubscriptionFilterService.CacheKey);
|
const cache = this._cacheService.get<AzureResourceSelectedSubscriptionsCache>(this._cacheKey);
|
||||||
if (cache) {
|
if (cache) {
|
||||||
selectedSubscriptions = cache.selectedSubscriptions[account.key.accountId];
|
selectedSubscriptions = cache.selectedSubscriptions[account.key.accountId];
|
||||||
}
|
}
|
||||||
@@ -33,10 +35,10 @@ export class AzureResourceSubscriptionFilterService implements IAzureResourceSub
|
|||||||
return selectedSubscriptions;
|
return selectedSubscriptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async saveSelectedSubscriptions(account: Account, selectedSubscriptions: AzureResourceSubscription[]): Promise<void> {
|
public async saveSelectedSubscriptions(account: Account, selectedSubscriptions: azureResource.AzureResourceSubscription[]): Promise<void> {
|
||||||
let selectedSubscriptionsCache: { [accountId: string]: AzureResourceSubscription[]} = {};
|
let selectedSubscriptionsCache: { [accountId: string]: azureResource.AzureResourceSubscription[]} = {};
|
||||||
|
|
||||||
const cache = this._cacheService.get<AzureResourceSelectedSubscriptionsCache>(AzureResourceSubscriptionFilterService.CacheKey);
|
const cache = this._cacheService.get<AzureResourceSelectedSubscriptionsCache>(this._cacheKey);
|
||||||
if (cache) {
|
if (cache) {
|
||||||
selectedSubscriptionsCache = cache.selectedSubscriptions;
|
selectedSubscriptionsCache = cache.selectedSubscriptions;
|
||||||
}
|
}
|
||||||
@@ -47,14 +49,14 @@ export class AzureResourceSubscriptionFilterService implements IAzureResourceSub
|
|||||||
|
|
||||||
selectedSubscriptionsCache[account.key.accountId] = selectedSubscriptions;
|
selectedSubscriptionsCache[account.key.accountId] = selectedSubscriptions;
|
||||||
|
|
||||||
this._cacheService.update<AzureResourceSelectedSubscriptionsCache>(AzureResourceSubscriptionFilterService.CacheKey, { selectedSubscriptions: selectedSubscriptionsCache });
|
this._cacheService.update<AzureResourceSelectedSubscriptionsCache>(this._cacheKey, { selectedSubscriptions: selectedSubscriptionsCache });
|
||||||
|
|
||||||
const filters: string[] = [];
|
const filters: string[] = [];
|
||||||
for (const accountId in selectedSubscriptionsCache) {
|
for (const accountId in selectedSubscriptionsCache) {
|
||||||
filters.push(...selectedSubscriptionsCache[accountId].map((subcription) => `${accountId}/${subcription.id}/${subcription.name}`));
|
filters.push(...selectedSubscriptionsCache[accountId].map((subcription) => `${accountId}/${subcription.id}/${subcription.name}`));
|
||||||
}
|
}
|
||||||
|
|
||||||
const resourceFilterConfig = this._config.inspect<string[]>(AzureResourceSubscriptionFilterService.FilterConfigName);
|
const resourceFilterConfig = this._config.inspect<string[]>(AzureResourceSubscriptionFilterService.filterConfigName);
|
||||||
let configTarget = ConfigurationTarget.Global;
|
let configTarget = ConfigurationTarget.Global;
|
||||||
if (resourceFilterConfig) {
|
if (resourceFilterConfig) {
|
||||||
if (resourceFilterConfig.workspaceFolderValue) {
|
if (resourceFilterConfig.workspaceFolderValue) {
|
||||||
@@ -66,12 +68,12 @@ export class AzureResourceSubscriptionFilterService implements IAzureResourceSub
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await this._config.update(AzureResourceSubscriptionFilterService.FilterConfigName, filters, configTarget);
|
await this._config.update(AzureResourceSubscriptionFilterService.filterConfigName, filters, configTarget);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _config: WorkspaceConfiguration = undefined;
|
private _config: WorkspaceConfiguration = undefined;
|
||||||
private _cacheService: IAzureResourceCacheService = undefined;
|
private _cacheService: IAzureResourceCacheService = undefined;
|
||||||
|
private _cacheKey: string = undefined;
|
||||||
|
|
||||||
private static readonly FilterConfigName = 'resourceFilter';
|
private static readonly filterConfigName = 'azure.resource.config.filter';
|
||||||
private static readonly CacheKey = 'azureResource.cache.selectedSubscriptions';
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,24 +9,19 @@ import { Account } from 'sqlops';
|
|||||||
import { ServiceClientCredentials } from 'ms-rest';
|
import { ServiceClientCredentials } from 'ms-rest';
|
||||||
import { SubscriptionClient } from 'azure-arm-resource';
|
import { SubscriptionClient } from 'azure-arm-resource';
|
||||||
|
|
||||||
|
import { azureResource } from '../azure-resource';
|
||||||
import { IAzureResourceSubscriptionService } from '../interfaces';
|
import { IAzureResourceSubscriptionService } from '../interfaces';
|
||||||
import { AzureResourceSubscription } from '../models';
|
|
||||||
|
|
||||||
export class AzureResourceSubscriptionService implements IAzureResourceSubscriptionService {
|
export class AzureResourceSubscriptionService implements IAzureResourceSubscriptionService {
|
||||||
public async getSubscriptions(account: Account, credentials: ServiceClientCredentials[]): Promise<AzureResourceSubscription[]> {
|
public async getSubscriptions(account: Account, credential: ServiceClientCredentials): Promise<azureResource.AzureResourceSubscription[]> {
|
||||||
let subscriptions: AzureResourceSubscription[] = [];
|
const subscriptions: azureResource.AzureResourceSubscription[] = [];
|
||||||
for (let cred of credentials) {
|
|
||||||
let subClient = new SubscriptionClient.SubscriptionClient(cred);
|
const subClient = new SubscriptionClient.SubscriptionClient(credential);
|
||||||
try {
|
const subs = await subClient.subscriptions.list();
|
||||||
let subs = await subClient.subscriptions.list();
|
|
||||||
subs.forEach((sub) => subscriptions.push({
|
subs.forEach((sub) => subscriptions.push({
|
||||||
id: sub.subscriptionId,
|
id: sub.subscriptionId,
|
||||||
name: sub.displayName
|
name: sub.displayName
|
||||||
}));
|
}));
|
||||||
} catch (error) {
|
|
||||||
// Swallow the exception here.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return subscriptions;
|
return subscriptions;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import * as request from 'request';
|
||||||
|
|
||||||
|
import { azureResource } from '../azure-resource';
|
||||||
|
import { IAzureResourceTenantService } from '../interfaces';
|
||||||
|
|
||||||
|
export class AzureResourceTenantService implements IAzureResourceTenantService {
|
||||||
|
public async getTenantId(subscription: azureResource.AzureResourceSubscription): Promise<string> {
|
||||||
|
const requestPromisified = new Promise<string>((resolve, reject) => {
|
||||||
|
const url = `https://management.azure.com/subscriptions/${subscription.id}?api-version=2014-04-01`;
|
||||||
|
request(url, function (error, response, body) {
|
||||||
|
if (response.statusCode === 401) {
|
||||||
|
const tenantIdRegEx = /authorization_uri="https:\/\/login\.windows\.net\/([0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12})"/;
|
||||||
|
const teantIdString = response.headers['www-authenticate'];
|
||||||
|
if (tenantIdRegEx.test(teantIdString)) {
|
||||||
|
resolve(tenantIdRegEx.exec(teantIdString)[1]);
|
||||||
|
} else {
|
||||||
|
reject();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return await requestPromisified;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,10 +7,10 @@
|
|||||||
|
|
||||||
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
|
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
|
||||||
import { NodeInfo } from 'sqlops';
|
import { NodeInfo } from 'sqlops';
|
||||||
import { TreeNode } from '../../treeNodes';
|
|
||||||
import * as nls from 'vscode-nls';
|
import * as nls from 'vscode-nls';
|
||||||
const localize = nls.loadMessageBundle();
|
const localize = nls.loadMessageBundle();
|
||||||
|
|
||||||
|
import { TreeNode } from '../treeNode';
|
||||||
import { AzureResourceItemType } from '../constants';
|
import { AzureResourceItemType } from '../constants';
|
||||||
|
|
||||||
export class AzureResourceAccountNotSignedInTreeNode extends TreeNode {
|
export class AzureResourceAccountNotSignedInTreeNode extends TreeNode {
|
||||||
@@ -19,11 +19,11 @@ export class AzureResourceAccountNotSignedInTreeNode extends TreeNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public getTreeItem(): TreeItem | Promise<TreeItem> {
|
public getTreeItem(): TreeItem | Promise<TreeItem> {
|
||||||
let item = new TreeItem(AzureResourceAccountNotSignedInTreeNode.SignInLabel, TreeItemCollapsibleState.None);
|
let item = new TreeItem(AzureResourceAccountNotSignedInTreeNode.signInLabel, TreeItemCollapsibleState.None);
|
||||||
item.contextValue = AzureResourceItemType.message;
|
item.contextValue = AzureResourceItemType.message;
|
||||||
item.command = {
|
item.command = {
|
||||||
title: AzureResourceAccountNotSignedInTreeNode.SignInLabel,
|
title: AzureResourceAccountNotSignedInTreeNode.signInLabel,
|
||||||
command: 'azureresource.signin',
|
command: 'azure.resource.signin',
|
||||||
arguments: [this]
|
arguments: [this]
|
||||||
};
|
};
|
||||||
return item;
|
return item;
|
||||||
@@ -31,7 +31,7 @@ export class AzureResourceAccountNotSignedInTreeNode extends TreeNode {
|
|||||||
|
|
||||||
public getNodeInfo(): NodeInfo {
|
public getNodeInfo(): NodeInfo {
|
||||||
return {
|
return {
|
||||||
label: AzureResourceAccountNotSignedInTreeNode.SignInLabel,
|
label: AzureResourceAccountNotSignedInTreeNode.signInLabel,
|
||||||
isLeaf: true,
|
isLeaf: true,
|
||||||
errorMessage: undefined,
|
errorMessage: undefined,
|
||||||
metadata: undefined,
|
metadata: undefined,
|
||||||
@@ -47,5 +47,5 @@ export class AzureResourceAccountNotSignedInTreeNode extends TreeNode {
|
|||||||
return 'message_accountNotSignedIn';
|
return 'message_accountNotSignedIn';
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly SignInLabel = localize('azureResource.tree.accountNotSignedInTreeNode.signIn', 'Sign in to Azure ...');
|
private static readonly signInLabel = localize('azure.resource.tree.accountNotSignedInTreeNode.signInLabel', 'Sign in to Azure ...');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,44 +6,59 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
|
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
|
||||||
import { Account, NodeInfo } from 'sqlops';
|
import { Account, NodeInfo, AzureResource } from 'sqlops';
|
||||||
import { TreeNode } from '../../treeNodes';
|
import { TokenCredentials } from 'ms-rest';
|
||||||
|
import { AppContext } from '../../appContext';
|
||||||
import * as nls from 'vscode-nls';
|
import * as nls from 'vscode-nls';
|
||||||
const localize = nls.loadMessageBundle();
|
const localize = nls.loadMessageBundle();
|
||||||
|
|
||||||
|
import { azureResource } from '../azure-resource';
|
||||||
|
import { TreeNode } from '../treeNode';
|
||||||
|
import { AzureResourceCredentialError } from '../errors';
|
||||||
import { AzureResourceContainerTreeNodeBase } from './baseTreeNodes';
|
import { AzureResourceContainerTreeNodeBase } from './baseTreeNodes';
|
||||||
import { AzureResourceItemType } from '../constants';
|
import { AzureResourceItemType, AzureResourceServiceNames } from '../constants';
|
||||||
import { AzureResourceSubscriptionTreeNode } from './subscriptionTreeNode';
|
import { AzureResourceSubscriptionTreeNode } from './subscriptionTreeNode';
|
||||||
import { AzureResourceMessageTreeNode } from './messageTreeNode';
|
import { AzureResourceMessageTreeNode } from '../messageTreeNode';
|
||||||
import { AzureResourceErrorMessageUtil } from '../utils';
|
import { AzureResourceErrorMessageUtil } from '../utils';
|
||||||
import { AzureResourceSubscription } from '../models';
|
import { IAzureResourceTreeChangeHandler } from './treeChangeHandler';
|
||||||
import { IAzureResourceTreeChangeHandler } from './treeProvider';
|
import { IAzureResourceSubscriptionService, IAzureResourceSubscriptionFilterService, IAzureResourceTenantService } from '../../azureResource/interfaces';
|
||||||
|
|
||||||
export class AzureResourceAccountTreeNode extends AzureResourceContainerTreeNodeBase {
|
export class AzureResourceAccountTreeNode extends AzureResourceContainerTreeNodeBase {
|
||||||
public constructor(
|
public constructor(
|
||||||
account: Account,
|
public readonly account: Account,
|
||||||
|
appContext: AppContext,
|
||||||
treeChangeHandler: IAzureResourceTreeChangeHandler
|
treeChangeHandler: IAzureResourceTreeChangeHandler
|
||||||
) {
|
) {
|
||||||
super(account, treeChangeHandler, undefined);
|
super(appContext, treeChangeHandler, undefined);
|
||||||
|
|
||||||
|
this._subscriptionService = this.appContext.getService<IAzureResourceSubscriptionService>(AzureResourceServiceNames.subscriptionService);
|
||||||
|
this._subscriptionFilterService = this.appContext.getService<IAzureResourceSubscriptionFilterService>(AzureResourceServiceNames.subscriptionFilterService);
|
||||||
|
this._tenantService = this.appContext.getService<IAzureResourceTenantService>(AzureResourceServiceNames.tenantService);
|
||||||
|
|
||||||
this._id = `account_${this.account.key.accountId}`;
|
this._id = `account_${this.account.key.accountId}`;
|
||||||
|
this.setCacheKey(`${this._id}.subscriptions`);
|
||||||
this._label = this.generateLabel();
|
this._label = this.generateLabel();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getChildren(): Promise<TreeNode[]> {
|
public async getChildren(): Promise<TreeNode[]> {
|
||||||
try {
|
try {
|
||||||
let subscriptions: AzureResourceSubscription[] = [];
|
let subscriptions: azureResource.AzureResourceSubscription[] = [];
|
||||||
|
|
||||||
if (this._isClearingCache) {
|
if (this._isClearingCache) {
|
||||||
const credentials = await this.getCredentials();
|
try {
|
||||||
subscriptions = (await this.servicePool.subscriptionService.getSubscriptions(this.account, credentials)) || <AzureResourceSubscription[]>[];
|
const tokens = await this.appContext.apiWrapper.getSecurityToken(this.account, AzureResource.ResourceManagement);
|
||||||
|
|
||||||
let cache = this.getCache<AzureResourceSubscriptionsCache>();
|
for (const tenant of this.account.properties.tenants) {
|
||||||
if (!cache) {
|
const token = tokens[tenant.id].token;
|
||||||
cache = { subscriptions: { } };
|
const tokenType = tokens[tenant.id].tokenType;
|
||||||
|
|
||||||
|
subscriptions.push(...(await this._subscriptionService.getSubscriptions(this.account, new TokenCredentials(token, tokenType)) || <azureResource.AzureResourceSubscription[]>[]));
|
||||||
}
|
}
|
||||||
cache.subscriptions[this.account.key.accountId] = subscriptions;
|
} catch (error) {
|
||||||
this.updateCache<AzureResourceSubscriptionsCache>(cache);
|
throw new AzureResourceCredentialError(localize('azure.resource.tree.accountTreeNode.credentialError', 'Failed to get credential for account {0}. Please refresh the account.', this.account.key.accountId), error);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateCache<azureResource.AzureResourceSubscription[]>(subscriptions);
|
||||||
|
|
||||||
this._isClearingCache = false;
|
this._isClearingCache = false;
|
||||||
} else {
|
} else {
|
||||||
@@ -52,8 +67,8 @@ export class AzureResourceAccountTreeNode extends AzureResourceContainerTreeNode
|
|||||||
|
|
||||||
this._totalSubscriptionCount = subscriptions.length;
|
this._totalSubscriptionCount = subscriptions.length;
|
||||||
|
|
||||||
let selectedSubscriptions = await this.servicePool.subscriptionFilterService.getSelectedSubscriptions(this.account);
|
const selectedSubscriptions = await this._subscriptionFilterService.getSelectedSubscriptions(this.account);
|
||||||
let selectedSubscriptionIds = (selectedSubscriptions || <AzureResourceSubscription[]>[]).map((subscription) => subscription.id);
|
const selectedSubscriptionIds = (selectedSubscriptions || <azureResource.AzureResourceSubscription[]>[]).map((subscription) => subscription.id);
|
||||||
if (selectedSubscriptionIds.length > 0) {
|
if (selectedSubscriptionIds.length > 0) {
|
||||||
subscriptions = subscriptions.filter((subscription) => selectedSubscriptionIds.indexOf(subscription.id) !== -1);
|
subscriptions = subscriptions.filter((subscription) => selectedSubscriptionIds.indexOf(subscription.id) !== -1);
|
||||||
this._selectedSubscriptionCount = selectedSubscriptionIds.length;
|
this._selectedSubscriptionCount = selectedSubscriptionIds.length;
|
||||||
@@ -65,31 +80,33 @@ export class AzureResourceAccountTreeNode extends AzureResourceContainerTreeNode
|
|||||||
this.refreshLabel();
|
this.refreshLabel();
|
||||||
|
|
||||||
if (subscriptions.length === 0) {
|
if (subscriptions.length === 0) {
|
||||||
return [AzureResourceMessageTreeNode.create(AzureResourceAccountTreeNode.NoSubscriptions, this)];
|
return [AzureResourceMessageTreeNode.create(AzureResourceAccountTreeNode.noSubscriptionsLabel, this)];
|
||||||
} else {
|
} else {
|
||||||
return subscriptions.map((subscription) => new AzureResourceSubscriptionTreeNode(subscription, this.account, this.treeChangeHandler, this));
|
return await Promise.all(subscriptions.map(async (subscription) => {
|
||||||
|
const tenantId = await this._tenantService.getTenantId(subscription);
|
||||||
|
|
||||||
|
return new AzureResourceSubscriptionTreeNode(this.account, subscription, tenantId, this.appContext, this.treeChangeHandler, this);
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (error instanceof AzureResourceCredentialError) {
|
||||||
|
this.appContext.apiWrapper.executeCommand('azure.resource.signin');
|
||||||
|
}
|
||||||
return [AzureResourceMessageTreeNode.create(AzureResourceErrorMessageUtil.getErrorMessage(error), this)];
|
return [AzureResourceMessageTreeNode.create(AzureResourceErrorMessageUtil.getErrorMessage(error), this)];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getCachedSubscriptions(): Promise<AzureResourceSubscription[]> {
|
public async getCachedSubscriptions(): Promise<azureResource.AzureResourceSubscription[]> {
|
||||||
const subscriptions: AzureResourceSubscription[] = [];
|
return this.getCache<azureResource.AzureResourceSubscription[]>();
|
||||||
const cache = this.getCache<AzureResourceSubscriptionsCache>();
|
|
||||||
if (cache) {
|
|
||||||
subscriptions.push(...cache.subscriptions[this.account.key.accountId]);
|
|
||||||
}
|
|
||||||
return subscriptions;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public getTreeItem(): TreeItem | Promise<TreeItem> {
|
public getTreeItem(): TreeItem | Promise<TreeItem> {
|
||||||
let item = new TreeItem(this._label, TreeItemCollapsibleState.Collapsed);
|
const item = new TreeItem(this._label, TreeItemCollapsibleState.Collapsed);
|
||||||
item.id = this._id;
|
item.id = this._id;
|
||||||
item.contextValue = AzureResourceItemType.account;
|
item.contextValue = AzureResourceItemType.account;
|
||||||
item.iconPath = {
|
item.iconPath = {
|
||||||
dark: this.servicePool.contextService.getAbsolutePath('resources/dark/account_inverse.svg'),
|
dark: this.appContext.extensionContext.asAbsolutePath('resources/dark/account_inverse.svg'),
|
||||||
light: this.servicePool.contextService.getAbsolutePath('resources/light/account.svg')
|
light: this.appContext.extensionContext.asAbsolutePath('resources/light/account.svg')
|
||||||
};
|
};
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
@@ -128,10 +145,6 @@ export class AzureResourceAccountTreeNode extends AzureResourceContainerTreeNode
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected get cacheKey(): string {
|
|
||||||
return 'azureResource.cache.subscriptions';
|
|
||||||
}
|
|
||||||
|
|
||||||
private generateLabel(): string {
|
private generateLabel(): string {
|
||||||
let label = `${this.account.displayInfo.displayName} (${this.account.key.accountId})`;
|
let label = `${this.account.displayInfo.displayName} (${this.account.key.accountId})`;
|
||||||
|
|
||||||
@@ -142,14 +155,14 @@ export class AzureResourceAccountTreeNode extends AzureResourceContainerTreeNode
|
|||||||
return label;
|
return label;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _subscriptionService: IAzureResourceSubscriptionService = undefined;
|
||||||
|
private _subscriptionFilterService: IAzureResourceSubscriptionFilterService = undefined;
|
||||||
|
private _tenantService: IAzureResourceTenantService = undefined;
|
||||||
|
|
||||||
private _id: string = undefined;
|
private _id: string = undefined;
|
||||||
private _label: string = undefined;
|
private _label: string = undefined;
|
||||||
private _totalSubscriptionCount = 0;
|
private _totalSubscriptionCount = 0;
|
||||||
private _selectedSubscriptionCount = 0;
|
private _selectedSubscriptionCount = 0;
|
||||||
|
|
||||||
private static readonly NoSubscriptions = localize('azureResource.tree.accountTreeNode.noSubscriptions', 'No Subscriptions found.');
|
private static readonly noSubscriptionsLabel = localize('azure.resource.tree.accountTreeNode.noSubscriptionsLabel', 'No Subscriptions found.');
|
||||||
}
|
|
||||||
|
|
||||||
interface AzureResourceSubscriptionsCache {
|
|
||||||
subscriptions: { [accountId: string]: AzureResourceSubscription[] };
|
|
||||||
}
|
}
|
||||||
@@ -5,16 +5,16 @@
|
|||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import { Account } from 'sqlops';
|
import { AppContext } from '../../appContext';
|
||||||
import { ServiceClientCredentials } from 'ms-rest';
|
|
||||||
import { TreeNode } from '../../treeNodes';
|
|
||||||
|
|
||||||
import { AzureResourceServicePool } from '../servicePool';
|
import { TreeNode } from '../treeNode';
|
||||||
import { AzureResourceCredentialError } from '../errors';
|
|
||||||
import { IAzureResourceTreeChangeHandler } from './treeChangeHandler';
|
import { IAzureResourceTreeChangeHandler } from './treeChangeHandler';
|
||||||
|
import { IAzureResourceCacheService } from '../../azureResource/interfaces';
|
||||||
|
import { AzureResourceServiceNames } from '../constants';
|
||||||
|
|
||||||
export abstract class AzureResourceTreeNodeBase extends TreeNode {
|
export abstract class AzureResourceTreeNodeBase extends TreeNode {
|
||||||
public constructor(
|
public constructor(
|
||||||
|
public readonly appContext: AppContext,
|
||||||
public readonly treeChangeHandler: IAzureResourceTreeChangeHandler,
|
public readonly treeChangeHandler: IAzureResourceTreeChangeHandler,
|
||||||
parent: TreeNode
|
parent: TreeNode
|
||||||
) {
|
) {
|
||||||
@@ -22,17 +22,17 @@ export abstract class AzureResourceTreeNodeBase extends TreeNode {
|
|||||||
|
|
||||||
this.parent = parent;
|
this.parent = parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly servicePool = AzureResourceServicePool.getInstance();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class AzureResourceContainerTreeNodeBase extends AzureResourceTreeNodeBase {
|
export abstract class AzureResourceContainerTreeNodeBase extends AzureResourceTreeNodeBase {
|
||||||
public constructor(
|
public constructor(
|
||||||
public readonly account: Account,
|
appContext: AppContext,
|
||||||
treeChangeHandler: IAzureResourceTreeChangeHandler,
|
treeChangeHandler: IAzureResourceTreeChangeHandler,
|
||||||
parent: TreeNode
|
parent: TreeNode
|
||||||
) {
|
) {
|
||||||
super(treeChangeHandler, parent);
|
super(appContext, treeChangeHandler, parent);
|
||||||
|
|
||||||
|
this._cacheService = this.appContext.getService<IAzureResourceCacheService>(AzureResourceServiceNames.cacheService);
|
||||||
}
|
}
|
||||||
|
|
||||||
public clearCache(): void {
|
public clearCache(): void {
|
||||||
@@ -43,29 +43,19 @@ export abstract class AzureResourceContainerTreeNodeBase extends AzureResourceTr
|
|||||||
return this._isClearingCache;
|
return this._isClearingCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async getCredentials(): Promise<ServiceClientCredentials[]> {
|
protected setCacheKey(id: string): void {
|
||||||
try {
|
this._cacheKey = this._cacheService.generateKey(id);
|
||||||
return await this.servicePool.credentialService.getCredentials(this.account);
|
|
||||||
} catch (error) {
|
|
||||||
if (error instanceof AzureResourceCredentialError) {
|
|
||||||
this.servicePool.contextService.showErrorMessage(error.message);
|
|
||||||
|
|
||||||
this.servicePool.contextService.executeCommand('azureresource.signin');
|
|
||||||
} else {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected updateCache<T>(cache: T): void {
|
protected updateCache<T>(cache: T): void {
|
||||||
this.servicePool.cacheService.update<T>(this.cacheKey, cache);
|
this._cacheService.update<T>(this._cacheKey, cache);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getCache<T>(): T {
|
protected getCache<T>(): T {
|
||||||
return this.servicePool.cacheService.get<T>(this.cacheKey);
|
return this._cacheService.get<T>(this._cacheKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract get cacheKey(): string;
|
|
||||||
|
|
||||||
protected _isClearingCache = true;
|
protected _isClearingCache = true;
|
||||||
|
private _cacheService: IAzureResourceCacheService = undefined;
|
||||||
|
private _cacheKey: string = undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,103 +0,0 @@
|
|||||||
/*---------------------------------------------------------------------------------------------
|
|
||||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
|
||||||
*--------------------------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
|
|
||||||
import { Account, NodeInfo } from 'sqlops';
|
|
||||||
import { TreeNode } from '../../treeNodes';
|
|
||||||
import * as nls from 'vscode-nls';
|
|
||||||
const localize = nls.loadMessageBundle();
|
|
||||||
|
|
||||||
import { AzureResourceContainerTreeNodeBase } from './baseTreeNodes';
|
|
||||||
import { AzureResourceItemType } from '../constants';
|
|
||||||
import { AzureResourceErrorMessageUtil } from '../utils';
|
|
||||||
import { AzureResourceDatabaseTreeNode } from './databaseTreeNode';
|
|
||||||
import { AzureResourceMessageTreeNode } from './messageTreeNode';
|
|
||||||
import { AzureResourceSubscription, AzureResourceDatabase } from '../models';
|
|
||||||
import { IAzureResourceTreeChangeHandler } from './treeProvider';
|
|
||||||
|
|
||||||
export class AzureResourceDatabaseContainerTreeNode extends AzureResourceContainerTreeNodeBase {
|
|
||||||
public constructor(
|
|
||||||
public readonly subscription: AzureResourceSubscription,
|
|
||||||
account: Account,
|
|
||||||
treeChangeHandler: IAzureResourceTreeChangeHandler,
|
|
||||||
parent: TreeNode
|
|
||||||
) {
|
|
||||||
super(account, treeChangeHandler, parent);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getChildren(): Promise<TreeNode[]> {
|
|
||||||
try {
|
|
||||||
let databases: AzureResourceDatabase[] = [];
|
|
||||||
|
|
||||||
if (this._isClearingCache) {
|
|
||||||
let credentials = await this.getCredentials();
|
|
||||||
databases = (await this.servicePool.databaseService.getDatabases(this.subscription, credentials)) || <AzureResourceDatabase[]>[];
|
|
||||||
|
|
||||||
let cache = this.getCache<AzureResourceDatabasesCache>();
|
|
||||||
if (!cache) {
|
|
||||||
cache = { databases: { } };
|
|
||||||
}
|
|
||||||
cache.databases[this.subscription.id] = databases;
|
|
||||||
this.updateCache(cache);
|
|
||||||
|
|
||||||
this._isClearingCache = false;
|
|
||||||
} else {
|
|
||||||
const cache = this.getCache<AzureResourceDatabasesCache>();
|
|
||||||
if (cache) {
|
|
||||||
databases = cache.databases[this.subscription.id] || <AzureResourceDatabase[]>[];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (databases.length === 0) {
|
|
||||||
return [AzureResourceMessageTreeNode.create(AzureResourceDatabaseContainerTreeNode.NoDatabases, this)];
|
|
||||||
} else {
|
|
||||||
return databases.map((database) => new AzureResourceDatabaseTreeNode(database, this.treeChangeHandler, this));
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
return [AzureResourceMessageTreeNode.create(AzureResourceErrorMessageUtil.getErrorMessage(error), this)];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public getTreeItem(): TreeItem | Promise<TreeItem> {
|
|
||||||
let item = new TreeItem(AzureResourceDatabaseContainerTreeNode.Label, TreeItemCollapsibleState.Collapsed);
|
|
||||||
item.contextValue = AzureResourceItemType.databaseContainer;
|
|
||||||
item.iconPath = {
|
|
||||||
dark: this.servicePool.contextService.getAbsolutePath('resources/dark/folder_inverse.svg'),
|
|
||||||
light: this.servicePool.contextService.getAbsolutePath('resources/light/folder.svg')
|
|
||||||
};
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getNodeInfo(): NodeInfo {
|
|
||||||
return {
|
|
||||||
label: AzureResourceDatabaseContainerTreeNode.Label,
|
|
||||||
isLeaf: false,
|
|
||||||
errorMessage: undefined,
|
|
||||||
metadata: undefined,
|
|
||||||
nodePath: this.generateNodePath(),
|
|
||||||
nodeStatus: undefined,
|
|
||||||
nodeType: AzureResourceItemType.databaseContainer,
|
|
||||||
nodeSubType: undefined,
|
|
||||||
iconType: AzureResourceItemType.databaseContainer
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public get nodePathValue(): string {
|
|
||||||
return 'databaseContainer';
|
|
||||||
}
|
|
||||||
|
|
||||||
protected get cacheKey(): string {
|
|
||||||
return 'azureResource.cache.databases';
|
|
||||||
}
|
|
||||||
|
|
||||||
private static readonly Label = localize('azureResource.tree.databaseContainerTreeNode.label', 'SQL Databases');
|
|
||||||
private static readonly NoDatabases = localize('azureResource.tree.databaseContainerTreeNode.noDatabases', 'No SQL Databases found.');
|
|
||||||
}
|
|
||||||
|
|
||||||
interface AzureResourceDatabasesCache {
|
|
||||||
databases: { [subscriptionId: string]: AzureResourceDatabase[] };
|
|
||||||
}
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
/*---------------------------------------------------------------------------------------------
|
|
||||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
|
||||||
*--------------------------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
|
|
||||||
import { Account, NodeInfo } from 'sqlops';
|
|
||||||
import { TreeNode } from '../../treeNodes';
|
|
||||||
import * as nls from 'vscode-nls';
|
|
||||||
const localize = nls.loadMessageBundle();
|
|
||||||
|
|
||||||
import { AzureResourceContainerTreeNodeBase } from './baseTreeNodes';
|
|
||||||
import { AzureResourceItemType } from '../constants';
|
|
||||||
import { AzureResourceMessageTreeNode } from './messageTreeNode';
|
|
||||||
import { AzureResourceErrorMessageUtil } from '../utils';
|
|
||||||
import { AzureResourceSubscription, AzureResourceDatabaseServer } from '../models';
|
|
||||||
import { AzureResourceDatabaseServerTreeNode } from './databaseServerTreeNode';
|
|
||||||
import { IAzureResourceTreeChangeHandler } from './treeProvider';
|
|
||||||
|
|
||||||
export class AzureResourceDatabaseServerContainerTreeNode extends AzureResourceContainerTreeNodeBase {
|
|
||||||
public constructor(
|
|
||||||
public readonly subscription: AzureResourceSubscription,
|
|
||||||
account: Account,
|
|
||||||
treeChangeHandler: IAzureResourceTreeChangeHandler,
|
|
||||||
parent: TreeNode
|
|
||||||
) {
|
|
||||||
super(account, treeChangeHandler, parent);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getChildren(): Promise<TreeNode[]> {
|
|
||||||
try {
|
|
||||||
let databaseServers: AzureResourceDatabaseServer[] = [];
|
|
||||||
|
|
||||||
if (this._isClearingCache) {
|
|
||||||
let credentials = await this.getCredentials();
|
|
||||||
databaseServers = (await this.servicePool.databaseServerService.getDatabaseServers(this.subscription, credentials)) || <AzureResourceDatabaseServer[]>[];
|
|
||||||
|
|
||||||
let cache = this.getCache<AzureResourceDatabaseServersCache>();
|
|
||||||
if (!cache) {
|
|
||||||
cache = { databaseServers: { } };
|
|
||||||
}
|
|
||||||
cache.databaseServers[this.subscription.id] = databaseServers;
|
|
||||||
this.updateCache<AzureResourceDatabaseServersCache>(cache);
|
|
||||||
|
|
||||||
this._isClearingCache = false;
|
|
||||||
} else {
|
|
||||||
const cache = this.getCache<AzureResourceDatabaseServersCache>();
|
|
||||||
if (cache) {
|
|
||||||
databaseServers = cache.databaseServers[this.subscription.id] || <AzureResourceDatabaseServer[]>[];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (databaseServers.length === 0) {
|
|
||||||
return [AzureResourceMessageTreeNode.create(AzureResourceDatabaseServerContainerTreeNode.NoDatabaseServers, this)];
|
|
||||||
} else {
|
|
||||||
return databaseServers.map((server) => new AzureResourceDatabaseServerTreeNode(server, this.treeChangeHandler, this));
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
return [AzureResourceMessageTreeNode.create(AzureResourceErrorMessageUtil.getErrorMessage(error), this)];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public getTreeItem(): TreeItem | Promise<TreeItem> {
|
|
||||||
let item = new TreeItem(AzureResourceDatabaseServerContainerTreeNode.Label, TreeItemCollapsibleState.Collapsed);
|
|
||||||
item.contextValue = AzureResourceItemType.databaseServerContainer;
|
|
||||||
item.iconPath = {
|
|
||||||
dark: this.servicePool.contextService.getAbsolutePath('resources/dark/folder_inverse.svg'),
|
|
||||||
light: this.servicePool.contextService.getAbsolutePath('resources/light/folder.svg')
|
|
||||||
};
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getNodeInfo(): NodeInfo {
|
|
||||||
return {
|
|
||||||
label: AzureResourceDatabaseServerContainerTreeNode.Label,
|
|
||||||
isLeaf: false,
|
|
||||||
errorMessage: undefined,
|
|
||||||
metadata: undefined,
|
|
||||||
nodePath: this.generateNodePath(),
|
|
||||||
nodeStatus: undefined,
|
|
||||||
nodeType: AzureResourceItemType.databaseServerContainer,
|
|
||||||
nodeSubType: undefined,
|
|
||||||
iconType: AzureResourceItemType.databaseServerContainer
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public get nodePathValue(): string {
|
|
||||||
return 'databaseServerContainer';
|
|
||||||
}
|
|
||||||
|
|
||||||
protected get cacheKey(): string {
|
|
||||||
return 'azureResource.cache.databaseServers';
|
|
||||||
}
|
|
||||||
|
|
||||||
private static readonly Label = localize('azureResource.tree.databaseServerContainerTreeNode.label', 'SQL Servers');
|
|
||||||
private static readonly NoDatabaseServers = localize('azureResource.tree.databaseContainerTreeNode.noDatabaseServers', 'No SQL Servers found.');
|
|
||||||
}
|
|
||||||
|
|
||||||
interface AzureResourceDatabaseServersCache {
|
|
||||||
databaseServers: { [subscriptionId: string]: AzureResourceDatabaseServer[] };
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
/*---------------------------------------------------------------------------------------------
|
|
||||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
|
||||||
*--------------------------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
|
|
||||||
import { NodeInfo } from 'sqlops';
|
|
||||||
import { TreeNode } from '../../treeNodes';
|
|
||||||
|
|
||||||
import { AzureResourceTreeNodeBase } from './baseTreeNodes';
|
|
||||||
import { AzureResourceItemType } from '../constants';
|
|
||||||
import { AzureResourceDatabaseServer } from '../models';
|
|
||||||
import { IAzureResourceTreeChangeHandler } from './treeProvider';
|
|
||||||
|
|
||||||
export class AzureResourceDatabaseServerTreeNode extends AzureResourceTreeNodeBase {
|
|
||||||
public constructor(
|
|
||||||
public readonly databaseServer: AzureResourceDatabaseServer,
|
|
||||||
treeChangeHandler: IAzureResourceTreeChangeHandler,
|
|
||||||
parent: TreeNode
|
|
||||||
) {
|
|
||||||
super(treeChangeHandler, parent);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getChildren(): Promise<TreeNode[]> {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
public getTreeItem(): TreeItem | Promise<TreeItem> {
|
|
||||||
let item = new TreeItem(this.databaseServer.name, TreeItemCollapsibleState.None);
|
|
||||||
item.contextValue = AzureResourceItemType.databaseServer;
|
|
||||||
item.iconPath = {
|
|
||||||
dark: this.servicePool.contextService.getAbsolutePath('resources/dark/sql_server_inverse.svg'),
|
|
||||||
light: this.servicePool.contextService.getAbsolutePath('resources/light/sql_server.svg')
|
|
||||||
};
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getNodeInfo(): NodeInfo {
|
|
||||||
return {
|
|
||||||
label: this.databaseServer.name,
|
|
||||||
isLeaf: true,
|
|
||||||
errorMessage: undefined,
|
|
||||||
metadata: undefined,
|
|
||||||
nodePath: this.generateNodePath(),
|
|
||||||
nodeStatus: undefined,
|
|
||||||
nodeType: AzureResourceItemType.databaseServer,
|
|
||||||
nodeSubType: undefined,
|
|
||||||
iconType: AzureResourceItemType.databaseServer
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public get nodePathValue(): string {
|
|
||||||
return `databaseServer_${this.databaseServer.name}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
/*---------------------------------------------------------------------------------------------
|
|
||||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
|
||||||
*--------------------------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
|
|
||||||
import { NodeInfo } from 'sqlops';
|
|
||||||
import { TreeNode } from '../../treeNodes';
|
|
||||||
|
|
||||||
import { AzureResourceTreeNodeBase } from './baseTreeNodes';
|
|
||||||
import { AzureResourceItemType } from '../constants';
|
|
||||||
import { AzureResourceDatabase } from '../models';
|
|
||||||
import { IAzureResourceTreeChangeHandler } from './treeProvider';
|
|
||||||
|
|
||||||
export class AzureResourceDatabaseTreeNode extends AzureResourceTreeNodeBase {
|
|
||||||
public constructor(
|
|
||||||
public readonly database: AzureResourceDatabase,
|
|
||||||
treeChangeHandler: IAzureResourceTreeChangeHandler,
|
|
||||||
parent: TreeNode
|
|
||||||
) {
|
|
||||||
super(treeChangeHandler, parent);
|
|
||||||
|
|
||||||
this._label = `${this.database.name} (${this.database.serverName})`;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getChildren(): Promise<TreeNode[]> {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
public getTreeItem(): TreeItem | Promise<TreeItem> {
|
|
||||||
let item = new TreeItem(this._label, TreeItemCollapsibleState.None);
|
|
||||||
item.contextValue = AzureResourceItemType.database;
|
|
||||||
item.iconPath = {
|
|
||||||
dark: this.servicePool.contextService.getAbsolutePath('resources/dark/sql_database_inverse.svg'),
|
|
||||||
light: this.servicePool.contextService.getAbsolutePath('resources/light/sql_database.svg')
|
|
||||||
};
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getNodeInfo(): NodeInfo {
|
|
||||||
return {
|
|
||||||
label: this._label,
|
|
||||||
isLeaf: true,
|
|
||||||
errorMessage: undefined,
|
|
||||||
metadata: undefined,
|
|
||||||
nodePath: this.generateNodePath(),
|
|
||||||
nodeStatus: undefined,
|
|
||||||
nodeType: AzureResourceItemType.database,
|
|
||||||
nodeSubType: undefined,
|
|
||||||
iconType: AzureResourceItemType.database
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public get nodePathValue(): string {
|
|
||||||
return `database_${this.database.name}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _label: string = undefined;
|
|
||||||
}
|
|
||||||
@@ -7,38 +7,66 @@
|
|||||||
|
|
||||||
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
|
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
|
||||||
import { Account, NodeInfo } from 'sqlops';
|
import { Account, NodeInfo } from 'sqlops';
|
||||||
import { TreeNode } from '../../treeNodes';
|
import { AppContext } from '../../appContext';
|
||||||
|
import * as nls from 'vscode-nls';
|
||||||
|
const localize = nls.loadMessageBundle();
|
||||||
|
|
||||||
import { AzureResourceTreeNodeBase, AzureResourceContainerTreeNodeBase } from './baseTreeNodes';
|
import { azureResource } from '../azure-resource';
|
||||||
|
import { TreeNode } from '../treeNode';
|
||||||
|
import { IAzureResourceNodeWithProviderId } from '../interfaces';
|
||||||
|
import { AzureResourceContainerTreeNodeBase } from './baseTreeNodes';
|
||||||
import { AzureResourceItemType } from '../constants';
|
import { AzureResourceItemType } from '../constants';
|
||||||
import { AzureResourceDatabaseContainerTreeNode } from './databaseContainerTreeNode';
|
|
||||||
import { AzureResourceDatabaseServerContainerTreeNode } from './databaseServerContainerTreeNode';
|
|
||||||
import { AzureResourceSubscription } from '../models';
|
|
||||||
import { IAzureResourceTreeChangeHandler } from './treeChangeHandler';
|
import { IAzureResourceTreeChangeHandler } from './treeChangeHandler';
|
||||||
|
import { AzureResourceMessageTreeNode } from '../messageTreeNode';
|
||||||
|
import { AzureResourceErrorMessageUtil } from '../utils';
|
||||||
|
import { AzureResourceService } from '../resourceService';
|
||||||
|
import { AzureResourceResourceTreeNode } from '../resourceTreeNode';
|
||||||
|
|
||||||
export class AzureResourceSubscriptionTreeNode extends AzureResourceTreeNodeBase {
|
export class AzureResourceSubscriptionTreeNode extends AzureResourceContainerTreeNodeBase {
|
||||||
public constructor(
|
public constructor(
|
||||||
public readonly subscription: AzureResourceSubscription,
|
public readonly account: Account,
|
||||||
account: Account,
|
public readonly subscription: azureResource.AzureResourceSubscription,
|
||||||
|
public readonly tenatId: string,
|
||||||
|
appContext: AppContext,
|
||||||
treeChangeHandler: IAzureResourceTreeChangeHandler,
|
treeChangeHandler: IAzureResourceTreeChangeHandler,
|
||||||
parent: TreeNode
|
parent: TreeNode
|
||||||
) {
|
) {
|
||||||
super(treeChangeHandler, parent);
|
super(appContext, treeChangeHandler, parent);
|
||||||
|
|
||||||
this._children.push(new AzureResourceDatabaseContainerTreeNode(subscription, account, treeChangeHandler, this));
|
this._id = `account_${this.account.key.accountId}.subscription_${this.subscription.id}.tenant_${this.tenatId}`;
|
||||||
this._children.push(new AzureResourceDatabaseServerContainerTreeNode(subscription, account, treeChangeHandler, this));
|
this.setCacheKey(`${this._id}.resources`);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getChildren(): Promise<TreeNode[]> {
|
public async getChildren(): Promise<TreeNode[]> {
|
||||||
return this._children;
|
try {
|
||||||
|
const resourceService = AzureResourceService.getInstance();
|
||||||
|
|
||||||
|
const children: IAzureResourceNodeWithProviderId[] = [];
|
||||||
|
|
||||||
|
for (const resourceProviderId of await resourceService.listResourceProviderIds()) {
|
||||||
|
children.push(...await resourceService.getRootChildren(resourceProviderId, this.account, this.subscription, this.tenatId));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (children.length === 0) {
|
||||||
|
return [AzureResourceMessageTreeNode.create(AzureResourceSubscriptionTreeNode.noResourcesLabel, this)];
|
||||||
|
} else {
|
||||||
|
return children.map((child) => {
|
||||||
|
// To make tree node's id unique, otherwise, treeModel.js would complain 'item already registered'
|
||||||
|
child.resourceNode.treeItem.id = `${this._id}.${child.resourceNode.treeItem.id}`;
|
||||||
|
return new AzureResourceResourceTreeNode(child, this);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return [AzureResourceMessageTreeNode.create(AzureResourceErrorMessageUtil.getErrorMessage(error), this)];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public getTreeItem(): TreeItem | Promise<TreeItem> {
|
public getTreeItem(): TreeItem | Promise<TreeItem> {
|
||||||
let item = new TreeItem(this.subscription.name, TreeItemCollapsibleState.Collapsed);
|
const item = new TreeItem(this.subscription.name, TreeItemCollapsibleState.Collapsed);
|
||||||
item.contextValue = AzureResourceItemType.subscription;
|
item.contextValue = AzureResourceItemType.subscription;
|
||||||
item.iconPath = {
|
item.iconPath = {
|
||||||
dark: this.servicePool.contextService.getAbsolutePath('resources/dark/subscription_inverse.svg'),
|
dark: this.appContext.extensionContext.asAbsolutePath('resources/dark/subscription_inverse.svg'),
|
||||||
light: this.servicePool.contextService.getAbsolutePath('resources/light/subscription.svg')
|
light: this.appContext.extensionContext.asAbsolutePath('resources/light/subscription.svg')
|
||||||
};
|
};
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
@@ -58,8 +86,10 @@ export class AzureResourceSubscriptionTreeNode extends AzureResourceTreeNodeBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
public get nodePathValue(): string {
|
public get nodePathValue(): string {
|
||||||
return `subscription_${this.subscription.id}`;
|
return this._id;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _children: AzureResourceContainerTreeNodeBase[] = [];
|
private _id: string = undefined;
|
||||||
|
|
||||||
|
private static readonly noResourcesLabel = localize('azure.resource.tree.subscriptionTreeNode.noResourcesLabel', 'No Resources found.');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import { TreeNode } from '../../treeNodes';
|
import { TreeNode } from '../treeNode';
|
||||||
|
|
||||||
export interface IAzureResourceTreeChangeHandler {
|
export interface IAzureResourceTreeChangeHandler {
|
||||||
notifyNodeChanged(node: TreeNode): void;
|
notifyNodeChanged(node: TreeNode): void;
|
||||||
|
|||||||
@@ -6,26 +6,25 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import { TreeDataProvider, EventEmitter, Event, TreeItem } from 'vscode';
|
import { TreeDataProvider, EventEmitter, Event, TreeItem } from 'vscode';
|
||||||
import { DidChangeAccountsParams } from 'sqlops';
|
|
||||||
import { TreeNode } from '../../treeNodes';
|
|
||||||
import { setInterval, clearInterval } from 'timers';
|
import { setInterval, clearInterval } from 'timers';
|
||||||
|
import { AppContext } from '../../appContext';
|
||||||
import * as nls from 'vscode-nls';
|
import * as nls from 'vscode-nls';
|
||||||
const localize = nls.loadMessageBundle();
|
const localize = nls.loadMessageBundle();
|
||||||
|
|
||||||
import { AzureResourceServicePool } from '../servicePool';
|
import { TreeNode } from '../treeNode';
|
||||||
import { AzureResourceAccountTreeNode } from './accountTreeNode';
|
import { AzureResourceAccountTreeNode } from './accountTreeNode';
|
||||||
import { AzureResourceAccountNotSignedInTreeNode } from './accountNotSignedInTreeNode';
|
import { AzureResourceAccountNotSignedInTreeNode } from './accountNotSignedInTreeNode';
|
||||||
import { AzureResourceMessageTreeNode } from './messageTreeNode';
|
import { AzureResourceMessageTreeNode } from '../messageTreeNode';
|
||||||
import { AzureResourceContainerTreeNodeBase, AzureResourceTreeNodeBase } from './baseTreeNodes';
|
import { AzureResourceContainerTreeNodeBase } from './baseTreeNodes';
|
||||||
import { AzureResourceErrorMessageUtil } from '../utils';
|
import { AzureResourceErrorMessageUtil } from '../utils';
|
||||||
|
import { IAzureResourceTreeChangeHandler } from './treeChangeHandler';
|
||||||
export interface IAzureResourceTreeChangeHandler {
|
import { IAzureResourceAccountService } from '../../azureResource/interfaces';
|
||||||
notifyNodeChanged(node: TreeNode): void;
|
import { AzureResourceServiceNames } from '../constants';
|
||||||
}
|
|
||||||
|
|
||||||
export class AzureResourceTreeProvider implements TreeDataProvider<TreeNode>, IAzureResourceTreeChangeHandler {
|
export class AzureResourceTreeProvider implements TreeDataProvider<TreeNode>, IAzureResourceTreeChangeHandler {
|
||||||
public constructor() {
|
public constructor(
|
||||||
AzureResourceServicePool.getInstance().accountService.onDidChangeAccounts((e: DidChangeAccountsParams) => { this._onDidChangeTreeData.fire(undefined); });
|
public readonly appContext: AppContext
|
||||||
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getChildren(element?: TreeNode): Promise<TreeNode[]> {
|
public async getChildren(element?: TreeNode): Promise<TreeNode[]> {
|
||||||
@@ -33,11 +32,11 @@ export class AzureResourceTreeProvider implements TreeDataProvider<TreeNode>, IA
|
|||||||
return element.getChildren(true);
|
return element.getChildren(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.isSystemInitialized) {
|
if (!this.isSystemInitialized && !this._loadingTimer) {
|
||||||
this._loadingTimer = setInterval(async () => {
|
this._loadingTimer = setInterval(async () => {
|
||||||
try {
|
try {
|
||||||
// Call sqlops.accounts.getAllAccounts() to determine whether the system has been initialized.
|
// Call sqlops.accounts.getAllAccounts() to determine whether the system has been initialized.
|
||||||
await AzureResourceServicePool.getInstance().accountService.getAccounts();
|
await this.appContext.getService<IAzureResourceAccountService>(AzureResourceServiceNames.accountService).getAccounts();
|
||||||
|
|
||||||
// System has been initialized
|
// System has been initialized
|
||||||
this.isSystemInitialized = true;
|
this.isSystemInitialized = true;
|
||||||
@@ -51,16 +50,16 @@ export class AzureResourceTreeProvider implements TreeDataProvider<TreeNode>, IA
|
|||||||
// System not initialized yet
|
// System not initialized yet
|
||||||
this.isSystemInitialized = false;
|
this.isSystemInitialized = false;
|
||||||
}
|
}
|
||||||
}, AzureResourceTreeProvider.LoadingTimerInterval);
|
}, AzureResourceTreeProvider.loadingTimerInterval);
|
||||||
|
|
||||||
return [AzureResourceMessageTreeNode.create(AzureResourceTreeProvider.Loading, undefined)];
|
return [AzureResourceMessageTreeNode.create(AzureResourceTreeProvider.loadingLabel, undefined)];
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const accounts = await AzureResourceServicePool.getInstance().accountService.getAccounts();
|
const accounts = await this.appContext.getService<IAzureResourceAccountService>(AzureResourceServiceNames.accountService).getAccounts();
|
||||||
|
|
||||||
if (accounts && accounts.length > 0) {
|
if (accounts && accounts.length > 0) {
|
||||||
return accounts.map((account) => new AzureResourceAccountTreeNode(account, this));
|
return accounts.map((account) => new AzureResourceAccountTreeNode(account, this.appContext, this));
|
||||||
} else {
|
} else {
|
||||||
return [new AzureResourceAccountNotSignedInTreeNode()];
|
return [new AzureResourceAccountNotSignedInTreeNode()];
|
||||||
}
|
}
|
||||||
@@ -96,6 +95,6 @@ export class AzureResourceTreeProvider implements TreeDataProvider<TreeNode>, IA
|
|||||||
private _loadingTimer: NodeJS.Timer = undefined;
|
private _loadingTimer: NodeJS.Timer = undefined;
|
||||||
private _onDidChangeTreeData = new EventEmitter<TreeNode>();
|
private _onDidChangeTreeData = new EventEmitter<TreeNode>();
|
||||||
|
|
||||||
private static readonly Loading = localize('azureResource.tree.treeProvider.loading', 'Loading ...');
|
private static readonly loadingLabel = localize('azure.resource.tree.treeProvider.loadingLabel', 'Loading ...');
|
||||||
private static readonly LoadingTimerInterval = 5000;
|
private static readonly loadingTimerInterval = 5000;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,16 +11,6 @@ import * as vscode from 'vscode';
|
|||||||
type TreeNodePredicate = (node: TreeNode) => boolean;
|
type TreeNodePredicate = (node: TreeNode) => boolean;
|
||||||
|
|
||||||
export abstract class TreeNode {
|
export abstract class TreeNode {
|
||||||
private _parent: TreeNode = undefined;
|
|
||||||
|
|
||||||
public get parent(): TreeNode {
|
|
||||||
return this._parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
public set parent(node: TreeNode) {
|
|
||||||
this._parent = node;
|
|
||||||
}
|
|
||||||
|
|
||||||
public generateNodePath(): string {
|
public generateNodePath(): string {
|
||||||
let path = undefined;
|
let path = undefined;
|
||||||
if (this.parent) {
|
if (this.parent) {
|
||||||
@@ -65,13 +55,23 @@ export abstract class TreeNode {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get parent(): TreeNode {
|
||||||
|
return this._parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public set parent(node: TreeNode) {
|
||||||
|
this._parent = node;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract getChildren(refreshChildren: boolean): TreeNode[] | Promise<TreeNode[]>;
|
||||||
|
public abstract getTreeItem(): vscode.TreeItem | Promise<vscode.TreeItem>;
|
||||||
|
|
||||||
|
public abstract getNodeInfo(): sqlops.NodeInfo;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The value to use for this node in the node path
|
* The value to use for this node in the node path
|
||||||
*/
|
*/
|
||||||
public abstract get nodePathValue(): string;
|
public abstract get nodePathValue(): string;
|
||||||
|
|
||||||
abstract getChildren(refreshChildren: boolean): TreeNode[] | Promise<TreeNode[]>;
|
private _parent: TreeNode = undefined;
|
||||||
abstract getTreeItem(): vscode.TreeItem | Promise<vscode.TreeItem>;
|
|
||||||
|
|
||||||
abstract getNodeInfo(): sqlops.NodeInfo;
|
|
||||||
}
|
}
|
||||||
@@ -12,10 +12,9 @@ export function getErrorMessage(error: Error | string): string {
|
|||||||
return (error instanceof Error) ? error.message : error;
|
return (error instanceof Error) ? error.message : error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export class AzureResourceErrorMessageUtil {
|
export class AzureResourceErrorMessageUtil {
|
||||||
public static getErrorMessage(error: Error | string): string {
|
public static getErrorMessage(error: Error | string): string {
|
||||||
return localize('azureResource.error', 'Error: {0}', getErrorMessage(error));
|
return localize('azure.resource.error', 'Error: {0}', getErrorMessage(error));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,3 +41,56 @@ export function generateGuid(): string {
|
|||||||
return oct.substr(0, 8) + '-' + oct.substr(9, 4) + '-4' + oct.substr(13, 3) + '-' + clockSequenceHi + oct.substr(16, 3) + '-' + oct.substr(19, 12);
|
return oct.substr(0, 8) + '-' + oct.substr(9, 4) + '-4' + oct.substr(13, 3) + '-' + clockSequenceHi + oct.substr(16, 3) + '-' + oct.substr(19, 12);
|
||||||
/* tslint:enable:no-bitwise */
|
/* tslint:enable:no-bitwise */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function equals(one: any, other: any): boolean {
|
||||||
|
if (one === other) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (one === null || one === undefined || other === null || other === undefined) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (typeof one !== typeof other) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (typeof one !== 'object') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ((Array.isArray(one)) !== (Array.isArray(other))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let i: number;
|
||||||
|
let key: string;
|
||||||
|
|
||||||
|
if (Array.isArray(one)) {
|
||||||
|
if (one.length !== other.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (i = 0; i < one.length; i++) {
|
||||||
|
if (!equals(one[i], other[i])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const oneKeys: string[] = [];
|
||||||
|
|
||||||
|
for (key in one) {
|
||||||
|
oneKeys.push(key);
|
||||||
|
}
|
||||||
|
oneKeys.sort();
|
||||||
|
const otherKeys: string[] = [];
|
||||||
|
for (key in other) {
|
||||||
|
otherKeys.push(key);
|
||||||
|
}
|
||||||
|
otherKeys.sort();
|
||||||
|
if (!equals(oneKeys, otherKeys)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (i = 0; i < oneKeys.length; i++) {
|
||||||
|
if (!equals(one[oneKeys[i]], other[oneKeys[i]])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import ControllerBase from './controllerBase';
|
||||||
|
import { DidChangeAccountsParams } from 'sqlops';
|
||||||
|
|
||||||
|
import {
|
||||||
|
IAzureResourceCacheService,
|
||||||
|
IAzureResourceAccountService,
|
||||||
|
IAzureResourceSubscriptionService,
|
||||||
|
IAzureResourceSubscriptionFilterService,
|
||||||
|
IAzureResourceTenantService } from '../azureResource/interfaces';
|
||||||
|
import { AzureResourceServiceNames } from '../azureResource/constants';
|
||||||
|
import { AzureResourceTreeProvider } from '../azureResource/tree/treeProvider';
|
||||||
|
import { registerAzureResourceCommands } from '../azureResource/commands';
|
||||||
|
import { AzureResourceAccountService } from '../azureResource/services/accountService';
|
||||||
|
import { AzureResourceSubscriptionService } from '../azureResource/services/subscriptionService';
|
||||||
|
import { AzureResourceSubscriptionFilterService } from '../azureResource/services/subscriptionFilterService';
|
||||||
|
import { AzureResourceCacheService } from '../azureResource/services/cacheService';
|
||||||
|
import { AzureResourceTenantService } from '../azureResource/services/tenantService';
|
||||||
|
|
||||||
|
import { registerAzureResourceDatabaseServerCommands } from '../azureResource/providers/databaseServer/commands';
|
||||||
|
import { registerAzureResourceDatabaseCommands } from '../azureResource/providers/database/commands';
|
||||||
|
import { equals } from '../azureResource/utils';
|
||||||
|
|
||||||
|
export default class AzureResourceController extends ControllerBase {
|
||||||
|
public activate(): Promise<boolean> {
|
||||||
|
this.appContext.registerService<IAzureResourceCacheService>(AzureResourceServiceNames.cacheService, new AzureResourceCacheService(this.extensionContext));
|
||||||
|
this.appContext.registerService<IAzureResourceAccountService>(AzureResourceServiceNames.accountService, new AzureResourceAccountService(this.apiWrapper));
|
||||||
|
this.appContext.registerService<IAzureResourceSubscriptionService>(AzureResourceServiceNames.subscriptionService, new AzureResourceSubscriptionService());
|
||||||
|
this.appContext.registerService<IAzureResourceSubscriptionFilterService>(AzureResourceServiceNames.subscriptionFilterService, new AzureResourceSubscriptionFilterService(new AzureResourceCacheService(this.extensionContext)));
|
||||||
|
this.appContext.registerService<IAzureResourceTenantService>(AzureResourceServiceNames.tenantService, new AzureResourceTenantService());
|
||||||
|
|
||||||
|
const azureResourceTree = new AzureResourceTreeProvider(this.appContext);
|
||||||
|
this.extensionContext.subscriptions.push(this.apiWrapper.registerTreeDataProvider('azureResourceExplorer', azureResourceTree));
|
||||||
|
|
||||||
|
let previousAccounts = undefined;
|
||||||
|
this.appContext.getService<IAzureResourceAccountService>(AzureResourceServiceNames.accountService).onDidChangeAccounts((e: DidChangeAccountsParams) => {
|
||||||
|
// the onDidChangeAccounts event will trigger in many cases where the accounts didn't actually change
|
||||||
|
// the notifyNodeChanged event triggers a refresh which triggers a getChildren which can trigger this callback
|
||||||
|
// this below check short-circuits the infinite callback loop
|
||||||
|
if (!equals(e.accounts, previousAccounts)) {
|
||||||
|
azureResourceTree.notifyNodeChanged(undefined);
|
||||||
|
}
|
||||||
|
previousAccounts = e.accounts;
|
||||||
|
});
|
||||||
|
|
||||||
|
registerAzureResourceCommands(this.appContext, azureResourceTree);
|
||||||
|
|
||||||
|
registerAzureResourceDatabaseServerCommands(this.appContext);
|
||||||
|
|
||||||
|
registerAzureResourceDatabaseCommands(this.appContext);
|
||||||
|
|
||||||
|
return Promise.resolve(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public deactivate(): void {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
/*---------------------------------------------------------------------------------------------
|
|
||||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
|
||||||
*--------------------------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
import ControllerBase from './controllerBase';
|
|
||||||
|
|
||||||
import { AzureResourceTreeProvider } from '../azureResource/tree/treeProvider';
|
|
||||||
import { registerAzureResourceCommands } from '../azureResource/commands';
|
|
||||||
import { AzureResourceServicePool } from '../azureResource/servicePool';
|
|
||||||
import { AzureResourceCredentialService } from '../azureResource/services/credentialService';
|
|
||||||
import { AzureResourceAccountService } from '../azureResource/services/accountService';
|
|
||||||
import { AzureResourceSubscriptionService } from '../azureResource/services/subscriptionService';
|
|
||||||
import { AzureResourceSubscriptionFilterService } from '../azureResource/services/subscriptionFilterService';
|
|
||||||
import { AzureResourceDatabaseServerService } from '../azureResource/services/databaseServerService';
|
|
||||||
import { AzureResourceDatabaseService } from '../azureResource/services/databaseService';
|
|
||||||
import { AzureResourceCacheService } from '../azureResource/services/cacheService';
|
|
||||||
import { AzureResourceContextService } from '../azureResource/services/contextService';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The main controller class that initializes the extension
|
|
||||||
*/
|
|
||||||
export default class MainController extends ControllerBase {
|
|
||||||
// PUBLIC METHODS //////////////////////////////////////////////////////
|
|
||||||
/**
|
|
||||||
* Deactivates the extension
|
|
||||||
*/
|
|
||||||
public deactivate(): void {
|
|
||||||
}
|
|
||||||
|
|
||||||
public activate(): Promise<boolean> {
|
|
||||||
this.configureAzureResource();
|
|
||||||
return Promise.resolve(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private configureAzureResource(): void {
|
|
||||||
let servicePool = AzureResourceServicePool.getInstance();
|
|
||||||
servicePool.cacheService = new AzureResourceCacheService(this.extensionContext);
|
|
||||||
servicePool.contextService = new AzureResourceContextService(this.extensionContext, this.apiWrapper);
|
|
||||||
servicePool.accountService = new AzureResourceAccountService(this.apiWrapper);
|
|
||||||
servicePool.credentialService = new AzureResourceCredentialService(this.apiWrapper);
|
|
||||||
servicePool.subscriptionService = new AzureResourceSubscriptionService();
|
|
||||||
servicePool.subscriptionFilterService = new AzureResourceSubscriptionFilterService(new AzureResourceCacheService(this.extensionContext));
|
|
||||||
servicePool.databaseService = new AzureResourceDatabaseService();
|
|
||||||
servicePool.databaseServerService = new AzureResourceDatabaseServerService();
|
|
||||||
|
|
||||||
let azureResourceTree = new AzureResourceTreeProvider();
|
|
||||||
this.extensionContext.subscriptions.push(this.apiWrapper.registerTreeDataProvider('azureResourceExplorer', azureResourceTree));
|
|
||||||
|
|
||||||
registerAzureResourceCommands(this.apiWrapper, azureResourceTree);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -6,12 +6,17 @@ import * as path from 'path';
|
|||||||
import * as os from 'os';
|
import * as os from 'os';
|
||||||
import * as constants from './constants';
|
import * as constants from './constants';
|
||||||
|
|
||||||
import MainController from './controllers/mainController';
|
import AzureResourceController from './controllers/azureResourceController';
|
||||||
import { AppContext } from './appContext';
|
import { AppContext } from './appContext';
|
||||||
import ControllerBase from './controllers/controllerBase';
|
import ControllerBase from './controllers/controllerBase';
|
||||||
import { ApiWrapper } from './apiWrapper';
|
import { ApiWrapper } from './apiWrapper';
|
||||||
import { AzureAccountProviderService } from './account-provider/azureAccountProviderService';
|
import { AzureAccountProviderService } from './account-provider/azureAccountProviderService';
|
||||||
|
|
||||||
|
import { AzureResourceDatabaseServerProvider } from './azureResource/providers/databaseServer/databaseServerProvider';
|
||||||
|
import { AzureResourceDatabaseServerService } from './azureResource/providers/databaseServer/databaseServerService';
|
||||||
|
import { AzureResourceDatabaseProvider } from './azureResource/providers/database/databaseProvider';
|
||||||
|
import { AzureResourceDatabaseService } from './azureResource/providers/database/databaseService';
|
||||||
|
|
||||||
let controllers: ControllerBase[] = [];
|
let controllers: ControllerBase[] = [];
|
||||||
|
|
||||||
|
|
||||||
@@ -35,7 +40,8 @@ export function getDefaultLogLocation() {
|
|||||||
// this method is called when your extension is activated
|
// this method is called when your extension is activated
|
||||||
// your extension is activated the very first time the command is executed
|
// your extension is activated the very first time the command is executed
|
||||||
export function activate(extensionContext: vscode.ExtensionContext) {
|
export function activate(extensionContext: vscode.ExtensionContext) {
|
||||||
let appContext = new AppContext(extensionContext, new ApiWrapper());
|
const apiWrapper = new ApiWrapper();
|
||||||
|
let appContext = new AppContext(extensionContext, apiWrapper);
|
||||||
let activations: Thenable<boolean>[] = [];
|
let activations: Thenable<boolean>[] = [];
|
||||||
|
|
||||||
// Create the folder for storing the token caches
|
// Create the folder for storing the token caches
|
||||||
@@ -56,21 +62,19 @@ export function activate(extensionContext: vscode.ExtensionContext) {
|
|||||||
extensionContext.subscriptions.push(accountProviderService);
|
extensionContext.subscriptions.push(accountProviderService);
|
||||||
accountProviderService.activate();
|
accountProviderService.activate();
|
||||||
|
|
||||||
// Start the main controller
|
const azureResourceController = new AzureResourceController(appContext);
|
||||||
let mainController = new MainController(appContext);
|
controllers.push(azureResourceController);
|
||||||
controllers.push(mainController);
|
extensionContext.subscriptions.push(azureResourceController);
|
||||||
extensionContext.subscriptions.push(mainController);
|
activations.push(azureResourceController.activate());
|
||||||
activations.push(mainController.activate());
|
|
||||||
|
|
||||||
return Promise.all(activations)
|
return {
|
||||||
.then((results: boolean[]) => {
|
provideResources() {
|
||||||
for (let result of results) {
|
return [
|
||||||
if (!result) {
|
new AzureResourceDatabaseServerProvider(new AzureResourceDatabaseServerService(), apiWrapper, extensionContext),
|
||||||
return false;
|
new AzureResourceDatabaseProvider(new AzureResourceDatabaseService(), apiWrapper, extensionContext)
|
||||||
|
];
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// this method is called when your extension is deactivated
|
// this method is called when your extension is deactivated
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import * as should from 'should';
|
|||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import 'mocha';
|
import 'mocha';
|
||||||
|
|
||||||
import { AzureResourceItemType } from '../../../azureResource/constants';
|
import { AzureResourceItemType } from '../../azureResource/constants';
|
||||||
import { AzureResourceMessageTreeNode } from '../../../azureResource/tree/messageTreeNode';
|
import { AzureResourceMessageTreeNode } from '../../azureResource/messageTreeNode';
|
||||||
|
|
||||||
describe('AzureResourceMessageTreeNode.info', function(): void {
|
describe('AzureResourceMessageTreeNode.info', function(): void {
|
||||||
it('Should be correct when created.', async function(): Promise<void> {
|
it('Should be correct when created.', async function(): Promise<void> {
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import * as should from 'should';
|
||||||
|
import * as TypeMoq from 'typemoq';
|
||||||
|
import * as sqlops from 'sqlops';
|
||||||
|
import * as vscode from 'vscode';
|
||||||
|
import 'mocha';
|
||||||
|
|
||||||
|
import { azureResource } from '../../../../azureResource/azure-resource';
|
||||||
|
import { ApiWrapper } from '../../../../apiWrapper';
|
||||||
|
import { IAzureResourceDatabaseService } from '../../../../azureResource/providers/database/interfaces';
|
||||||
|
import { AzureResourceDatabaseTreeDataProvider } from '../../../../azureResource/providers/database/databaseTreeDataProvider';
|
||||||
|
import { AzureResourceDatabase } from '../../../../azureResource/providers/database/models';
|
||||||
|
import { AzureResourceItemType } from '../../../../azureResource/constants';
|
||||||
|
|
||||||
|
// Mock services
|
||||||
|
let mockDatabaseService: TypeMoq.IMock<IAzureResourceDatabaseService>;
|
||||||
|
let mockApiWrapper: TypeMoq.IMock<ApiWrapper>;
|
||||||
|
let mockExtensionContext: TypeMoq.IMock<vscode.ExtensionContext>;
|
||||||
|
|
||||||
|
// Mock test data
|
||||||
|
const mockAccount: sqlops.Account = {
|
||||||
|
key: {
|
||||||
|
accountId: 'mock_account',
|
||||||
|
providerId: 'mock_provider'
|
||||||
|
},
|
||||||
|
displayInfo: {
|
||||||
|
displayName: 'mock_account@test.com',
|
||||||
|
accountType: 'Microsoft',
|
||||||
|
contextualDisplayName: 'test'
|
||||||
|
},
|
||||||
|
properties: undefined,
|
||||||
|
isStale: false
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockSubscription: azureResource.AzureResourceSubscription = {
|
||||||
|
id: 'mock_subscription',
|
||||||
|
name: 'mock subscription'
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockTenantId: string = 'mock_tenant';
|
||||||
|
|
||||||
|
const mockResourceRootNode: azureResource.IAzureResourceNode = {
|
||||||
|
account: mockAccount,
|
||||||
|
subscription: mockSubscription,
|
||||||
|
tenantId: mockTenantId,
|
||||||
|
treeItem: {
|
||||||
|
id: 'mock_resource_root_node',
|
||||||
|
label: 'mock resource root node',
|
||||||
|
iconPath: undefined,
|
||||||
|
collapsibleState: vscode.TreeItemCollapsibleState.Collapsed,
|
||||||
|
contextValue: 'mock_resource_root_node'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockTokens = {};
|
||||||
|
mockTokens[mockTenantId] = {
|
||||||
|
token: 'mock_token',
|
||||||
|
tokenType: 'Bearer'
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockDatabases: AzureResourceDatabase[] = [
|
||||||
|
{
|
||||||
|
name: 'mock database 1',
|
||||||
|
serverName: 'mock database server 1',
|
||||||
|
serverFullName: 'mock database server full name 1',
|
||||||
|
loginName: 'mock login'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'mock database 2',
|
||||||
|
serverName: 'mock database server 2',
|
||||||
|
serverFullName: 'mock database server full name 2',
|
||||||
|
loginName: 'mock login'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
describe('AzureResourceDatabaseTreeDataProvider.info', function(): void {
|
||||||
|
beforeEach(() => {
|
||||||
|
mockDatabaseService = TypeMoq.Mock.ofType<IAzureResourceDatabaseService>();
|
||||||
|
mockApiWrapper = TypeMoq.Mock.ofType<ApiWrapper>();
|
||||||
|
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should be correct when created.', async function(): Promise<void> {
|
||||||
|
const treeDataProvider = new AzureResourceDatabaseTreeDataProvider(mockDatabaseService.object, mockApiWrapper.object, mockExtensionContext.object);
|
||||||
|
|
||||||
|
const treeItem = await treeDataProvider.getTreeItem(mockResourceRootNode);
|
||||||
|
should(treeItem.id).equal(mockResourceRootNode.treeItem.id);
|
||||||
|
should(treeItem.label).equal(mockResourceRootNode.treeItem.label);
|
||||||
|
should(treeItem.collapsibleState).equal(mockResourceRootNode.treeItem.collapsibleState);
|
||||||
|
should(treeItem.contextValue).equal(mockResourceRootNode.treeItem.contextValue);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('AzureResourceDatabaseTreeDataProvider.getChildren', function(): void {
|
||||||
|
beforeEach(() => {
|
||||||
|
mockDatabaseService = TypeMoq.Mock.ofType<IAzureResourceDatabaseService>();
|
||||||
|
mockApiWrapper = TypeMoq.Mock.ofType<ApiWrapper>();
|
||||||
|
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
|
||||||
|
|
||||||
|
mockApiWrapper.setup((o) => o.getSecurityToken(mockAccount, sqlops.AzureResource.ResourceManagement)).returns(() => Promise.resolve(mockTokens));
|
||||||
|
mockDatabaseService.setup((o) => o.getDatabases(mockSubscription, TypeMoq.It.isAny())).returns(() => Promise.resolve(mockDatabases));
|
||||||
|
mockExtensionContext.setup((o) => o.asAbsolutePath(TypeMoq.It.isAnyString())).returns(() => TypeMoq.It.isAnyString());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should return container node when element is undefined.', async function(): Promise<void> {
|
||||||
|
const treeDataProvider = new AzureResourceDatabaseTreeDataProvider(mockDatabaseService.object, mockApiWrapper.object, mockExtensionContext.object);
|
||||||
|
|
||||||
|
const children = await treeDataProvider.getChildren();
|
||||||
|
|
||||||
|
should(children).Array();
|
||||||
|
should(children.length).equal(1);
|
||||||
|
|
||||||
|
const child = children[0];
|
||||||
|
should(child.account).undefined();
|
||||||
|
should(child.subscription).undefined();
|
||||||
|
should(child.tenantId).undefined();
|
||||||
|
should(child.treeItem.id).equal('azure.resource.providers.database.treeDataProvider.databaseContainer');
|
||||||
|
should(child.treeItem.label).equal('SQL Databases');
|
||||||
|
should(child.treeItem.collapsibleState).equal(vscode.TreeItemCollapsibleState.Collapsed);
|
||||||
|
should(child.treeItem.contextValue).equal('azure.resource.itemType.databaseContainer');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should return resource nodes when it is container node.', async function(): Promise<void> {
|
||||||
|
const treeDataProvider = new AzureResourceDatabaseTreeDataProvider(mockDatabaseService.object, mockApiWrapper.object, mockExtensionContext.object);
|
||||||
|
|
||||||
|
const children = await treeDataProvider.getChildren(mockResourceRootNode);
|
||||||
|
|
||||||
|
should(children).Array();
|
||||||
|
should(children.length).equal(mockDatabases.length);
|
||||||
|
|
||||||
|
for (let ix = 0; ix < children.length; ix++) {
|
||||||
|
const child = children[ix];
|
||||||
|
const database = mockDatabases[ix];
|
||||||
|
|
||||||
|
should(child.account).equal(mockAccount);
|
||||||
|
should(child.subscription).equal(mockSubscription);
|
||||||
|
should(child.tenantId).equal(mockTenantId);
|
||||||
|
should(child.treeItem.id).equal(`databaseServer_${database.serverFullName}.database_${database.name}`);
|
||||||
|
should(child.treeItem.label).equal(`${database.name} (${database.serverName})`);
|
||||||
|
should(child.treeItem.collapsibleState).equal(vscode.TreeItemCollapsibleState.None);
|
||||||
|
should(child.treeItem.contextValue).equal(AzureResourceItemType.database);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import * as should from 'should';
|
||||||
|
import * as TypeMoq from 'typemoq';
|
||||||
|
import * as sqlops from 'sqlops';
|
||||||
|
import * as vscode from 'vscode';
|
||||||
|
import 'mocha';
|
||||||
|
|
||||||
|
import { azureResource } from '../../../../azureResource/azure-resource';
|
||||||
|
import { ApiWrapper } from '../../../../apiWrapper';
|
||||||
|
import { IAzureResourceDatabaseServerService } from '../../../../azureResource/providers/databaseServer/interfaces';
|
||||||
|
import { AzureResourceDatabaseServerTreeDataProvider } from '../../../../azureResource/providers/databaseServer/databaseServerTreeDataProvider';
|
||||||
|
import { AzureResourceDatabaseServer } from '../../../../azureResource/providers/databaseServer/models';
|
||||||
|
import { AzureResourceItemType } from '../../../../azureResource/constants';
|
||||||
|
|
||||||
|
// Mock services
|
||||||
|
let mockDatabaseServerService: TypeMoq.IMock<IAzureResourceDatabaseServerService>;
|
||||||
|
let mockApiWrapper: TypeMoq.IMock<ApiWrapper>;
|
||||||
|
let mockExtensionContext: TypeMoq.IMock<vscode.ExtensionContext>;
|
||||||
|
|
||||||
|
// Mock test data
|
||||||
|
const mockAccount: sqlops.Account = {
|
||||||
|
key: {
|
||||||
|
accountId: 'mock_account',
|
||||||
|
providerId: 'mock_provider'
|
||||||
|
},
|
||||||
|
displayInfo: {
|
||||||
|
displayName: 'mock_account@test.com',
|
||||||
|
accountType: 'Microsoft',
|
||||||
|
contextualDisplayName: 'test'
|
||||||
|
},
|
||||||
|
properties: undefined,
|
||||||
|
isStale: false
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockSubscription: azureResource.AzureResourceSubscription = {
|
||||||
|
id: 'mock_subscription',
|
||||||
|
name: 'mock subscription'
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockTenantId: string = 'mock_tenant';
|
||||||
|
|
||||||
|
const mockResourceRootNode: azureResource.IAzureResourceNode = {
|
||||||
|
account: mockAccount,
|
||||||
|
subscription: mockSubscription,
|
||||||
|
tenantId: mockTenantId,
|
||||||
|
treeItem: {
|
||||||
|
id: 'mock_resource_root_node',
|
||||||
|
label: 'mock resource root node',
|
||||||
|
iconPath: undefined,
|
||||||
|
collapsibleState: vscode.TreeItemCollapsibleState.Collapsed,
|
||||||
|
contextValue: 'mock_resource_root_node'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockTokens = {};
|
||||||
|
mockTokens[mockTenantId] = {
|
||||||
|
token: 'mock_token',
|
||||||
|
tokenType: 'Bearer'
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockDatabaseServers: AzureResourceDatabaseServer[] = [
|
||||||
|
{
|
||||||
|
name: 'mock database server 1',
|
||||||
|
fullName: 'mock database server full name 1',
|
||||||
|
loginName: 'mock login',
|
||||||
|
defaultDatabaseName: 'master'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'mock database server 2',
|
||||||
|
fullName: 'mock database server full name 2',
|
||||||
|
loginName: 'mock login',
|
||||||
|
defaultDatabaseName: 'master'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
describe('AzureResourceDatabaseServerTreeDataProvider.info', function(): void {
|
||||||
|
beforeEach(() => {
|
||||||
|
mockDatabaseServerService = TypeMoq.Mock.ofType<IAzureResourceDatabaseServerService>();
|
||||||
|
mockApiWrapper = TypeMoq.Mock.ofType<ApiWrapper>();
|
||||||
|
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should be correct when created.', async function(): Promise<void> {
|
||||||
|
const treeDataProvider = new AzureResourceDatabaseServerTreeDataProvider(mockDatabaseServerService.object, mockApiWrapper.object, mockExtensionContext.object);
|
||||||
|
|
||||||
|
const treeItem = await treeDataProvider.getTreeItem(mockResourceRootNode);
|
||||||
|
should(treeItem.id).equal(mockResourceRootNode.treeItem.id);
|
||||||
|
should(treeItem.label).equal(mockResourceRootNode.treeItem.label);
|
||||||
|
should(treeItem.collapsibleState).equal(mockResourceRootNode.treeItem.collapsibleState);
|
||||||
|
should(treeItem.contextValue).equal(mockResourceRootNode.treeItem.contextValue);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('AzureResourceDatabaseServerTreeDataProvider.getChildren', function(): void {
|
||||||
|
beforeEach(() => {
|
||||||
|
mockDatabaseServerService = TypeMoq.Mock.ofType<IAzureResourceDatabaseServerService>();
|
||||||
|
mockApiWrapper = TypeMoq.Mock.ofType<ApiWrapper>();
|
||||||
|
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
|
||||||
|
|
||||||
|
mockApiWrapper.setup((o) => o.getSecurityToken(mockAccount, sqlops.AzureResource.ResourceManagement)).returns(() => Promise.resolve(mockTokens));
|
||||||
|
mockDatabaseServerService.setup((o) => o.getDatabaseServers(mockSubscription, TypeMoq.It.isAny())).returns(() => Promise.resolve(mockDatabaseServers));
|
||||||
|
mockExtensionContext.setup((o) => o.asAbsolutePath(TypeMoq.It.isAnyString())).returns(() => TypeMoq.It.isAnyString());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should return container node when element is undefined.', async function(): Promise<void> {
|
||||||
|
const treeDataProvider = new AzureResourceDatabaseServerTreeDataProvider(mockDatabaseServerService.object, mockApiWrapper.object, mockExtensionContext.object);
|
||||||
|
|
||||||
|
const children = await treeDataProvider.getChildren();
|
||||||
|
|
||||||
|
should(children).Array();
|
||||||
|
should(children.length).equal(1);
|
||||||
|
|
||||||
|
const child = children[0];
|
||||||
|
should(child.account).undefined();
|
||||||
|
should(child.subscription).undefined();
|
||||||
|
should(child.tenantId).undefined();
|
||||||
|
should(child.treeItem.id).equal('azure.resource.providers.databaseServer.treeDataProvider.databaseServerContainer');
|
||||||
|
should(child.treeItem.label).equal('SQL Servers');
|
||||||
|
should(child.treeItem.collapsibleState).equal(vscode.TreeItemCollapsibleState.Collapsed);
|
||||||
|
should(child.treeItem.contextValue).equal('azure.resource.itemType.databaseServerContainer');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should return resource nodes when it is container node.', async function(): Promise<void> {
|
||||||
|
const treeDataProvider = new AzureResourceDatabaseServerTreeDataProvider(mockDatabaseServerService.object, mockApiWrapper.object, mockExtensionContext.object);
|
||||||
|
|
||||||
|
const children = await treeDataProvider.getChildren(mockResourceRootNode);
|
||||||
|
|
||||||
|
should(children).Array();
|
||||||
|
should(children.length).equal(mockDatabaseServers.length);
|
||||||
|
|
||||||
|
for (let ix = 0; ix < children.length; ix++) {
|
||||||
|
const child = children[ix];
|
||||||
|
const databaseServer = mockDatabaseServers[ix];
|
||||||
|
|
||||||
|
should(child.account).equal(mockAccount);
|
||||||
|
should(child.subscription).equal(mockSubscription);
|
||||||
|
should(child.tenantId).equal(mockTenantId);
|
||||||
|
should(child.treeItem.id).equal(`databaseServer_${databaseServer.name}`);
|
||||||
|
should(child.treeItem.label).equal(databaseServer.name);
|
||||||
|
should(child.treeItem.collapsibleState).equal(vscode.TreeItemCollapsibleState.None);
|
||||||
|
should(child.treeItem.contextValue).equal(AzureResourceItemType.databaseServer);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,180 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import * as should from 'should';
|
||||||
|
import * as TypeMoq from 'typemoq';
|
||||||
|
import * as sqlops from 'sqlops';
|
||||||
|
import 'mocha';
|
||||||
|
import { fail } from 'assert';
|
||||||
|
|
||||||
|
import { azureResource } from '../../azureResource/azure-resource';
|
||||||
|
import { AzureResourceService } from '../../azureResource/resourceService';
|
||||||
|
|
||||||
|
// Mock test data
|
||||||
|
const mockAccount: sqlops.Account = {
|
||||||
|
key: {
|
||||||
|
accountId: 'mock_account',
|
||||||
|
providerId: 'mock_provider'
|
||||||
|
},
|
||||||
|
displayInfo: {
|
||||||
|
displayName: 'mock_account@test.com',
|
||||||
|
accountType: 'Microsoft',
|
||||||
|
contextualDisplayName: 'test'
|
||||||
|
},
|
||||||
|
properties: undefined,
|
||||||
|
isStale: false
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockSubscription: azureResource.AzureResourceSubscription = {
|
||||||
|
id: 'mock_subscription',
|
||||||
|
name: 'mock subscription'
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockTenantId: string = 'mock_tenant';
|
||||||
|
|
||||||
|
let mockResourceTreeDataProvider1: TypeMoq.IMock<azureResource.IAzureResourceTreeDataProvider>;
|
||||||
|
let mockResourceProvider1: TypeMoq.IMock<azureResource.IAzureResourceProvider>;
|
||||||
|
|
||||||
|
let mockResourceTreeDataProvider2: TypeMoq.IMock<azureResource.IAzureResourceTreeDataProvider>;
|
||||||
|
let mockResourceProvider2: TypeMoq.IMock<azureResource.IAzureResourceProvider>;
|
||||||
|
|
||||||
|
const resourceService: AzureResourceService = AzureResourceService.getInstance();
|
||||||
|
|
||||||
|
describe('AzureResourceService.listResourceProviderIds', function(): void {
|
||||||
|
beforeEach(() => {
|
||||||
|
mockResourceTreeDataProvider1 = TypeMoq.Mock.ofType<azureResource.IAzureResourceTreeDataProvider>();
|
||||||
|
mockResourceTreeDataProvider1.setup((o) => o.getChildren()).returns(() => Promise.resolve([TypeMoq.Mock.ofType<azureResource.IAzureResourceNode>().object]));
|
||||||
|
mockResourceTreeDataProvider1.setup((o) => o.getTreeItem(TypeMoq.It.isAny())).returns(() => Promise.resolve(TypeMoq.It.isAny()));
|
||||||
|
mockResourceProvider1 = TypeMoq.Mock.ofType<azureResource.IAzureResourceProvider>();
|
||||||
|
mockResourceProvider1.setup((o) => o.providerId).returns(() => 'mockResourceProvider1');
|
||||||
|
mockResourceProvider1.setup((o) => o.getTreeDataProvider()).returns(() => mockResourceTreeDataProvider1.object);
|
||||||
|
|
||||||
|
mockResourceTreeDataProvider2 = TypeMoq.Mock.ofType<azureResource.IAzureResourceTreeDataProvider>();
|
||||||
|
mockResourceTreeDataProvider2.setup((o) => o.getChildren()).returns(() => Promise.resolve([TypeMoq.Mock.ofType<azureResource.IAzureResourceNode>().object]));
|
||||||
|
mockResourceTreeDataProvider2.setup((o) => o.getTreeItem(TypeMoq.It.isAny())).returns(() => Promise.resolve(TypeMoq.It.isAny()));
|
||||||
|
mockResourceProvider2 = TypeMoq.Mock.ofType<azureResource.IAzureResourceProvider>();
|
||||||
|
mockResourceProvider2.setup((o) => o.providerId).returns(() => 'mockResourceProvider2');
|
||||||
|
mockResourceProvider2.setup((o) => o.getTreeDataProvider()).returns(() => mockResourceTreeDataProvider2.object);
|
||||||
|
|
||||||
|
resourceService.clearResourceProviders();
|
||||||
|
resourceService.areResourceProvidersLoaded = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should be correct when registering providers.', async function(): Promise<void> {
|
||||||
|
resourceService.registerResourceProvider(mockResourceProvider1.object);
|
||||||
|
let providerIds = await resourceService.listResourceProviderIds();
|
||||||
|
should(providerIds).Array();
|
||||||
|
should(providerIds.length).equal(1);
|
||||||
|
should(providerIds[0]).equal(mockResourceProvider1.object.providerId);
|
||||||
|
|
||||||
|
resourceService.registerResourceProvider(mockResourceProvider2.object);
|
||||||
|
providerIds = await resourceService.listResourceProviderIds();
|
||||||
|
should(providerIds).Array();
|
||||||
|
should(providerIds.length).equal(2);
|
||||||
|
should(providerIds[0]).equal(mockResourceProvider1.object.providerId);
|
||||||
|
should(providerIds[1]).equal(mockResourceProvider2.object.providerId);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('AzureResourceService.getRootChildren', function(): void {
|
||||||
|
beforeEach(() => {
|
||||||
|
mockResourceTreeDataProvider1 = TypeMoq.Mock.ofType<azureResource.IAzureResourceTreeDataProvider>();
|
||||||
|
mockResourceTreeDataProvider1.setup((o) => o.getChildren()).returns(() => Promise.resolve([TypeMoq.Mock.ofType<azureResource.IAzureResourceNode>().object]));
|
||||||
|
mockResourceTreeDataProvider1.setup((o) => o.getTreeItem(TypeMoq.It.isAny())).returns(() => Promise.resolve(TypeMoq.It.isAny()));
|
||||||
|
mockResourceProvider1 = TypeMoq.Mock.ofType<azureResource.IAzureResourceProvider>();
|
||||||
|
mockResourceProvider1.setup((o) => o.providerId).returns(() => 'mockResourceProvider1');
|
||||||
|
mockResourceProvider1.setup((o) => o.getTreeDataProvider()).returns(() => mockResourceTreeDataProvider1.object);
|
||||||
|
|
||||||
|
resourceService.clearResourceProviders();
|
||||||
|
resourceService.registerResourceProvider(mockResourceProvider1.object);
|
||||||
|
resourceService.areResourceProvidersLoaded = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should be correct when provider id is correct.', async function(): Promise<void> {
|
||||||
|
const children = await resourceService.getRootChildren(mockResourceProvider1.object.providerId, mockAccount, mockSubscription, mockTenantId);
|
||||||
|
|
||||||
|
should(children).Array();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should throw exceptions when provider id is incorrect.', async function(): Promise<void> {
|
||||||
|
const providerId = 'non_existent_provider_id';
|
||||||
|
try {
|
||||||
|
await resourceService.getRootChildren(providerId, mockAccount, mockSubscription, mockTenantId);
|
||||||
|
} catch (error) {
|
||||||
|
should(error.message).equal(`Azure resource provider doesn't exist. Id: ${providerId}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fail();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('AzureResourceService.getChildren', function(): void {
|
||||||
|
beforeEach(() => {
|
||||||
|
mockResourceTreeDataProvider1 = TypeMoq.Mock.ofType<azureResource.IAzureResourceTreeDataProvider>();
|
||||||
|
mockResourceTreeDataProvider1.setup((o) => o.getChildren()).returns(() => Promise.resolve([TypeMoq.Mock.ofType<azureResource.IAzureResourceNode>().object]));
|
||||||
|
mockResourceTreeDataProvider1.setup((o) => o.getChildren(TypeMoq.It.isAny())).returns(() => Promise.resolve([TypeMoq.Mock.ofType<azureResource.IAzureResourceNode>().object]));
|
||||||
|
mockResourceTreeDataProvider1.setup((o) => o.getTreeItem(TypeMoq.It.isAny())).returns(() => Promise.resolve(TypeMoq.It.isAny()));
|
||||||
|
mockResourceProvider1 = TypeMoq.Mock.ofType<azureResource.IAzureResourceProvider>();
|
||||||
|
mockResourceProvider1.setup((o) => o.providerId).returns(() => 'mockResourceProvider1');
|
||||||
|
mockResourceProvider1.setup((o) => o.getTreeDataProvider()).returns(() => mockResourceTreeDataProvider1.object);
|
||||||
|
|
||||||
|
resourceService.clearResourceProviders();
|
||||||
|
resourceService.registerResourceProvider(mockResourceProvider1.object);
|
||||||
|
resourceService.areResourceProvidersLoaded = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should be correct when provider id is correct.', async function(): Promise<void> {
|
||||||
|
const children = await resourceService.getChildren(mockResourceProvider1.object.providerId, TypeMoq.It.isAny());
|
||||||
|
should(children).Array();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should throw exceptions when provider id is incorrect.', async function(): Promise<void> {
|
||||||
|
const providerId = 'non_existent_provider_id';
|
||||||
|
try {
|
||||||
|
await resourceService.getRootChildren(providerId, mockAccount, mockSubscription, mockTenantId);
|
||||||
|
} catch (error) {
|
||||||
|
should(error.message).equal(`Azure resource provider doesn't exist. Id: ${providerId}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fail();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('AzureResourceService.getTreeItem', function(): void {
|
||||||
|
beforeEach(() => {
|
||||||
|
mockResourceTreeDataProvider1 = TypeMoq.Mock.ofType<azureResource.IAzureResourceTreeDataProvider>();
|
||||||
|
mockResourceTreeDataProvider1.setup((o) => o.getChildren()).returns(() => Promise.resolve([TypeMoq.Mock.ofType<azureResource.IAzureResourceNode>().object]));
|
||||||
|
mockResourceTreeDataProvider1.setup((o) => o.getChildren(TypeMoq.It.isAny())).returns(() => Promise.resolve([TypeMoq.Mock.ofType<azureResource.IAzureResourceNode>().object]));
|
||||||
|
mockResourceTreeDataProvider1.setup((o) => o.getTreeItem(TypeMoq.It.isAny())).returns(() => Promise.resolve(TypeMoq.It.isAny()));
|
||||||
|
mockResourceProvider1 = TypeMoq.Mock.ofType<azureResource.IAzureResourceProvider>();
|
||||||
|
mockResourceProvider1.setup((o) => o.providerId).returns(() => 'mockResourceProvider1');
|
||||||
|
mockResourceProvider1.setup((o) => o.getTreeDataProvider()).returns(() => mockResourceTreeDataProvider1.object);
|
||||||
|
|
||||||
|
resourceService.clearResourceProviders();
|
||||||
|
resourceService.registerResourceProvider(mockResourceProvider1.object);
|
||||||
|
resourceService.areResourceProvidersLoaded = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should be correct when provider id is correct.', async function(): Promise<void> {
|
||||||
|
const treeItem = await resourceService.getTreeItem(mockResourceProvider1.object.providerId, TypeMoq.It.isAny());
|
||||||
|
should(treeItem).Object();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should throw exceptions when provider id is incorrect.', async function(): Promise<void> {
|
||||||
|
const providerId = 'non_existent_provider_id';
|
||||||
|
try {
|
||||||
|
await resourceService.getRootChildren(providerId, mockAccount, mockSubscription, mockTenantId);
|
||||||
|
} catch (error) {
|
||||||
|
should(error.message).equal(`Azure resource provider doesn't exist. Id: ${providerId}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fail();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,184 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import * as should from 'should';
|
||||||
|
import * as TypeMoq from 'typemoq';
|
||||||
|
import * as sqlops from 'sqlops';
|
||||||
|
import * as vscode from 'vscode';
|
||||||
|
import 'mocha';
|
||||||
|
|
||||||
|
import { azureResource } from '../../azureResource/azure-resource';
|
||||||
|
import { AzureResourceService } from '../../azureResource/resourceService';
|
||||||
|
import { AzureResourceResourceTreeNode } from '../../azureResource/resourceTreeNode';
|
||||||
|
|
||||||
|
const resourceService = AzureResourceService.getInstance();
|
||||||
|
|
||||||
|
// Mock test data
|
||||||
|
const mockAccount: sqlops.Account = {
|
||||||
|
key: {
|
||||||
|
accountId: 'mock_account',
|
||||||
|
providerId: 'mock_provider'
|
||||||
|
},
|
||||||
|
displayInfo: {
|
||||||
|
displayName: 'mock_account@test.com',
|
||||||
|
accountType: 'Microsoft',
|
||||||
|
contextualDisplayName: 'test'
|
||||||
|
},
|
||||||
|
properties: undefined,
|
||||||
|
isStale: false
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockSubscription: azureResource.AzureResourceSubscription = {
|
||||||
|
id: 'mock_subscription',
|
||||||
|
name: 'mock subscription'
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockTenantId: string = 'mock_tenant';
|
||||||
|
|
||||||
|
const mockResourceProviderId: string = 'mock_resource_provider';
|
||||||
|
|
||||||
|
const mockResourceRootNode: azureResource.IAzureResourceNode = {
|
||||||
|
account: mockAccount,
|
||||||
|
subscription: mockSubscription,
|
||||||
|
tenantId: mockTenantId,
|
||||||
|
treeItem: {
|
||||||
|
id: 'mock_resource_root_node',
|
||||||
|
label: 'mock resource root node',
|
||||||
|
iconPath: undefined,
|
||||||
|
collapsibleState: vscode.TreeItemCollapsibleState.Collapsed,
|
||||||
|
contextValue: 'mock_resource_root_node'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockResourceNode1: azureResource.IAzureResourceNode = {
|
||||||
|
account: mockAccount,
|
||||||
|
subscription: mockSubscription,
|
||||||
|
tenantId: mockTenantId,
|
||||||
|
treeItem: {
|
||||||
|
id: 'mock_resource_node_1',
|
||||||
|
label: 'mock resource node 1',
|
||||||
|
iconPath: undefined,
|
||||||
|
collapsibleState: vscode.TreeItemCollapsibleState.None,
|
||||||
|
contextValue: 'mock_resource_node'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockResourceNode2: azureResource.IAzureResourceNode = {
|
||||||
|
account: mockAccount,
|
||||||
|
subscription: mockSubscription,
|
||||||
|
tenantId: mockTenantId,
|
||||||
|
treeItem: {
|
||||||
|
id: 'mock_resource_node_2',
|
||||||
|
label: 'mock resource node 2',
|
||||||
|
iconPath: undefined,
|
||||||
|
collapsibleState: vscode.TreeItemCollapsibleState.None,
|
||||||
|
contextValue: 'mock_resource_node'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockResourceNodes: azureResource.IAzureResourceNode[] = [mockResourceNode1, mockResourceNode2];
|
||||||
|
|
||||||
|
let mockResourceTreeDataProvider: TypeMoq.IMock<azureResource.IAzureResourceTreeDataProvider>;
|
||||||
|
let mockResourceProvider: TypeMoq.IMock<azureResource.IAzureResourceProvider>;
|
||||||
|
|
||||||
|
describe('AzureResourceResourceTreeNode.info', function(): void {
|
||||||
|
beforeEach(() => {
|
||||||
|
mockResourceTreeDataProvider = TypeMoq.Mock.ofType<azureResource.IAzureResourceTreeDataProvider>();
|
||||||
|
mockResourceTreeDataProvider.setup((o) => o.getTreeItem(mockResourceRootNode)).returns(() => mockResourceRootNode.treeItem);
|
||||||
|
mockResourceTreeDataProvider.setup((o) => o.getChildren(mockResourceRootNode)).returns(() => Promise.resolve(mockResourceNodes));
|
||||||
|
|
||||||
|
mockResourceProvider = TypeMoq.Mock.ofType<azureResource.IAzureResourceProvider>();
|
||||||
|
mockResourceProvider.setup((o) => o.providerId).returns(() => mockResourceProviderId);
|
||||||
|
mockResourceProvider.setup((o) => o.getTreeDataProvider()).returns(() => mockResourceTreeDataProvider.object);
|
||||||
|
|
||||||
|
resourceService.clearResourceProviders();
|
||||||
|
resourceService.registerResourceProvider(mockResourceProvider.object);
|
||||||
|
|
||||||
|
resourceService.areResourceProvidersLoaded = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should be correct when created.', async function(): Promise<void> {
|
||||||
|
const resourceTreeNode = new AzureResourceResourceTreeNode({
|
||||||
|
resourceProviderId: mockResourceProviderId,
|
||||||
|
resourceNode: mockResourceRootNode
|
||||||
|
}, undefined);
|
||||||
|
|
||||||
|
should(resourceTreeNode.nodePathValue).equal(mockResourceRootNode.treeItem.id);
|
||||||
|
|
||||||
|
const treeItem = await resourceTreeNode.getTreeItem();
|
||||||
|
should(treeItem.id).equal(mockResourceRootNode.treeItem.id);
|
||||||
|
should(treeItem.label).equal(mockResourceRootNode.treeItem.label);
|
||||||
|
should(treeItem.collapsibleState).equal(mockResourceRootNode.treeItem.collapsibleState);
|
||||||
|
should(treeItem.contextValue).equal(mockResourceRootNode.treeItem.contextValue);
|
||||||
|
|
||||||
|
const nodeInfo = resourceTreeNode.getNodeInfo();
|
||||||
|
should(nodeInfo.label).equal(mockResourceRootNode.treeItem.label);
|
||||||
|
should(nodeInfo.isLeaf).equal(mockResourceRootNode.treeItem.collapsibleState === vscode.TreeItemCollapsibleState.None);
|
||||||
|
should(nodeInfo.nodeType).equal(mockResourceRootNode.treeItem.contextValue);
|
||||||
|
should(nodeInfo.iconType).equal(mockResourceRootNode.treeItem.contextValue);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('AzureResourceResourceTreeNode.getChildren', function(): void {
|
||||||
|
beforeEach(() => {
|
||||||
|
mockResourceTreeDataProvider = TypeMoq.Mock.ofType<azureResource.IAzureResourceTreeDataProvider>();
|
||||||
|
mockResourceTreeDataProvider.setup((o) => o.getChildren(mockResourceRootNode)).returns(() => Promise.resolve(mockResourceNodes));
|
||||||
|
|
||||||
|
mockResourceProvider = TypeMoq.Mock.ofType<azureResource.IAzureResourceProvider>();
|
||||||
|
mockResourceProvider.setup((o) => o.providerId).returns(() => mockResourceProviderId);
|
||||||
|
mockResourceProvider.setup((o) => o.getTreeDataProvider()).returns(() => mockResourceTreeDataProvider.object);
|
||||||
|
|
||||||
|
resourceService.clearResourceProviders();
|
||||||
|
resourceService.registerResourceProvider(mockResourceProvider.object);
|
||||||
|
|
||||||
|
resourceService.areResourceProvidersLoaded = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should return resource nodes when it is container node.', async function(): Promise<void> {
|
||||||
|
const resourceTreeNode = new AzureResourceResourceTreeNode({
|
||||||
|
resourceProviderId: mockResourceProviderId,
|
||||||
|
resourceNode: mockResourceRootNode
|
||||||
|
}, undefined);
|
||||||
|
|
||||||
|
const children = await resourceTreeNode.getChildren();
|
||||||
|
|
||||||
|
mockResourceTreeDataProvider.verify((o) => o.getChildren(mockResourceRootNode), TypeMoq.Times.once());
|
||||||
|
|
||||||
|
should(children).Array();
|
||||||
|
should(children.length).equal(mockResourceNodes.length);
|
||||||
|
|
||||||
|
for (let ix = 0; ix < children.length; ix++) {
|
||||||
|
const child = children[ix];
|
||||||
|
|
||||||
|
should(child).instanceOf(AzureResourceResourceTreeNode);
|
||||||
|
|
||||||
|
const childNode = (child as AzureResourceResourceTreeNode).resourceNodeWithProviderId;
|
||||||
|
should(childNode.resourceProviderId).equal(mockResourceProviderId);
|
||||||
|
should(childNode.resourceNode.account).equal(mockAccount);
|
||||||
|
should(childNode.resourceNode.subscription).equal(mockSubscription);
|
||||||
|
should(childNode.resourceNode.tenantId).equal(mockTenantId);
|
||||||
|
should(childNode.resourceNode.treeItem.id).equal(mockResourceNodes[ix].treeItem.id);
|
||||||
|
should(childNode.resourceNode.treeItem.label).equal(mockResourceNodes[ix].treeItem.label);
|
||||||
|
should(childNode.resourceNode.treeItem.collapsibleState).equal(mockResourceNodes[ix].treeItem.collapsibleState);
|
||||||
|
should(childNode.resourceNode.treeItem.contextValue).equal(mockResourceNodes[ix].treeItem.contextValue);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should return empty when it is leaf node.', async function(): Promise<void> {
|
||||||
|
const resourceTreeNode = new AzureResourceResourceTreeNode({
|
||||||
|
resourceProviderId: mockResourceProviderId,
|
||||||
|
resourceNode: mockResourceNode1
|
||||||
|
}, undefined);
|
||||||
|
|
||||||
|
const children = await resourceTreeNode.getChildren();
|
||||||
|
|
||||||
|
mockResourceTreeDataProvider.verify((o) => o.getChildren(), TypeMoq.Times.exactly(0));
|
||||||
|
|
||||||
|
should(children).Array();
|
||||||
|
should(children.length).equal(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -26,7 +26,7 @@ describe('AzureResourceAccountNotSignedInTreeNode.info', function(): void {
|
|||||||
should(treeItem.collapsibleState).equal(vscode.TreeItemCollapsibleState.None);
|
should(treeItem.collapsibleState).equal(vscode.TreeItemCollapsibleState.None);
|
||||||
should(treeItem.command).not.undefined();
|
should(treeItem.command).not.undefined();
|
||||||
should(treeItem.command.title).equal(label);
|
should(treeItem.command.title).equal(label);
|
||||||
should(treeItem.command.command).equal('azureresource.signin');
|
should(treeItem.command.command).equal('azure.resource.signin');
|
||||||
|
|
||||||
const nodeInfo = treeNode.getNodeInfo();
|
const nodeInfo = treeNode.getNodeInfo();
|
||||||
should(nodeInfo.isLeaf).true();
|
should(nodeInfo.isLeaf).true();
|
||||||
|
|||||||
@@ -10,35 +10,38 @@ import * as TypeMoq from 'typemoq';
|
|||||||
import * as sqlops from 'sqlops';
|
import * as sqlops from 'sqlops';
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import 'mocha';
|
import 'mocha';
|
||||||
import { ServiceClientCredentials } from 'ms-rest';
|
import { TokenCredentials } from 'ms-rest';
|
||||||
|
import { AppContext } from '../../../appContext';
|
||||||
|
|
||||||
import { AzureResourceServicePool } from '../../../azureResource/servicePool';
|
import { azureResource } from '../../../azureResource/azure-resource';
|
||||||
import {
|
import {
|
||||||
IAzureResourceCacheService,
|
IAzureResourceCacheService,
|
||||||
IAzureResourceContextService,
|
|
||||||
IAzureResourceCredentialService,
|
|
||||||
IAzureResourceSubscriptionService,
|
IAzureResourceSubscriptionService,
|
||||||
IAzureResourceSubscriptionFilterService
|
IAzureResourceSubscriptionFilterService,
|
||||||
|
IAzureResourceTenantService
|
||||||
} from '../../../azureResource/interfaces';
|
} from '../../../azureResource/interfaces';
|
||||||
import { IAzureResourceTreeChangeHandler } from '../../../azureResource/tree/treeChangeHandler';
|
import { IAzureResourceTreeChangeHandler } from '../../../azureResource/tree/treeChangeHandler';
|
||||||
import { AzureResourceAccountTreeNode } from '../../../azureResource/tree/accountTreeNode';
|
import { AzureResourceAccountTreeNode } from '../../../azureResource/tree/accountTreeNode';
|
||||||
import { AzureResourceSubscription } from '../../../azureResource/models';
|
|
||||||
import { AzureResourceSubscriptionTreeNode } from '../../../azureResource/tree/subscriptionTreeNode';
|
import { AzureResourceSubscriptionTreeNode } from '../../../azureResource/tree/subscriptionTreeNode';
|
||||||
import { AzureResourceItemType } from '../../../azureResource/constants';
|
import { AzureResourceItemType, AzureResourceServiceNames } from '../../../azureResource/constants';
|
||||||
import { AzureResourceMessageTreeNode } from '../../../azureResource/tree/messageTreeNode';
|
import { AzureResourceMessageTreeNode } from '../../../azureResource/messageTreeNode';
|
||||||
|
import { ApiWrapper } from '../../../apiWrapper';
|
||||||
|
import { generateGuid } from '../../../azureResource/utils';
|
||||||
|
|
||||||
// Mock services
|
// Mock services
|
||||||
const mockServicePool = AzureResourceServicePool.getInstance();
|
let mockExtensionContext: TypeMoq.IMock<vscode.ExtensionContext>;
|
||||||
|
let mockApiWrapper: TypeMoq.IMock<ApiWrapper>;
|
||||||
let mockCacheService: TypeMoq.IMock<IAzureResourceCacheService>;
|
let mockCacheService: TypeMoq.IMock<IAzureResourceCacheService>;
|
||||||
let mockContextService: TypeMoq.IMock<IAzureResourceContextService>;
|
|
||||||
let mockCredentialService: TypeMoq.IMock<IAzureResourceCredentialService>;
|
|
||||||
let mockSubscriptionService: TypeMoq.IMock<IAzureResourceSubscriptionService>;
|
let mockSubscriptionService: TypeMoq.IMock<IAzureResourceSubscriptionService>;
|
||||||
let mockSubscriptionFilterService: TypeMoq.IMock<IAzureResourceSubscriptionFilterService>;
|
let mockSubscriptionFilterService: TypeMoq.IMock<IAzureResourceSubscriptionFilterService>;
|
||||||
|
let mockTenantService: TypeMoq.IMock<IAzureResourceTenantService>;
|
||||||
|
let mockAppContext: AppContext;
|
||||||
|
|
||||||
let mockTreeChangeHandler: TypeMoq.IMock<IAzureResourceTreeChangeHandler>;
|
let mockTreeChangeHandler: TypeMoq.IMock<IAzureResourceTreeChangeHandler>;
|
||||||
|
|
||||||
// Mock test data
|
// Mock test data
|
||||||
|
const mockTenantId = 'mock_tenant_id';
|
||||||
|
|
||||||
const mockAccount: sqlops.Account = {
|
const mockAccount: sqlops.Account = {
|
||||||
key: {
|
key: {
|
||||||
accountId: 'mock_account',
|
accountId: 'mock_account',
|
||||||
@@ -49,51 +52,68 @@ const mockAccount: sqlops.Account = {
|
|||||||
accountType: 'Microsoft',
|
accountType: 'Microsoft',
|
||||||
contextualDisplayName: 'test'
|
contextualDisplayName: 'test'
|
||||||
},
|
},
|
||||||
properties: undefined,
|
properties: {
|
||||||
|
tenants: [
|
||||||
|
{
|
||||||
|
id: mockTenantId
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
isStale: false
|
isStale: false
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockCredential = TypeMoq.Mock.ofType<ServiceClientCredentials>().object;
|
const mockSubscription1: azureResource.AzureResourceSubscription = {
|
||||||
const mockCredentials = [mockCredential];
|
|
||||||
|
|
||||||
const mockSubscription1: AzureResourceSubscription = {
|
|
||||||
id: 'mock_subscription_1',
|
id: 'mock_subscription_1',
|
||||||
name: 'mock subscription 1'
|
name: 'mock subscription 1'
|
||||||
};
|
};
|
||||||
const mockSubscription2: AzureResourceSubscription = {
|
|
||||||
|
const mockSubscription2: azureResource.AzureResourceSubscription = {
|
||||||
id: 'mock_subscription_2',
|
id: 'mock_subscription_2',
|
||||||
name: 'mock subscription 2'
|
name: 'mock subscription 2'
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockSubscriptions = [mockSubscription1, mockSubscription2];
|
const mockSubscriptions = [mockSubscription1, mockSubscription2];
|
||||||
|
|
||||||
const mockFilteredSubscriptions = [mockSubscription1];
|
const mockFilteredSubscriptions = [mockSubscription1];
|
||||||
|
|
||||||
let mockSubscriptionCache: { subscriptions: { [accountId: string]: AzureResourceSubscription[]} };
|
const mockTokens = {};
|
||||||
|
mockTokens[mockTenantId] = {
|
||||||
|
token: 'mock_token',
|
||||||
|
tokenType: 'Bearer'
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockCredential = new TokenCredentials(mockTokens[mockTenantId].token, mockTokens[mockTenantId].tokenType);
|
||||||
|
|
||||||
|
let mockSubscriptionCache: azureResource.AzureResourceSubscription[] = [];
|
||||||
|
|
||||||
describe('AzureResourceAccountTreeNode.info', function(): void {
|
describe('AzureResourceAccountTreeNode.info', function(): void {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockContextService = TypeMoq.Mock.ofType<IAzureResourceContextService>();
|
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
|
||||||
|
mockApiWrapper = TypeMoq.Mock.ofType<ApiWrapper>();
|
||||||
mockCacheService = TypeMoq.Mock.ofType<IAzureResourceCacheService>();
|
mockCacheService = TypeMoq.Mock.ofType<IAzureResourceCacheService>();
|
||||||
mockCredentialService = TypeMoq.Mock.ofType<IAzureResourceCredentialService>();
|
|
||||||
mockSubscriptionService = TypeMoq.Mock.ofType<IAzureResourceSubscriptionService>();
|
mockSubscriptionService = TypeMoq.Mock.ofType<IAzureResourceSubscriptionService>();
|
||||||
mockSubscriptionFilterService = TypeMoq.Mock.ofType<IAzureResourceSubscriptionFilterService>();
|
mockSubscriptionFilterService = TypeMoq.Mock.ofType<IAzureResourceSubscriptionFilterService>();
|
||||||
|
mockTenantService = TypeMoq.Mock.ofType<IAzureResourceTenantService>();
|
||||||
|
|
||||||
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
|
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
|
||||||
|
|
||||||
mockSubscriptionCache = { subscriptions: {} };
|
mockSubscriptionCache = [];
|
||||||
|
|
||||||
mockServicePool.contextService = mockContextService.object;
|
mockAppContext = new AppContext(mockExtensionContext.object, mockApiWrapper.object);
|
||||||
mockServicePool.cacheService = mockCacheService.object;
|
mockAppContext.registerService<IAzureResourceCacheService>(AzureResourceServiceNames.cacheService, mockCacheService.object);
|
||||||
mockServicePool.credentialService = mockCredentialService.object;
|
mockAppContext.registerService<IAzureResourceSubscriptionService>(AzureResourceServiceNames.subscriptionService, mockSubscriptionService.object);
|
||||||
mockServicePool.subscriptionService = mockSubscriptionService.object;
|
mockAppContext.registerService<IAzureResourceSubscriptionFilterService>(AzureResourceServiceNames.subscriptionFilterService, mockSubscriptionFilterService.object);
|
||||||
mockServicePool.subscriptionFilterService = mockSubscriptionFilterService.object;
|
mockAppContext.registerService<IAzureResourceTenantService>(AzureResourceServiceNames.tenantService, mockTenantService.object);
|
||||||
|
|
||||||
mockCredentialService.setup((o) => o.getCredentials(mockAccount)).returns(() => Promise.resolve(mockCredentials));
|
mockApiWrapper.setup((o) => o.getSecurityToken(mockAccount, sqlops.AzureResource.ResourceManagement)).returns(() => Promise.resolve(mockTokens));
|
||||||
|
mockCacheService.setup((o) => o.generateKey(TypeMoq.It.isAnyString())).returns(() => generateGuid());
|
||||||
mockCacheService.setup((o) => o.get(TypeMoq.It.isAnyString())).returns(() => mockSubscriptionCache);
|
mockCacheService.setup((o) => o.get(TypeMoq.It.isAnyString())).returns(() => mockSubscriptionCache);
|
||||||
mockCacheService.setup((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny())).returns(() => mockSubscriptionCache.subscriptions[mockAccount.key.accountId] = mockSubscriptions);
|
mockCacheService.setup((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny())).returns(() => mockSubscriptionCache = mockSubscriptions);
|
||||||
|
mockTenantService.setup((o) => o.getTenantId(TypeMoq.It.isAny())).returns(() => Promise.resolve(mockTenantId));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should be correct when created.', async function(): Promise<void> {
|
it('Should be correct when created.', async function(): Promise<void> {
|
||||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockTreeChangeHandler.object);
|
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
|
||||||
|
|
||||||
const accountTreeNodeId = `account_${mockAccount.key.accountId}`;
|
const accountTreeNodeId = `account_${mockAccount.key.accountId}`;
|
||||||
const accountTreeNodeLabel = `${mockAccount.displayInfo.displayName} (${mockAccount.key.accountId})`;
|
const accountTreeNodeLabel = `${mockAccount.displayInfo.displayName} (${mockAccount.key.accountId})`;
|
||||||
@@ -114,14 +134,17 @@ describe('AzureResourceAccountTreeNode.info', function(): void {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('Should be correct when there are subscriptions listed.', async function(): Promise<void> {
|
it('Should be correct when there are subscriptions listed.', async function(): Promise<void> {
|
||||||
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, mockCredentials)).returns(() => Promise.resolve(mockSubscriptions));
|
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, mockCredential)).returns(() => Promise.resolve(mockSubscriptions));
|
||||||
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => Promise.resolve(undefined));
|
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => Promise.resolve(undefined));
|
||||||
|
|
||||||
const accountTreeNodeLabel = `${mockAccount.displayInfo.displayName} (${mockAccount.key.accountId}) (${mockSubscriptions.length} / ${mockSubscriptions.length} subscriptions)`;
|
const accountTreeNodeLabel = `${mockAccount.displayInfo.displayName} (${mockAccount.key.accountId}) (${mockSubscriptions.length} / ${mockSubscriptions.length} subscriptions)`;
|
||||||
|
|
||||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockTreeChangeHandler.object);
|
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
|
||||||
|
|
||||||
await accountTreeNode.getChildren();
|
const subscriptionNodes = await accountTreeNode.getChildren();
|
||||||
|
|
||||||
|
should(subscriptionNodes).Array();
|
||||||
|
should(subscriptionNodes.length).equal(mockSubscriptions.length);
|
||||||
|
|
||||||
const treeItem = await accountTreeNode.getTreeItem();
|
const treeItem = await accountTreeNode.getTreeItem();
|
||||||
should(treeItem.label).equal(accountTreeNodeLabel);
|
should(treeItem.label).equal(accountTreeNodeLabel);
|
||||||
@@ -131,14 +154,17 @@ describe('AzureResourceAccountTreeNode.info', function(): void {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('Should be correct when there are subscriptions filtered.', async function(): Promise<void> {
|
it('Should be correct when there are subscriptions filtered.', async function(): Promise<void> {
|
||||||
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, mockCredentials)).returns(() => Promise.resolve(mockSubscriptions));
|
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, mockCredential)).returns(() => Promise.resolve(mockSubscriptions));
|
||||||
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => Promise.resolve(mockFilteredSubscriptions));
|
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => Promise.resolve(mockFilteredSubscriptions));
|
||||||
|
|
||||||
const accountTreeNodeLabel = `${mockAccount.displayInfo.displayName} (${mockAccount.key.accountId}) (${mockFilteredSubscriptions.length} / ${mockSubscriptions.length} subscriptions)`;
|
const accountTreeNodeLabel = `${mockAccount.displayInfo.displayName} (${mockAccount.key.accountId}) (${mockFilteredSubscriptions.length} / ${mockSubscriptions.length} subscriptions)`;
|
||||||
|
|
||||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockTreeChangeHandler.object);
|
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
|
||||||
|
|
||||||
await accountTreeNode.getChildren();
|
const subscriptionNodes = await accountTreeNode.getChildren();
|
||||||
|
|
||||||
|
should(subscriptionNodes).Array();
|
||||||
|
should(subscriptionNodes.length).equal(mockFilteredSubscriptions.length);
|
||||||
|
|
||||||
const treeItem = await accountTreeNode.getTreeItem();
|
const treeItem = await accountTreeNode.getTreeItem();
|
||||||
should(treeItem.label).equal(accountTreeNodeLabel);
|
should(treeItem.label).equal(accountTreeNodeLabel);
|
||||||
@@ -150,36 +176,41 @@ describe('AzureResourceAccountTreeNode.info', function(): void {
|
|||||||
|
|
||||||
describe('AzureResourceAccountTreeNode.getChildren', function(): void {
|
describe('AzureResourceAccountTreeNode.getChildren', function(): void {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
|
||||||
|
mockApiWrapper = TypeMoq.Mock.ofType<ApiWrapper>();
|
||||||
mockCacheService = TypeMoq.Mock.ofType<IAzureResourceCacheService>();
|
mockCacheService = TypeMoq.Mock.ofType<IAzureResourceCacheService>();
|
||||||
mockCredentialService = TypeMoq.Mock.ofType<IAzureResourceCredentialService>();
|
|
||||||
mockSubscriptionService = TypeMoq.Mock.ofType<IAzureResourceSubscriptionService>();
|
mockSubscriptionService = TypeMoq.Mock.ofType<IAzureResourceSubscriptionService>();
|
||||||
mockSubscriptionFilterService = TypeMoq.Mock.ofType<IAzureResourceSubscriptionFilterService>();
|
mockSubscriptionFilterService = TypeMoq.Mock.ofType<IAzureResourceSubscriptionFilterService>();
|
||||||
|
mockTenantService = TypeMoq.Mock.ofType<IAzureResourceTenantService>();
|
||||||
|
|
||||||
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
|
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
|
||||||
|
|
||||||
mockSubscriptionCache = { subscriptions: {} };
|
mockSubscriptionCache = [];
|
||||||
|
|
||||||
mockServicePool.cacheService = mockCacheService.object;
|
mockAppContext = new AppContext(mockExtensionContext.object, mockApiWrapper.object);
|
||||||
mockServicePool.credentialService = mockCredentialService.object;
|
mockAppContext.registerService<IAzureResourceCacheService>(AzureResourceServiceNames.cacheService, mockCacheService.object);
|
||||||
mockServicePool.subscriptionService = mockSubscriptionService.object;
|
mockAppContext.registerService<IAzureResourceSubscriptionService>(AzureResourceServiceNames.subscriptionService, mockSubscriptionService.object);
|
||||||
mockServicePool.subscriptionFilterService = mockSubscriptionFilterService.object;
|
mockAppContext.registerService<IAzureResourceSubscriptionFilterService>(AzureResourceServiceNames.subscriptionFilterService, mockSubscriptionFilterService.object);
|
||||||
|
mockAppContext.registerService<IAzureResourceTenantService>(AzureResourceServiceNames.tenantService, mockTenantService.object);
|
||||||
|
|
||||||
mockCredentialService.setup((o) => o.getCredentials(mockAccount)).returns(() => Promise.resolve(mockCredentials));
|
mockApiWrapper.setup((o) => o.getSecurityToken(mockAccount, sqlops.AzureResource.ResourceManagement)).returns(() => Promise.resolve(mockTokens));
|
||||||
|
mockCacheService.setup((o) => o.generateKey(TypeMoq.It.isAnyString())).returns(() => generateGuid());
|
||||||
mockCacheService.setup((o) => o.get(TypeMoq.It.isAnyString())).returns(() => mockSubscriptionCache);
|
mockCacheService.setup((o) => o.get(TypeMoq.It.isAnyString())).returns(() => mockSubscriptionCache);
|
||||||
mockCacheService.setup((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny())).returns(() => mockSubscriptionCache.subscriptions[mockAccount.key.accountId] = mockSubscriptions);
|
mockCacheService.setup((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny())).returns(() => mockSubscriptionCache = mockSubscriptions);
|
||||||
|
mockTenantService.setup((o) => o.getTenantId(TypeMoq.It.isAny())).returns(() => Promise.resolve(mockTenantId));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should load subscriptions from scratch and update cache when it is clearing cache.', async function(): Promise<void> {
|
it('Should load subscriptions from scratch and update cache when it is clearing cache.', async function(): Promise<void> {
|
||||||
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, mockCredentials)).returns(() => Promise.resolve(mockSubscriptions));
|
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, mockCredential)).returns(() => Promise.resolve(mockSubscriptions));
|
||||||
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => Promise.resolve(undefined));
|
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => Promise.resolve([]));
|
||||||
|
|
||||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockTreeChangeHandler.object);
|
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
|
||||||
|
|
||||||
const children = await accountTreeNode.getChildren();
|
const children = await accountTreeNode.getChildren();
|
||||||
|
|
||||||
mockCredentialService.verify((o) => o.getCredentials(mockAccount), TypeMoq.Times.once());
|
mockApiWrapper.verify((o) => o.getSecurityToken(mockAccount, sqlops.AzureResource.ResourceManagement), TypeMoq.Times.once());
|
||||||
mockSubscriptionService.verify((o) => o.getSubscriptions(mockAccount, mockCredentials), TypeMoq.Times.once());
|
mockSubscriptionService.verify((o) => o.getSubscriptions(mockAccount, mockCredential), TypeMoq.Times.once());
|
||||||
mockCacheService.verify((o) => o.get(TypeMoq.It.isAnyString()), TypeMoq.Times.once());
|
mockCacheService.verify((o) => o.get(TypeMoq.It.isAnyString()), TypeMoq.Times.exactly(0));
|
||||||
mockCacheService.verify((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.once());
|
mockCacheService.verify((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.once());
|
||||||
mockSubscriptionFilterService.verify((o) => o.getSelectedSubscriptions(mockAccount), TypeMoq.Times.once());
|
mockSubscriptionFilterService.verify((o) => o.getSelectedSubscriptions(mockAccount), TypeMoq.Times.once());
|
||||||
|
|
||||||
@@ -192,43 +223,42 @@ describe('AzureResourceAccountTreeNode.getChildren', function(): void {
|
|||||||
should(children).Array();
|
should(children).Array();
|
||||||
should(children.length).equal(mockSubscriptions.length);
|
should(children.length).equal(mockSubscriptions.length);
|
||||||
|
|
||||||
should(Object.keys(mockSubscriptionCache.subscriptions)).deepEqual([mockAccount.key.accountId]);
|
should(mockSubscriptionCache).deepEqual(mockSubscriptions);
|
||||||
should(mockSubscriptionCache.subscriptions[mockAccount.key.accountId]).deepEqual(mockSubscriptions);
|
|
||||||
|
|
||||||
for (let ix = 0; ix < mockSubscriptions.length; ix++) {
|
for (let ix = 0; ix < mockSubscriptions.length; ix++) {
|
||||||
const child = children[ix];
|
const child = children[ix];
|
||||||
const subscription = mockSubscriptions[ix];
|
const subscription = mockSubscriptions[ix];
|
||||||
|
|
||||||
should(child).instanceof(AzureResourceSubscriptionTreeNode);
|
should(child).instanceof(AzureResourceSubscriptionTreeNode);
|
||||||
should(child.nodePathValue).equal(`subscription_${subscription.id}`);
|
should(child.nodePathValue).equal(`account_${mockAccount.key.accountId}.subscription_${subscription.id}.tenant_${mockTenantId}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should load subscriptions from cache when it is not clearing cache.', async function(): Promise<void> {
|
it('Should load subscriptions from cache when it is not clearing cache.', async function(): Promise<void> {
|
||||||
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, mockCredentials)).returns(() => Promise.resolve(mockSubscriptions));
|
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, mockCredential)).returns(() => Promise.resolve(mockSubscriptions));
|
||||||
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => Promise.resolve(undefined));
|
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => Promise.resolve(undefined));
|
||||||
|
|
||||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockTreeChangeHandler.object);
|
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
|
||||||
|
|
||||||
await accountTreeNode.getChildren();
|
await accountTreeNode.getChildren();
|
||||||
const children = await accountTreeNode.getChildren();
|
const children = await accountTreeNode.getChildren();
|
||||||
|
|
||||||
mockCredentialService.verify((o) => o.getCredentials(mockAccount), TypeMoq.Times.exactly(1));
|
mockApiWrapper.verify((o) => o.getSecurityToken(mockAccount, sqlops.AzureResource.ResourceManagement), TypeMoq.Times.once());
|
||||||
mockSubscriptionService.verify((o) => o.getSubscriptions(mockAccount, mockCredentials), TypeMoq.Times.exactly(1));
|
mockSubscriptionService.verify((o) => o.getSubscriptions(mockAccount, mockCredential), TypeMoq.Times.once());
|
||||||
mockCacheService.verify((o) => o.get(TypeMoq.It.isAnyString()), TypeMoq.Times.exactly(2));
|
mockCacheService.verify((o) => o.get(TypeMoq.It.isAnyString()), TypeMoq.Times.once());
|
||||||
mockCacheService.verify((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.exactly(1));
|
mockCacheService.verify((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.once());
|
||||||
|
|
||||||
should(children.length).equal(mockSubscriptionCache.subscriptions[mockAccount.key.accountId].length);
|
should(children.length).equal(mockSubscriptionCache.length);
|
||||||
|
|
||||||
for (let ix = 0; ix < mockSubscriptionCache.subscriptions[mockAccount.key.accountId].length; ix++) {
|
for (let ix = 0; ix < mockSubscriptionCache.length; ix++) {
|
||||||
should(children[ix].nodePathValue).equal(`subscription_${mockSubscriptionCache.subscriptions[mockAccount.key.accountId][ix].id}`);
|
should(children[ix].nodePathValue).equal(`account_${mockAccount.key.accountId}.subscription_${mockSubscriptionCache[ix].id}.tenant_${mockTenantId}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should handle when there is no subscriptions.', async function(): Promise<void> {
|
it('Should handle when there is no subscriptions.', async function(): Promise<void> {
|
||||||
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, mockCredentials)).returns(() => Promise.resolve(undefined));
|
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, mockCredential)).returns(() => Promise.resolve(undefined));
|
||||||
|
|
||||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockTreeChangeHandler.object);
|
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
|
||||||
|
|
||||||
const children = await accountTreeNode.getChildren();
|
const children = await accountTreeNode.getChildren();
|
||||||
|
|
||||||
@@ -242,10 +272,10 @@ describe('AzureResourceAccountTreeNode.getChildren', function(): void {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('Should honor subscription filtering.', async function(): Promise<void> {
|
it('Should honor subscription filtering.', async function(): Promise<void> {
|
||||||
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, mockCredentials)).returns(() => Promise.resolve(mockSubscriptions));
|
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, mockCredential)).returns(() => Promise.resolve(mockSubscriptions));
|
||||||
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => Promise.resolve(mockFilteredSubscriptions));
|
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => Promise.resolve(mockFilteredSubscriptions));
|
||||||
|
|
||||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockTreeChangeHandler.object);
|
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
|
||||||
|
|
||||||
const children = await accountTreeNode.getChildren();
|
const children = await accountTreeNode.getChildren();
|
||||||
|
|
||||||
@@ -255,23 +285,25 @@ describe('AzureResourceAccountTreeNode.getChildren', function(): void {
|
|||||||
should(children.length).equal(mockFilteredSubscriptions.length);
|
should(children.length).equal(mockFilteredSubscriptions.length);
|
||||||
|
|
||||||
for (let ix = 0; ix < mockFilteredSubscriptions.length; ix++) {
|
for (let ix = 0; ix < mockFilteredSubscriptions.length; ix++) {
|
||||||
should(children[ix].nodePathValue).equal(`subscription_${mockFilteredSubscriptions[ix].id}`);
|
should(children[ix].nodePathValue).equal(`account_${mockAccount.key.accountId}.subscription_${mockFilteredSubscriptions[ix].id}.tenant_${mockTenantId}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should handle errors.', async function(): Promise<void> {
|
it('Should handle errors.', async function(): Promise<void> {
|
||||||
const mockError = 'Test error';
|
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, mockCredential)).returns(() => Promise.resolve(mockSubscriptions));
|
||||||
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, mockCredentials)).returns(() => { throw new Error(mockError); });
|
|
||||||
|
|
||||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockTreeChangeHandler.object);
|
const mockError = 'Test error';
|
||||||
|
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => { throw new Error(mockError); });
|
||||||
|
|
||||||
|
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
|
||||||
|
|
||||||
const children = await accountTreeNode.getChildren();
|
const children = await accountTreeNode.getChildren();
|
||||||
|
|
||||||
mockCredentialService.verify((o) => o.getCredentials(mockAccount), TypeMoq.Times.once());
|
mockApiWrapper.verify((o) => o.getSecurityToken(mockAccount, sqlops.AzureResource.ResourceManagement), TypeMoq.Times.once());
|
||||||
mockSubscriptionService.verify((o) => o.getSubscriptions(mockAccount, mockCredentials), TypeMoq.Times.once());
|
mockSubscriptionService.verify((o) => o.getSubscriptions(mockAccount, mockCredential), TypeMoq.Times.once());
|
||||||
|
mockSubscriptionFilterService.verify((o) => o.getSelectedSubscriptions(mockAccount), TypeMoq.Times.once());
|
||||||
mockCacheService.verify((o) => o.get(TypeMoq.It.isAnyString()), TypeMoq.Times.never());
|
mockCacheService.verify((o) => o.get(TypeMoq.It.isAnyString()), TypeMoq.Times.never());
|
||||||
mockCacheService.verify((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.never());
|
mockCacheService.verify((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.once());
|
||||||
mockSubscriptionFilterService.verify((o) => o.getSelectedSubscriptions(mockAccount), TypeMoq.Times.never());
|
|
||||||
|
|
||||||
should(children).Array();
|
should(children).Array();
|
||||||
should(children.length).equal(1);
|
should(children.length).equal(1);
|
||||||
@@ -283,11 +315,32 @@ describe('AzureResourceAccountTreeNode.getChildren', function(): void {
|
|||||||
|
|
||||||
describe('AzureResourceAccountTreeNode.clearCache', function() : void {
|
describe('AzureResourceAccountTreeNode.clearCache', function() : void {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
|
||||||
|
mockApiWrapper = TypeMoq.Mock.ofType<ApiWrapper>();
|
||||||
|
mockCacheService = TypeMoq.Mock.ofType<IAzureResourceCacheService>();
|
||||||
|
mockSubscriptionService = TypeMoq.Mock.ofType<IAzureResourceSubscriptionService>();
|
||||||
|
mockSubscriptionFilterService = TypeMoq.Mock.ofType<IAzureResourceSubscriptionFilterService>();
|
||||||
|
mockTenantService = TypeMoq.Mock.ofType<IAzureResourceTenantService>();
|
||||||
|
|
||||||
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
|
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
|
||||||
|
|
||||||
|
mockSubscriptionCache = [];
|
||||||
|
|
||||||
|
mockAppContext = new AppContext(mockExtensionContext.object, mockApiWrapper.object);
|
||||||
|
mockAppContext.registerService<IAzureResourceCacheService>(AzureResourceServiceNames.cacheService, mockCacheService.object);
|
||||||
|
mockAppContext.registerService<IAzureResourceSubscriptionService>(AzureResourceServiceNames.subscriptionService, mockSubscriptionService.object);
|
||||||
|
mockAppContext.registerService<IAzureResourceSubscriptionFilterService>(AzureResourceServiceNames.subscriptionFilterService, mockSubscriptionFilterService.object);
|
||||||
|
mockAppContext.registerService<IAzureResourceTenantService>(AzureResourceServiceNames.tenantService, mockTenantService.object);
|
||||||
|
|
||||||
|
mockApiWrapper.setup((o) => o.getSecurityToken(mockAccount, sqlops.AzureResource.ResourceManagement)).returns(() => Promise.resolve(mockTokens));
|
||||||
|
mockCacheService.setup((o) => o.generateKey(TypeMoq.It.isAnyString())).returns(() => generateGuid());
|
||||||
|
mockCacheService.setup((o) => o.get(TypeMoq.It.isAnyString())).returns(() => mockSubscriptionCache);
|
||||||
|
mockCacheService.setup((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny())).returns(() => mockSubscriptionCache = mockSubscriptions);
|
||||||
|
mockTenantService.setup((o) => o.getTenantId(TypeMoq.It.isAny())).returns(() => Promise.resolve(mockTenantId));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should clear cache.', async function(): Promise<void> {
|
it('Should clear cache.', async function(): Promise<void> {
|
||||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockTreeChangeHandler.object);
|
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
|
||||||
accountTreeNode.clearCache();
|
accountTreeNode.clearCache();
|
||||||
should(accountTreeNode.isClearingCache).true();
|
should(accountTreeNode.isClearingCache).true();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,219 +0,0 @@
|
|||||||
/*---------------------------------------------------------------------------------------------
|
|
||||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
|
||||||
*--------------------------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
import * as should from 'should';
|
|
||||||
import * as TypeMoq from 'typemoq';
|
|
||||||
import * as sqlops from 'sqlops';
|
|
||||||
import * as vscode from 'vscode';
|
|
||||||
import 'mocha';
|
|
||||||
import { ServiceClientCredentials } from 'ms-rest';
|
|
||||||
|
|
||||||
import { AzureResourceServicePool } from '../../../azureResource/servicePool';
|
|
||||||
import {
|
|
||||||
IAzureResourceCacheService,
|
|
||||||
IAzureResourceContextService,
|
|
||||||
IAzureResourceCredentialService,
|
|
||||||
IAzureResourceDatabaseService
|
|
||||||
} from '../../../azureResource/interfaces';
|
|
||||||
import { IAzureResourceTreeChangeHandler } from '../../../azureResource/tree/treeChangeHandler';
|
|
||||||
import { AzureResourceSubscription, AzureResourceDatabase } from '../../../azureResource/models';
|
|
||||||
import { AzureResourceItemType } from '../../../azureResource/constants';
|
|
||||||
import { AzureResourceMessageTreeNode } from '../../../azureResource/tree/messageTreeNode';
|
|
||||||
import { AzureResourceDatabaseContainerTreeNode } from '../../../azureResource/tree/databaseContainerTreeNode';
|
|
||||||
import { AzureResourceDatabaseTreeNode } from '../../../azureResource/tree/databaseTreeNode';
|
|
||||||
|
|
||||||
// Mock services
|
|
||||||
const mockServicePool = AzureResourceServicePool.getInstance();
|
|
||||||
|
|
||||||
let mockCacheService: TypeMoq.IMock<IAzureResourceCacheService>;
|
|
||||||
let mockContextService: TypeMoq.IMock<IAzureResourceContextService>;
|
|
||||||
let mockCredentialService: TypeMoq.IMock<IAzureResourceCredentialService>;
|
|
||||||
let mockDatabaseService: TypeMoq.IMock<IAzureResourceDatabaseService>;
|
|
||||||
|
|
||||||
let mockTreeChangeHandler: TypeMoq.IMock<IAzureResourceTreeChangeHandler>;
|
|
||||||
|
|
||||||
// Mock test data
|
|
||||||
const mockAccount: sqlops.Account = {
|
|
||||||
key: {
|
|
||||||
accountId: 'mock_account',
|
|
||||||
providerId: 'mock_provider'
|
|
||||||
},
|
|
||||||
displayInfo: {
|
|
||||||
displayName: 'mock_account@test.com',
|
|
||||||
accountType: 'Microsoft',
|
|
||||||
contextualDisplayName: 'test'
|
|
||||||
},
|
|
||||||
properties: undefined,
|
|
||||||
isStale: false
|
|
||||||
};
|
|
||||||
|
|
||||||
const mockCredential = TypeMoq.Mock.ofType<ServiceClientCredentials>().object;
|
|
||||||
const mockCredentials = [mockCredential];
|
|
||||||
|
|
||||||
const mockSubscription: AzureResourceSubscription = {
|
|
||||||
id: 'mock_subscription',
|
|
||||||
name: 'mock subscription'
|
|
||||||
};
|
|
||||||
|
|
||||||
const mockDatabase1: AzureResourceDatabase = {
|
|
||||||
name: 'mock database 1',
|
|
||||||
serverName: 'mock server 1',
|
|
||||||
serverFullName: 'mock server 1',
|
|
||||||
loginName: 'mock user 1'
|
|
||||||
};
|
|
||||||
const mockDatabase2: AzureResourceDatabase = {
|
|
||||||
name: 'mock database 2',
|
|
||||||
serverName: 'mock server 2',
|
|
||||||
serverFullName: 'mock server 2',
|
|
||||||
loginName: 'mock user 2'
|
|
||||||
};
|
|
||||||
const mockDatabases = [mockDatabase1, mockDatabase2];
|
|
||||||
|
|
||||||
let mockDatabaseContainerCache: { databases: { [subscriptionId: string]: AzureResourceDatabase[] } };
|
|
||||||
|
|
||||||
describe('AzureResourceDatabaseContainerTreeNode.info', function(): void {
|
|
||||||
beforeEach(() => {
|
|
||||||
mockContextService = TypeMoq.Mock.ofType<IAzureResourceContextService>();
|
|
||||||
|
|
||||||
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
|
|
||||||
|
|
||||||
mockServicePool.contextService = mockContextService.object;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should be correct when created.', async function(): Promise<void> {
|
|
||||||
const databaseContainerTreeNode = new AzureResourceDatabaseContainerTreeNode(mockSubscription, mockAccount, mockTreeChangeHandler.object, undefined);
|
|
||||||
|
|
||||||
const databaseContainerTreeNodeLabel = 'SQL Databases';
|
|
||||||
|
|
||||||
should(databaseContainerTreeNode.nodePathValue).equal('databaseContainer');
|
|
||||||
|
|
||||||
const treeItem = await databaseContainerTreeNode.getTreeItem();
|
|
||||||
should(treeItem.label).equal(databaseContainerTreeNodeLabel);
|
|
||||||
should(treeItem.contextValue).equal(AzureResourceItemType.databaseContainer);
|
|
||||||
should(treeItem.collapsibleState).equal(vscode.TreeItemCollapsibleState.Collapsed);
|
|
||||||
|
|
||||||
const nodeInfo = databaseContainerTreeNode.getNodeInfo();
|
|
||||||
should(nodeInfo.isLeaf).false();
|
|
||||||
should(nodeInfo.label).equal(databaseContainerTreeNodeLabel);
|
|
||||||
should(nodeInfo.nodeType).equal(AzureResourceItemType.databaseContainer);
|
|
||||||
should(nodeInfo.iconType).equal(AzureResourceItemType.databaseContainer);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('AzureResourceDatabaseContainerTreeNode.getChildren', function(): void {
|
|
||||||
beforeEach(() => {
|
|
||||||
mockCacheService = TypeMoq.Mock.ofType<IAzureResourceCacheService>();
|
|
||||||
mockCredentialService = TypeMoq.Mock.ofType<IAzureResourceCredentialService>();
|
|
||||||
mockDatabaseService = TypeMoq.Mock.ofType<IAzureResourceDatabaseService>();
|
|
||||||
|
|
||||||
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
|
|
||||||
|
|
||||||
mockDatabaseContainerCache = { databases: {} };
|
|
||||||
|
|
||||||
mockServicePool.cacheService = mockCacheService.object;
|
|
||||||
mockServicePool.credentialService = mockCredentialService.object;
|
|
||||||
mockServicePool.databaseService = mockDatabaseService.object;
|
|
||||||
|
|
||||||
mockCredentialService.setup((o) => o.getCredentials(mockAccount)).returns(() => Promise.resolve(mockCredentials));
|
|
||||||
mockCacheService.setup((o) => o.get(TypeMoq.It.isAnyString())).returns(() => mockDatabaseContainerCache);
|
|
||||||
mockCacheService.setup((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny())).returns(() => mockDatabaseContainerCache.databases[mockSubscription.id] = mockDatabases);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should load databases from scratch and update cache when it is clearing cache.', async function(): Promise<void> {
|
|
||||||
mockDatabaseService.setup((o) => o.getDatabases(mockSubscription, mockCredentials)).returns(() => Promise.resolve(mockDatabases));
|
|
||||||
|
|
||||||
const databaseContainerTreeNode = new AzureResourceDatabaseContainerTreeNode(mockSubscription, mockAccount, mockTreeChangeHandler.object, undefined);
|
|
||||||
|
|
||||||
const children = await databaseContainerTreeNode.getChildren();
|
|
||||||
|
|
||||||
mockCredentialService.verify((o) => o.getCredentials(mockAccount), TypeMoq.Times.once());
|
|
||||||
mockDatabaseService.verify((o) => o.getDatabases(mockSubscription, mockCredentials), TypeMoq.Times.once());
|
|
||||||
mockCacheService.verify((o) => o.get(TypeMoq.It.isAnyString()), TypeMoq.Times.once());
|
|
||||||
mockCacheService.verify((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.once());
|
|
||||||
|
|
||||||
should(databaseContainerTreeNode.isClearingCache).false();
|
|
||||||
|
|
||||||
should(children).Array();
|
|
||||||
should(children.length).equal(mockDatabases.length);
|
|
||||||
|
|
||||||
should(Object.keys(mockDatabaseContainerCache.databases)).deepEqual([mockSubscription.id]);
|
|
||||||
should(mockDatabaseContainerCache.databases[mockSubscription.id]).deepEqual(mockDatabases);
|
|
||||||
|
|
||||||
for (let ix = 0; ix < mockDatabases.length; ix++) {
|
|
||||||
const child = children[ix];
|
|
||||||
const database = mockDatabases[ix];
|
|
||||||
|
|
||||||
should(child).instanceof(AzureResourceDatabaseTreeNode);
|
|
||||||
should(child.nodePathValue).equal(`database_${database.name}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should load databases from cache when it is not clearing cache.', async function(): Promise<void> {
|
|
||||||
mockDatabaseService.setup((o) => o.getDatabases(mockSubscription, mockCredentials)).returns(() => Promise.resolve(mockDatabases));
|
|
||||||
|
|
||||||
const databaseContainerTreeNode = new AzureResourceDatabaseContainerTreeNode(mockSubscription, mockAccount, mockTreeChangeHandler.object, undefined);
|
|
||||||
|
|
||||||
await databaseContainerTreeNode.getChildren();
|
|
||||||
const children = await databaseContainerTreeNode.getChildren();
|
|
||||||
|
|
||||||
mockCredentialService.verify((o) => o.getCredentials(mockAccount), TypeMoq.Times.exactly(1));
|
|
||||||
mockDatabaseService.verify((o) => o.getDatabases(mockSubscription, mockCredentials), TypeMoq.Times.exactly(1));
|
|
||||||
mockCacheService.verify((o) => o.get(TypeMoq.It.isAnyString()), TypeMoq.Times.exactly(2));
|
|
||||||
mockCacheService.verify((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.exactly(1));
|
|
||||||
|
|
||||||
should(children.length).equal(mockDatabaseContainerCache.databases[mockSubscription.id].length);
|
|
||||||
|
|
||||||
for (let ix = 0; ix < mockDatabaseContainerCache.databases[mockSubscription.id].length; ix++) {
|
|
||||||
should(children[ix].nodePathValue).equal(`database_${mockDatabaseContainerCache.databases[mockSubscription.id][ix].name}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should handle when there is no databases.', async function(): Promise<void> {
|
|
||||||
mockDatabaseService.setup((o) => o.getDatabases(mockSubscription, mockCredentials)).returns(() => Promise.resolve(undefined));
|
|
||||||
|
|
||||||
const databaseContainerTreeNode = new AzureResourceDatabaseContainerTreeNode(mockSubscription, mockAccount, mockTreeChangeHandler.object, undefined);
|
|
||||||
|
|
||||||
const children = await databaseContainerTreeNode.getChildren();
|
|
||||||
|
|
||||||
should(children).Array();
|
|
||||||
should(children.length).equal(1);
|
|
||||||
should(children[0]).instanceof(AzureResourceMessageTreeNode);
|
|
||||||
should(children[0].nodePathValue).startWith('message_');
|
|
||||||
should(children[0].getNodeInfo().label).equal('No SQL Databases found.');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should handle errors.', async function(): Promise<void> {
|
|
||||||
const mockError = 'Test error';
|
|
||||||
mockDatabaseService.setup((o) => o.getDatabases(mockSubscription, mockCredentials)).returns(() => { throw new Error(mockError); });
|
|
||||||
|
|
||||||
const databaseContainerTreeNode = new AzureResourceDatabaseContainerTreeNode(mockSubscription, mockAccount, mockTreeChangeHandler.object, undefined);
|
|
||||||
const children = await databaseContainerTreeNode.getChildren();
|
|
||||||
|
|
||||||
mockCredentialService.verify((o) => o.getCredentials(mockAccount), TypeMoq.Times.once());
|
|
||||||
mockDatabaseService.verify((o) => o.getDatabases(mockSubscription, mockCredentials), TypeMoq.Times.once());
|
|
||||||
mockCacheService.verify((o) => o.get(TypeMoq.It.isAnyString()), TypeMoq.Times.never());
|
|
||||||
mockCacheService.verify((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.never());
|
|
||||||
|
|
||||||
should(children).Array();
|
|
||||||
should(children.length).equal(1);
|
|
||||||
should(children[0]).instanceof(AzureResourceMessageTreeNode);
|
|
||||||
should(children[0].nodePathValue).startWith('message_');
|
|
||||||
should(children[0].getNodeInfo().label).equal(`Error: ${mockError}`);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('AzureResourceDatabaseContainerTreeNode.clearCache', function() : void {
|
|
||||||
beforeEach(() => {
|
|
||||||
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should clear cache.', async function(): Promise<void> {
|
|
||||||
const databaseContainerTreeNode = new AzureResourceDatabaseContainerTreeNode(mockSubscription, mockAccount, mockTreeChangeHandler.object, undefined);
|
|
||||||
databaseContainerTreeNode.clearCache();
|
|
||||||
should(databaseContainerTreeNode.isClearingCache).true();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,219 +0,0 @@
|
|||||||
/*---------------------------------------------------------------------------------------------
|
|
||||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
|
||||||
*--------------------------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
import * as should from 'should';
|
|
||||||
import * as TypeMoq from 'typemoq';
|
|
||||||
import * as sqlops from 'sqlops';
|
|
||||||
import * as vscode from 'vscode';
|
|
||||||
import 'mocha';
|
|
||||||
import { ServiceClientCredentials } from 'ms-rest';
|
|
||||||
|
|
||||||
import { AzureResourceServicePool } from '../../../azureResource/servicePool';
|
|
||||||
import {
|
|
||||||
IAzureResourceCacheService,
|
|
||||||
IAzureResourceContextService,
|
|
||||||
IAzureResourceCredentialService,
|
|
||||||
IAzureResourceDatabaseServerService
|
|
||||||
} from '../../../azureResource/interfaces';
|
|
||||||
import { IAzureResourceTreeChangeHandler } from '../../../azureResource/tree/treeChangeHandler';
|
|
||||||
import { AzureResourceSubscription, AzureResourceDatabaseServer } from '../../../azureResource/models';
|
|
||||||
import { AzureResourceItemType } from '../../../azureResource/constants';
|
|
||||||
import { AzureResourceMessageTreeNode } from '../../../azureResource/tree/messageTreeNode';
|
|
||||||
import { AzureResourceDatabaseServerContainerTreeNode } from '../../../azureResource/tree/databaseServerContainerTreeNode';
|
|
||||||
import { AzureResourceDatabaseServerTreeNode } from '../../../azureResource/tree/databaseServerTreeNode';
|
|
||||||
|
|
||||||
// Mock services
|
|
||||||
const mockServicePool = AzureResourceServicePool.getInstance();
|
|
||||||
|
|
||||||
let mockCacheService: TypeMoq.IMock<IAzureResourceCacheService>;
|
|
||||||
let mockContextService: TypeMoq.IMock<IAzureResourceContextService>;
|
|
||||||
let mockCredentialService: TypeMoq.IMock<IAzureResourceCredentialService>;
|
|
||||||
let mockDatabaseServerService: TypeMoq.IMock<IAzureResourceDatabaseServerService>;
|
|
||||||
|
|
||||||
let mockTreeChangeHandler: TypeMoq.IMock<IAzureResourceTreeChangeHandler>;
|
|
||||||
|
|
||||||
// Mock test data
|
|
||||||
const mockAccount: sqlops.Account = {
|
|
||||||
key: {
|
|
||||||
accountId: 'mock_account',
|
|
||||||
providerId: 'mock_provider'
|
|
||||||
},
|
|
||||||
displayInfo: {
|
|
||||||
displayName: 'mock_account@test.com',
|
|
||||||
accountType: 'Microsoft',
|
|
||||||
contextualDisplayName: 'test'
|
|
||||||
},
|
|
||||||
properties: undefined,
|
|
||||||
isStale: false
|
|
||||||
};
|
|
||||||
|
|
||||||
const mockCredential = TypeMoq.Mock.ofType<ServiceClientCredentials>().object;
|
|
||||||
const mockCredentials = [mockCredential];
|
|
||||||
|
|
||||||
const mockSubscription: AzureResourceSubscription = {
|
|
||||||
id: 'mock_subscription',
|
|
||||||
name: 'mock subscription'
|
|
||||||
};
|
|
||||||
|
|
||||||
const mockDatabaseServer1: AzureResourceDatabaseServer = {
|
|
||||||
name: 'mock server 1',
|
|
||||||
fullName: 'mock server 1',
|
|
||||||
loginName: 'mock user 1',
|
|
||||||
defaultDatabaseName: 'master'
|
|
||||||
};
|
|
||||||
const mockDatabaseServer2: AzureResourceDatabaseServer = {
|
|
||||||
name: 'mock server 2',
|
|
||||||
fullName: 'mock server 2',
|
|
||||||
loginName: 'mock user 2',
|
|
||||||
defaultDatabaseName: 'master'
|
|
||||||
};
|
|
||||||
const mockDatabaseServers = [mockDatabaseServer1, mockDatabaseServer2];
|
|
||||||
|
|
||||||
let mockDatabaseServerContainerCache: { databaseServers: { [subscriptionId: string]: AzureResourceDatabaseServer[] } };
|
|
||||||
|
|
||||||
describe('AzureResourceDatabaseServerContainerTreeNode.info', function(): void {
|
|
||||||
beforeEach(() => {
|
|
||||||
mockContextService = TypeMoq.Mock.ofType<IAzureResourceContextService>();
|
|
||||||
|
|
||||||
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
|
|
||||||
|
|
||||||
mockServicePool.contextService = mockContextService.object;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should be correct when created.', async function(): Promise<void> {
|
|
||||||
const databaseServerContainerTreeNode = new AzureResourceDatabaseServerContainerTreeNode(mockSubscription, mockAccount, mockTreeChangeHandler.object, undefined);
|
|
||||||
|
|
||||||
const databaseServerContainerTreeNodeLabel = 'SQL Servers';
|
|
||||||
|
|
||||||
should(databaseServerContainerTreeNode.nodePathValue).equal('databaseServerContainer');
|
|
||||||
|
|
||||||
const treeItem = await databaseServerContainerTreeNode.getTreeItem();
|
|
||||||
should(treeItem.label).equal(databaseServerContainerTreeNodeLabel);
|
|
||||||
should(treeItem.contextValue).equal(AzureResourceItemType.databaseServerContainer);
|
|
||||||
should(treeItem.collapsibleState).equal(vscode.TreeItemCollapsibleState.Collapsed);
|
|
||||||
|
|
||||||
const nodeInfo = databaseServerContainerTreeNode.getNodeInfo();
|
|
||||||
should(nodeInfo.isLeaf).false();
|
|
||||||
should(nodeInfo.label).equal(databaseServerContainerTreeNodeLabel);
|
|
||||||
should(nodeInfo.nodeType).equal(AzureResourceItemType.databaseServerContainer);
|
|
||||||
should(nodeInfo.iconType).equal(AzureResourceItemType.databaseServerContainer);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('AzureResourceDatabaseServerContainerTreeNode.getChildren', function(): void {
|
|
||||||
beforeEach(() => {
|
|
||||||
mockCacheService = TypeMoq.Mock.ofType<IAzureResourceCacheService>();
|
|
||||||
mockCredentialService = TypeMoq.Mock.ofType<IAzureResourceCredentialService>();
|
|
||||||
mockDatabaseServerService = TypeMoq.Mock.ofType<IAzureResourceDatabaseServerService>();
|
|
||||||
|
|
||||||
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
|
|
||||||
|
|
||||||
mockDatabaseServerContainerCache = { databaseServers: {} };
|
|
||||||
|
|
||||||
mockServicePool.cacheService = mockCacheService.object;
|
|
||||||
mockServicePool.credentialService = mockCredentialService.object;
|
|
||||||
mockServicePool.databaseServerService = mockDatabaseServerService.object;
|
|
||||||
|
|
||||||
mockCredentialService.setup((o) => o.getCredentials(mockAccount)).returns(() => Promise.resolve(mockCredentials));
|
|
||||||
mockCacheService.setup((o) => o.get(TypeMoq.It.isAnyString())).returns(() => mockDatabaseServerContainerCache);
|
|
||||||
mockCacheService.setup((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny())).returns(() => mockDatabaseServerContainerCache.databaseServers[mockSubscription.id] = mockDatabaseServers);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should load database servers from scratch and update cache when it is clearing cache.', async function(): Promise<void> {
|
|
||||||
mockDatabaseServerService.setup((o) => o.getDatabaseServers(mockSubscription, mockCredentials)).returns(() => Promise.resolve(mockDatabaseServers));
|
|
||||||
|
|
||||||
const databaseServerContainerTreeNode = new AzureResourceDatabaseServerContainerTreeNode(mockSubscription, mockAccount, mockTreeChangeHandler.object, undefined);
|
|
||||||
|
|
||||||
const children = await databaseServerContainerTreeNode.getChildren();
|
|
||||||
|
|
||||||
mockCredentialService.verify((o) => o.getCredentials(mockAccount), TypeMoq.Times.once());
|
|
||||||
mockDatabaseServerService.verify((o) => o.getDatabaseServers(mockSubscription, mockCredentials), TypeMoq.Times.once());
|
|
||||||
mockCacheService.verify((o) => o.get(TypeMoq.It.isAnyString()), TypeMoq.Times.once());
|
|
||||||
mockCacheService.verify((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.once());
|
|
||||||
|
|
||||||
should(databaseServerContainerTreeNode.isClearingCache).false();
|
|
||||||
|
|
||||||
should(children).Array();
|
|
||||||
should(children.length).equal(mockDatabaseServers.length);
|
|
||||||
|
|
||||||
should(Object.keys(mockDatabaseServerContainerCache.databaseServers)).deepEqual([mockSubscription.id]);
|
|
||||||
should(mockDatabaseServerContainerCache.databaseServers[mockSubscription.id]).deepEqual(mockDatabaseServers);
|
|
||||||
|
|
||||||
for (let ix = 0; ix < mockDatabaseServers.length; ix++) {
|
|
||||||
const child = children[ix];
|
|
||||||
const databaseServer = mockDatabaseServers[ix];
|
|
||||||
|
|
||||||
should(child).instanceof(AzureResourceDatabaseServerTreeNode);
|
|
||||||
should(child.nodePathValue).equal(`databaseServer_${databaseServer.name}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should load database servers from cache when it is not clearing cache.', async function(): Promise<void> {
|
|
||||||
mockDatabaseServerService.setup((o) => o.getDatabaseServers(mockSubscription, mockCredentials)).returns(() => Promise.resolve(mockDatabaseServers));
|
|
||||||
|
|
||||||
const databaseServerContainerTreeNode = new AzureResourceDatabaseServerContainerTreeNode(mockSubscription, mockAccount, mockTreeChangeHandler.object, undefined);
|
|
||||||
|
|
||||||
await databaseServerContainerTreeNode.getChildren();
|
|
||||||
const children = await databaseServerContainerTreeNode.getChildren();
|
|
||||||
|
|
||||||
mockCredentialService.verify((o) => o.getCredentials(mockAccount), TypeMoq.Times.exactly(1));
|
|
||||||
mockDatabaseServerService.verify((o) => o.getDatabaseServers(mockSubscription, mockCredentials), TypeMoq.Times.exactly(1));
|
|
||||||
mockCacheService.verify((o) => o.get(TypeMoq.It.isAnyString()), TypeMoq.Times.exactly(2));
|
|
||||||
mockCacheService.verify((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.exactly(1));
|
|
||||||
|
|
||||||
should(children.length).equal(mockDatabaseServerContainerCache.databaseServers[mockSubscription.id].length);
|
|
||||||
|
|
||||||
for (let ix = 0; ix < mockDatabaseServerContainerCache.databaseServers[mockSubscription.id].length; ix++) {
|
|
||||||
should(children[ix].nodePathValue).equal(`databaseServer_${mockDatabaseServerContainerCache.databaseServers[mockSubscription.id][ix].name}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should handle when there is no database servers.', async function(): Promise<void> {
|
|
||||||
mockDatabaseServerService.setup((o) => o.getDatabaseServers(mockSubscription, mockCredentials)).returns(() => Promise.resolve(undefined));
|
|
||||||
|
|
||||||
const databaseContainerTreeNode = new AzureResourceDatabaseServerContainerTreeNode(mockSubscription, mockAccount, mockTreeChangeHandler.object, undefined);
|
|
||||||
|
|
||||||
const children = await databaseContainerTreeNode.getChildren();
|
|
||||||
|
|
||||||
should(children).Array();
|
|
||||||
should(children.length).equal(1);
|
|
||||||
should(children[0]).instanceof(AzureResourceMessageTreeNode);
|
|
||||||
should(children[0].nodePathValue).startWith('message_');
|
|
||||||
should(children[0].getNodeInfo().label).equal('No SQL Servers found.');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should handle errors.', async function(): Promise<void> {
|
|
||||||
const mockError = 'Test error';
|
|
||||||
mockDatabaseServerService.setup((o) => o.getDatabaseServers(mockSubscription, mockCredentials)).returns(() => { throw new Error(mockError); });
|
|
||||||
|
|
||||||
const databaseServerContainerTreeNode = new AzureResourceDatabaseServerContainerTreeNode(mockSubscription, mockAccount, mockTreeChangeHandler.object, undefined);
|
|
||||||
const children = await databaseServerContainerTreeNode.getChildren();
|
|
||||||
|
|
||||||
mockCredentialService.verify((o) => o.getCredentials(mockAccount), TypeMoq.Times.once());
|
|
||||||
mockDatabaseServerService.verify((o) => o.getDatabaseServers(mockSubscription, mockCredentials), TypeMoq.Times.once());
|
|
||||||
mockCacheService.verify((o) => o.get(TypeMoq.It.isAnyString()), TypeMoq.Times.never());
|
|
||||||
mockCacheService.verify((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.never());
|
|
||||||
|
|
||||||
should(children).Array();
|
|
||||||
should(children.length).equal(1);
|
|
||||||
should(children[0]).instanceof(AzureResourceMessageTreeNode);
|
|
||||||
should(children[0].nodePathValue).startWith('message_');
|
|
||||||
should(children[0].getNodeInfo().label).equal(`Error: ${mockError}`);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('AzureResourceDatabaseServerContainerTreeNode.clearCache', function() : void {
|
|
||||||
beforeEach(() => {
|
|
||||||
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should clear cache.', async function(): Promise<void> {
|
|
||||||
const databaseServerContainerTreeNode = new AzureResourceDatabaseServerContainerTreeNode(mockSubscription, mockAccount, mockTreeChangeHandler.object, undefined);
|
|
||||||
databaseServerContainerTreeNode.clearCache();
|
|
||||||
should(databaseServerContainerTreeNode.isClearingCache).true();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
/*---------------------------------------------------------------------------------------------
|
|
||||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
|
||||||
*--------------------------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
import * as should from 'should';
|
|
||||||
import * as TypeMoq from 'typemoq';
|
|
||||||
import * as vscode from 'vscode';
|
|
||||||
import 'mocha';
|
|
||||||
|
|
||||||
import { AzureResourceServicePool } from '../../../azureResource/servicePool';
|
|
||||||
import { IAzureResourceContextService } from '../../../azureResource/interfaces';
|
|
||||||
import { IAzureResourceTreeChangeHandler } from '../../../azureResource/tree/treeChangeHandler';
|
|
||||||
import { AzureResourceDatabaseServer } from '../../../azureResource/models';
|
|
||||||
import { AzureResourceItemType } from '../../../azureResource/constants';
|
|
||||||
import { AzureResourceDatabaseServerTreeNode } from '../../../azureResource/tree/databaseServerTreeNode';
|
|
||||||
|
|
||||||
// Mock services
|
|
||||||
const mockServicePool = AzureResourceServicePool.getInstance();
|
|
||||||
|
|
||||||
let mockContextService: TypeMoq.IMock<IAzureResourceContextService>;
|
|
||||||
|
|
||||||
let mockTreeChangeHandler: TypeMoq.IMock<IAzureResourceTreeChangeHandler>;
|
|
||||||
|
|
||||||
// Mock test data
|
|
||||||
const mockDatabaseServer: AzureResourceDatabaseServer = {
|
|
||||||
name: 'mock database 1',
|
|
||||||
fullName: 'mock server 1',
|
|
||||||
loginName: 'mock user 1',
|
|
||||||
defaultDatabaseName: 'master'
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('AzureResourceDatabaseServerTreeNode.info', function(): void {
|
|
||||||
beforeEach(() => {
|
|
||||||
mockContextService = TypeMoq.Mock.ofType<IAzureResourceContextService>();
|
|
||||||
|
|
||||||
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
|
|
||||||
|
|
||||||
mockServicePool.contextService = mockContextService.object;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should be correct when created.', async function(): Promise<void> {
|
|
||||||
const databaseServerTreeNode = new AzureResourceDatabaseServerTreeNode(mockDatabaseServer, mockTreeChangeHandler.object, undefined);
|
|
||||||
|
|
||||||
const databaseServerTreeNodeLabel = mockDatabaseServer.name;
|
|
||||||
|
|
||||||
should(databaseServerTreeNode.nodePathValue).equal(`databaseServer_${mockDatabaseServer.name}`);
|
|
||||||
|
|
||||||
const treeItem = await databaseServerTreeNode.getTreeItem();
|
|
||||||
should(treeItem.label).equal(databaseServerTreeNodeLabel);
|
|
||||||
should(treeItem.contextValue).equal(AzureResourceItemType.databaseServer);
|
|
||||||
should(treeItem.collapsibleState).equal(vscode.TreeItemCollapsibleState.None);
|
|
||||||
|
|
||||||
const nodeInfo = databaseServerTreeNode.getNodeInfo();
|
|
||||||
should(nodeInfo.isLeaf).true();
|
|
||||||
should(nodeInfo.label).equal(databaseServerTreeNodeLabel);
|
|
||||||
should(nodeInfo.nodeType).equal(AzureResourceItemType.databaseServer);
|
|
||||||
should(nodeInfo.iconType).equal(AzureResourceItemType.databaseServer);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
/*---------------------------------------------------------------------------------------------
|
|
||||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
|
||||||
*--------------------------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
import * as should from 'should';
|
|
||||||
import * as TypeMoq from 'typemoq';
|
|
||||||
import * as vscode from 'vscode';
|
|
||||||
import 'mocha';
|
|
||||||
|
|
||||||
import { AzureResourceServicePool } from '../../../azureResource/servicePool';
|
|
||||||
import { IAzureResourceContextService } from '../../../azureResource/interfaces';
|
|
||||||
import { IAzureResourceTreeChangeHandler } from '../../../azureResource/tree/treeChangeHandler';
|
|
||||||
import { AzureResourceDatabase } from '../../../azureResource/models';
|
|
||||||
import { AzureResourceItemType } from '../../../azureResource/constants';
|
|
||||||
import { AzureResourceDatabaseTreeNode } from '../../../azureResource/tree/databaseTreeNode';
|
|
||||||
|
|
||||||
// Mock services
|
|
||||||
const mockServicePool = AzureResourceServicePool.getInstance();
|
|
||||||
|
|
||||||
let mockContextService: TypeMoq.IMock<IAzureResourceContextService>;
|
|
||||||
|
|
||||||
let mockTreeChangeHandler: TypeMoq.IMock<IAzureResourceTreeChangeHandler>;
|
|
||||||
|
|
||||||
// Mock test data
|
|
||||||
const mockDatabase: AzureResourceDatabase = {
|
|
||||||
name: 'mock database 1',
|
|
||||||
serverName: 'mock server 1',
|
|
||||||
serverFullName: 'mock server 1',
|
|
||||||
loginName: 'mock user 1'
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('AzureResourceDatabaseTreeNode.info', function(): void {
|
|
||||||
beforeEach(() => {
|
|
||||||
mockContextService = TypeMoq.Mock.ofType<IAzureResourceContextService>();
|
|
||||||
|
|
||||||
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
|
|
||||||
|
|
||||||
mockServicePool.contextService = mockContextService.object;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should be correct.', async function(): Promise<void> {
|
|
||||||
const databaseTreeNode = new AzureResourceDatabaseTreeNode(mockDatabase, mockTreeChangeHandler.object, undefined);
|
|
||||||
|
|
||||||
const databaseTreeNodeLabel = `${mockDatabase.name} (${mockDatabase.serverName})`;
|
|
||||||
|
|
||||||
should(databaseTreeNode.nodePathValue).equal(`database_${mockDatabase.name}`);
|
|
||||||
|
|
||||||
const treeItem = await databaseTreeNode.getTreeItem();
|
|
||||||
should(treeItem.label).equal(databaseTreeNodeLabel);
|
|
||||||
should(treeItem.contextValue).equal(AzureResourceItemType.database);
|
|
||||||
should(treeItem.collapsibleState).equal(vscode.TreeItemCollapsibleState.None);
|
|
||||||
|
|
||||||
const nodeInfo = databaseTreeNode.getNodeInfo();
|
|
||||||
should(nodeInfo.isLeaf).true();
|
|
||||||
should(nodeInfo.label).equal(databaseTreeNodeLabel);
|
|
||||||
should(nodeInfo.nodeType).equal(AzureResourceItemType.database);
|
|
||||||
should(nodeInfo.iconType).equal(AzureResourceItemType.database);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -10,20 +10,24 @@ import * as TypeMoq from 'typemoq';
|
|||||||
import * as sqlops from 'sqlops';
|
import * as sqlops from 'sqlops';
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import 'mocha';
|
import 'mocha';
|
||||||
|
import { AppContext } from '../../../appContext';
|
||||||
|
import { ApiWrapper } from '../../../apiWrapper';
|
||||||
|
|
||||||
import { AzureResourceServicePool } from '../../../azureResource/servicePool';
|
import { azureResource } from '../../../azureResource/azure-resource';
|
||||||
import { IAzureResourceContextService } from '../../../azureResource/interfaces';
|
|
||||||
import { IAzureResourceTreeChangeHandler } from '../../../azureResource/tree/treeChangeHandler';
|
import { IAzureResourceTreeChangeHandler } from '../../../azureResource/tree/treeChangeHandler';
|
||||||
import { AzureResourceSubscription } from '../../../azureResource/models';
|
|
||||||
import { AzureResourceSubscriptionTreeNode } from '../../../azureResource/tree/subscriptionTreeNode';
|
import { AzureResourceSubscriptionTreeNode } from '../../../azureResource/tree/subscriptionTreeNode';
|
||||||
import { AzureResourceDatabaseContainerTreeNode } from '../../../azureResource/tree/databaseContainerTreeNode';
|
import { AzureResourceItemType, AzureResourceServiceNames } from '../../../azureResource/constants';
|
||||||
import { AzureResourceDatabaseServerContainerTreeNode } from '../../../azureResource/tree/databaseServerContainerTreeNode';
|
import { AzureResourceService } from '../../../azureResource/resourceService';
|
||||||
import { AzureResourceItemType } from '../../../azureResource/constants';
|
import { AzureResourceResourceTreeNode } from '../../../azureResource/resourceTreeNode';
|
||||||
|
import { IAzureResourceCacheService } from '../../../azureResource/interfaces';
|
||||||
|
import { generateGuid } from '../../../azureResource/utils';
|
||||||
|
|
||||||
// Mock services
|
// Mock services
|
||||||
const mockServicePool = AzureResourceServicePool.getInstance();
|
let mockAppContext: AppContext;
|
||||||
|
|
||||||
let mockContextService: TypeMoq.IMock<IAzureResourceContextService>;
|
let mockExtensionContext: TypeMoq.IMock<vscode.ExtensionContext>;
|
||||||
|
let mockApiWrapper: TypeMoq.IMock<ApiWrapper>;
|
||||||
|
let mockCacheService: TypeMoq.IMock<IAzureResourceCacheService>;
|
||||||
|
|
||||||
let mockTreeChangeHandler: TypeMoq.IMock<IAzureResourceTreeChangeHandler>;
|
let mockTreeChangeHandler: TypeMoq.IMock<IAzureResourceTreeChangeHandler>;
|
||||||
|
|
||||||
@@ -42,24 +46,60 @@ const mockAccount: sqlops.Account = {
|
|||||||
isStale: false
|
isStale: false
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockSubscription: AzureResourceSubscription = {
|
const mockSubscription: azureResource.AzureResourceSubscription = {
|
||||||
id: 'mock_subscription',
|
id: 'mock_subscription',
|
||||||
name: 'mock subscription'
|
name: 'mock subscription'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const mockTenantId: string = 'mock_tenant';
|
||||||
|
|
||||||
|
let mockResourceTreeDataProvider1: TypeMoq.IMock<azureResource.IAzureResourceTreeDataProvider>;
|
||||||
|
let mockResourceProvider1: TypeMoq.IMock<azureResource.IAzureResourceProvider>;
|
||||||
|
|
||||||
|
let mockResourceTreeDataProvider2: TypeMoq.IMock<azureResource.IAzureResourceTreeDataProvider>;
|
||||||
|
let mockResourceProvider2: TypeMoq.IMock<azureResource.IAzureResourceProvider>;
|
||||||
|
|
||||||
|
const resourceService: AzureResourceService = AzureResourceService.getInstance();
|
||||||
|
|
||||||
describe('AzureResourceSubscriptionTreeNode.info', function(): void {
|
describe('AzureResourceSubscriptionTreeNode.info', function(): void {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockContextService = TypeMoq.Mock.ofType<IAzureResourceContextService>();
|
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
|
||||||
|
mockApiWrapper = TypeMoq.Mock.ofType<ApiWrapper>();
|
||||||
|
mockCacheService = TypeMoq.Mock.ofType<IAzureResourceCacheService>();
|
||||||
|
|
||||||
|
mockAppContext = new AppContext(mockExtensionContext.object, mockApiWrapper.object);
|
||||||
|
|
||||||
|
mockAppContext.registerService<IAzureResourceCacheService>(AzureResourceServiceNames.cacheService, mockCacheService.object);
|
||||||
|
|
||||||
|
mockCacheService.setup((o) => o.generateKey(TypeMoq.It.isAnyString())).returns(() => generateGuid());
|
||||||
|
|
||||||
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
|
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
|
||||||
|
|
||||||
mockServicePool.contextService = mockContextService.object;
|
mockResourceTreeDataProvider1 = TypeMoq.Mock.ofType<azureResource.IAzureResourceTreeDataProvider>();
|
||||||
|
mockResourceTreeDataProvider1.setup((o) => o.getChildren()).returns(() => Promise.resolve([TypeMoq.Mock.ofType<azureResource.IAzureResourceNode>().object]));
|
||||||
|
mockResourceTreeDataProvider1.setup((o) => o.getTreeItem(TypeMoq.It.isAny())).returns(() => Promise.resolve(TypeMoq.It.isAny()));
|
||||||
|
mockResourceProvider1 = TypeMoq.Mock.ofType<azureResource.IAzureResourceProvider>();
|
||||||
|
mockResourceProvider1.setup((o) => o.providerId).returns(() => 'mockResourceProvider1');
|
||||||
|
mockResourceProvider1.setup((o) => o.getTreeDataProvider()).returns(() => mockResourceTreeDataProvider1.object);
|
||||||
|
|
||||||
|
mockResourceTreeDataProvider2 = TypeMoq.Mock.ofType<azureResource.IAzureResourceTreeDataProvider>();
|
||||||
|
mockResourceTreeDataProvider2.setup((o) => o.getChildren()).returns(() => Promise.resolve([TypeMoq.Mock.ofType<azureResource.IAzureResourceNode>().object]));
|
||||||
|
mockResourceTreeDataProvider2.setup((o) => o.getTreeItem(TypeMoq.It.isAny())).returns(() => Promise.resolve(TypeMoq.It.isAny()));
|
||||||
|
mockResourceProvider2 = TypeMoq.Mock.ofType<azureResource.IAzureResourceProvider>();
|
||||||
|
mockResourceProvider2.setup((o) => o.providerId).returns(() => 'mockResourceProvider2');
|
||||||
|
mockResourceProvider2.setup((o) => o.getTreeDataProvider()).returns(() => mockResourceTreeDataProvider2.object);
|
||||||
|
|
||||||
|
resourceService.clearResourceProviders();
|
||||||
|
resourceService.registerResourceProvider(mockResourceProvider1.object);
|
||||||
|
resourceService.registerResourceProvider(mockResourceProvider2.object);
|
||||||
|
|
||||||
|
resourceService.areResourceProvidersLoaded = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should be correct when created.', async function(): Promise<void> {
|
it('Should be correct when created.', async function(): Promise<void> {
|
||||||
const subscriptionTreeNode = new AzureResourceSubscriptionTreeNode(mockSubscription, mockAccount, mockTreeChangeHandler.object, undefined);
|
const subscriptionTreeNode = new AzureResourceSubscriptionTreeNode(mockAccount, mockSubscription, mockTenantId, mockAppContext, mockTreeChangeHandler.object, undefined);
|
||||||
|
|
||||||
should(subscriptionTreeNode.nodePathValue).equal(`subscription_${mockSubscription.id}`);
|
should(subscriptionTreeNode.nodePathValue).equal(`account_${mockAccount.key.accountId}.subscription_${mockSubscription.id}.tenant_${mockTenantId}`);
|
||||||
|
|
||||||
const treeItem = await subscriptionTreeNode.getTreeItem();
|
const treeItem = await subscriptionTreeNode.getTreeItem();
|
||||||
should(treeItem.label).equal(mockSubscription.name);
|
should(treeItem.label).equal(mockSubscription.name);
|
||||||
@@ -76,16 +116,52 @@ describe('AzureResourceSubscriptionTreeNode.info', function(): void {
|
|||||||
|
|
||||||
describe('AzureResourceSubscriptionTreeNode.getChildren', function(): void {
|
describe('AzureResourceSubscriptionTreeNode.getChildren', function(): void {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
|
||||||
|
mockApiWrapper = TypeMoq.Mock.ofType<ApiWrapper>();
|
||||||
|
mockCacheService = TypeMoq.Mock.ofType<IAzureResourceCacheService>();
|
||||||
|
|
||||||
|
mockAppContext = new AppContext(mockExtensionContext.object, mockApiWrapper.object);
|
||||||
|
mockAppContext.registerService<IAzureResourceCacheService>(AzureResourceServiceNames.cacheService, mockCacheService.object);
|
||||||
|
|
||||||
|
mockCacheService.setup((o) => o.generateKey(TypeMoq.It.isAnyString())).returns(() => generateGuid());
|
||||||
|
|
||||||
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
|
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
|
||||||
|
|
||||||
|
mockResourceTreeDataProvider1 = TypeMoq.Mock.ofType<azureResource.IAzureResourceTreeDataProvider>();
|
||||||
|
mockResourceTreeDataProvider1.setup((o) => o.getChildren()).returns(() => Promise.resolve([TypeMoq.Mock.ofType<azureResource.IAzureResourceNode>().object]));
|
||||||
|
mockResourceTreeDataProvider1.setup((o) => o.getTreeItem(TypeMoq.It.isAny())).returns(() => Promise.resolve(TypeMoq.It.isAny()));
|
||||||
|
mockResourceProvider1 = TypeMoq.Mock.ofType<azureResource.IAzureResourceProvider>();
|
||||||
|
mockResourceProvider1.setup((o) => o.providerId).returns(() => 'mockResourceProvider1');
|
||||||
|
mockResourceProvider1.setup((o) => o.getTreeDataProvider()).returns(() => mockResourceTreeDataProvider1.object);
|
||||||
|
|
||||||
|
mockResourceTreeDataProvider2 = TypeMoq.Mock.ofType<azureResource.IAzureResourceTreeDataProvider>();
|
||||||
|
mockResourceTreeDataProvider2.setup((o) => o.getChildren()).returns(() => Promise.resolve([TypeMoq.Mock.ofType<azureResource.IAzureResourceNode>().object]));
|
||||||
|
mockResourceTreeDataProvider2.setup((o) => o.getTreeItem(TypeMoq.It.isAny())).returns(() => Promise.resolve(TypeMoq.It.isAny()));
|
||||||
|
mockResourceProvider2 = TypeMoq.Mock.ofType<azureResource.IAzureResourceProvider>();
|
||||||
|
mockResourceProvider2.setup((o) => o.providerId).returns(() => 'mockResourceProvider2');
|
||||||
|
mockResourceProvider2.setup((o) => o.getTreeDataProvider()).returns(() => mockResourceTreeDataProvider2.object);
|
||||||
|
|
||||||
|
resourceService.clearResourceProviders();
|
||||||
|
resourceService.registerResourceProvider(mockResourceProvider1.object);
|
||||||
|
resourceService.registerResourceProvider(mockResourceProvider2.object);
|
||||||
|
|
||||||
|
resourceService.areResourceProvidersLoaded = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should load database containers.', async function(): Promise<void> {
|
it('Should return resource containers.', async function(): Promise<void> {
|
||||||
const subscriptionTreeNode = new AzureResourceSubscriptionTreeNode(mockSubscription, mockAccount, mockTreeChangeHandler.object, undefined);
|
const subscriptionTreeNode = new AzureResourceSubscriptionTreeNode(mockAccount, mockSubscription, mockTenantId, mockAppContext, mockTreeChangeHandler.object, undefined);
|
||||||
const children = await subscriptionTreeNode.getChildren();
|
const children = await subscriptionTreeNode.getChildren();
|
||||||
|
|
||||||
|
mockResourceTreeDataProvider1.verify((o) => o.getChildren(), TypeMoq.Times.once());
|
||||||
|
|
||||||
|
mockResourceTreeDataProvider2.verify((o) => o.getChildren(), TypeMoq.Times.once());
|
||||||
|
|
||||||
|
const expectedChildren = await resourceService.listResourceProviderIds();
|
||||||
|
|
||||||
should(children).Array();
|
should(children).Array();
|
||||||
should(children.length).equal(2);
|
should(children.length).equal(expectedChildren.length);
|
||||||
should(children[0]).instanceof(AzureResourceDatabaseContainerTreeNode);
|
for (const child of children) {
|
||||||
should(children[1]).instanceof(AzureResourceDatabaseServerContainerTreeNode);
|
should(child).instanceOf(AzureResourceResourceTreeNode);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,21 +5,28 @@
|
|||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
import * as vscode from 'vscode';
|
||||||
import * as should from 'should';
|
import * as should from 'should';
|
||||||
import * as TypeMoq from 'typemoq';
|
import * as TypeMoq from 'typemoq';
|
||||||
import * as sqlops from 'sqlops';
|
import * as sqlops from 'sqlops';
|
||||||
import 'mocha';
|
import 'mocha';
|
||||||
|
import { AppContext } from '../../../appContext';
|
||||||
|
import { ApiWrapper } from '../../../apiWrapper';
|
||||||
|
|
||||||
import { AzureResourceServicePool } from '../../../azureResource/servicePool';
|
import { IAzureResourceCacheService, IAzureResourceAccountService } from '../../../azureResource/interfaces';
|
||||||
import { IAzureResourceAccountService } from '../../../azureResource/interfaces';
|
|
||||||
import { AzureResourceTreeProvider } from '../../../azureResource/tree/treeProvider';
|
import { AzureResourceTreeProvider } from '../../../azureResource/tree/treeProvider';
|
||||||
import { AzureResourceAccountTreeNode } from '../../../azureResource/tree/accountTreeNode';
|
import { AzureResourceAccountTreeNode } from '../../../azureResource/tree/accountTreeNode';
|
||||||
import { AzureResourceAccountNotSignedInTreeNode } from '../../../azureResource/tree/accountNotSignedInTreeNode';
|
import { AzureResourceAccountNotSignedInTreeNode } from '../../../azureResource/tree/accountNotSignedInTreeNode';
|
||||||
import { AzureResourceMessageTreeNode } from '../../../azureResource/tree/messageTreeNode';
|
import { AzureResourceMessageTreeNode } from '../../../azureResource/messageTreeNode';
|
||||||
|
import { AzureResourceServiceNames } from '../../../azureResource/constants';
|
||||||
|
import { generateGuid } from '../../../azureResource/utils';
|
||||||
|
|
||||||
// Mock services
|
// Mock services
|
||||||
const mockServicePool = AzureResourceServicePool.getInstance();
|
let mockAppContext: AppContext;
|
||||||
|
|
||||||
|
let mockExtensionContext: TypeMoq.IMock<vscode.ExtensionContext>;
|
||||||
|
let mockApiWrapper: TypeMoq.IMock<ApiWrapper>;
|
||||||
|
let mockCacheService: TypeMoq.IMock<IAzureResourceCacheService>;
|
||||||
let mockAccountService: TypeMoq.IMock<IAzureResourceAccountService>;
|
let mockAccountService: TypeMoq.IMock<IAzureResourceAccountService>;
|
||||||
|
|
||||||
// Mock test data
|
// Mock test data
|
||||||
@@ -53,15 +60,23 @@ const mockAccounts = [mockAccount1, mockAccount2];
|
|||||||
|
|
||||||
describe('AzureResourceTreeProvider.getChildren', function(): void {
|
describe('AzureResourceTreeProvider.getChildren', function(): void {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
|
||||||
|
mockApiWrapper = TypeMoq.Mock.ofType<ApiWrapper>();
|
||||||
|
mockCacheService = TypeMoq.Mock.ofType<IAzureResourceCacheService>();
|
||||||
mockAccountService = TypeMoq.Mock.ofType<IAzureResourceAccountService>();
|
mockAccountService = TypeMoq.Mock.ofType<IAzureResourceAccountService>();
|
||||||
|
|
||||||
mockServicePool.accountService = mockAccountService.object;
|
mockAppContext = new AppContext(mockExtensionContext.object, mockApiWrapper.object);
|
||||||
|
|
||||||
|
mockAppContext.registerService<IAzureResourceCacheService>(AzureResourceServiceNames.cacheService, mockCacheService.object);
|
||||||
|
mockAppContext.registerService<IAzureResourceAccountService>(AzureResourceServiceNames.accountService, mockAccountService.object);
|
||||||
|
|
||||||
|
mockCacheService.setup((o) => o.generateKey(TypeMoq.It.isAnyString())).returns(() => generateGuid());
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should load accounts.', async function(): Promise<void> {
|
it('Should load accounts.', async function(): Promise<void> {
|
||||||
mockAccountService.setup((o) => o.getAccounts()).returns(() => Promise.resolve(mockAccounts));
|
mockAccountService.setup((o) => o.getAccounts()).returns(() => Promise.resolve(mockAccounts));
|
||||||
|
|
||||||
const treeProvider = new AzureResourceTreeProvider();
|
const treeProvider = new AzureResourceTreeProvider(mockAppContext);
|
||||||
treeProvider.isSystemInitialized = true;
|
treeProvider.isSystemInitialized = true;
|
||||||
|
|
||||||
const children = await treeProvider.getChildren(undefined);
|
const children = await treeProvider.getChildren(undefined);
|
||||||
@@ -83,7 +98,7 @@ describe('AzureResourceTreeProvider.getChildren', function(): void {
|
|||||||
it('Should handle when there is no accounts.', async function(): Promise<void> {
|
it('Should handle when there is no accounts.', async function(): Promise<void> {
|
||||||
mockAccountService.setup((o) => o.getAccounts()).returns(() => Promise.resolve(undefined));
|
mockAccountService.setup((o) => o.getAccounts()).returns(() => Promise.resolve(undefined));
|
||||||
|
|
||||||
const treeProvider = new AzureResourceTreeProvider();
|
const treeProvider = new AzureResourceTreeProvider(mockAppContext);
|
||||||
treeProvider.isSystemInitialized = true;
|
treeProvider.isSystemInitialized = true;
|
||||||
|
|
||||||
const children = await treeProvider.getChildren(undefined);
|
const children = await treeProvider.getChildren(undefined);
|
||||||
@@ -97,7 +112,7 @@ describe('AzureResourceTreeProvider.getChildren', function(): void {
|
|||||||
const mockAccountError = 'Test account error';
|
const mockAccountError = 'Test account error';
|
||||||
mockAccountService.setup((o) => o.getAccounts()).returns(() => { throw new Error(mockAccountError); });
|
mockAccountService.setup((o) => o.getAccounts()).returns(() => { throw new Error(mockAccountError); });
|
||||||
|
|
||||||
const treeProvider = new AzureResourceTreeProvider();
|
const treeProvider = new AzureResourceTreeProvider(mockAppContext);
|
||||||
treeProvider.isSystemInitialized = true;
|
treeProvider.isSystemInitialized = true;
|
||||||
|
|
||||||
const children = await treeProvider.getChildren(undefined);
|
const children = await treeProvider.getChildren(undefined);
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
"emitDecoratorMetadata": true,
|
"emitDecoratorMetadata": true,
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"declaration": true
|
"declaration": false
|
||||||
},
|
},
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"node_modules"
|
"node_modules"
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -5,11 +5,14 @@
|
|||||||
"@types/node@7.0.4":
|
"@types/node@7.0.4":
|
||||||
version "7.0.4"
|
version "7.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-7.0.4.tgz#9aabc135979ded383325749f508894c662948c8b"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-7.0.4.tgz#9aabc135979ded383325749f508894c662948c8b"
|
||||||
|
integrity sha1-mqvBNZed7TgzJXSfUIiUxmKUjIs=
|
||||||
|
|
||||||
jsonc-parser@^1.0.0:
|
jsonc-parser@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-1.0.0.tgz#ddcc864ae708e60a7a6dd36daea00172fa8d9272"
|
resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-1.0.0.tgz#ddcc864ae708e60a7a6dd36daea00172fa8d9272"
|
||||||
|
integrity sha1-3cyGSucI5gp6bdNtrqABcvqNknI=
|
||||||
|
|
||||||
vscode-nls@^3.2.4:
|
vscode-nls@^3.2.4:
|
||||||
version "3.2.4"
|
version "3.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.4.tgz#2166b4183c8aea884d20727f5449e62be69fd398"
|
resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.4.tgz#2166b4183c8aea884d20727f5449e62be69fd398"
|
||||||
|
integrity sha512-FTjdqa4jDDoBjJqr36O8lmmZf/55kQ2w4ZY/+GL6K92fq765BqO3aYw21atnXUno/P04V5DWagNl4ybDIndJsw==
|
||||||
|
|||||||
@@ -5,34 +5,41 @@
|
|||||||
"@types/markdown-it@0.0.2":
|
"@types/markdown-it@0.0.2":
|
||||||
version "0.0.2"
|
version "0.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/@types/markdown-it/-/markdown-it-0.0.2.tgz#5d9ad19e6e6508cdd2f2596df86fd0aade598660"
|
resolved "https://registry.yarnpkg.com/@types/markdown-it/-/markdown-it-0.0.2.tgz#5d9ad19e6e6508cdd2f2596df86fd0aade598660"
|
||||||
|
integrity sha1-XZrRnm5lCM3S8llt+G/Qqt5ZhmA=
|
||||||
|
|
||||||
"@types/node@6.0.78", "@types/node@^6.0.46":
|
"@types/node@6.0.78", "@types/node@^6.0.46":
|
||||||
version "6.0.78"
|
version "6.0.78"
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-6.0.78.tgz#5d4a3f579c1524e01ee21bf474e6fba09198f470"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-6.0.78.tgz#5d4a3f579c1524e01ee21bf474e6fba09198f470"
|
||||||
|
integrity sha512-+vD6E8ixntRzzZukoF3uP1iV+ZjVN3koTcaeK+BEoc/kSfGbLDIGC7RmCaUgVpUfN6cWvfczFRERCyKM9mkvXg==
|
||||||
|
|
||||||
argparse@^1.0.7:
|
argparse@^1.0.7:
|
||||||
version "1.0.9"
|
version "1.0.9"
|
||||||
resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.9.tgz#73d83bc263f86e97f8cc4f6bae1b0e90a7d22c86"
|
resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.9.tgz#73d83bc263f86e97f8cc4f6bae1b0e90a7d22c86"
|
||||||
|
integrity sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY=
|
||||||
dependencies:
|
dependencies:
|
||||||
sprintf-js "~1.0.2"
|
sprintf-js "~1.0.2"
|
||||||
|
|
||||||
entities@~1.1.1:
|
entities@~1.1.1:
|
||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0"
|
resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0"
|
||||||
|
integrity sha1-blwtClYhtdra7O+AuQ7ftc13cvA=
|
||||||
|
|
||||||
jsonc-parser@^1.0.0:
|
jsonc-parser@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-1.0.0.tgz#ddcc864ae708e60a7a6dd36daea00172fa8d9272"
|
resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-1.0.0.tgz#ddcc864ae708e60a7a6dd36daea00172fa8d9272"
|
||||||
|
integrity sha1-3cyGSucI5gp6bdNtrqABcvqNknI=
|
||||||
|
|
||||||
linkify-it@^2.0.0:
|
linkify-it@^2.0.0:
|
||||||
version "2.0.3"
|
version "2.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-2.0.3.tgz#d94a4648f9b1c179d64fa97291268bdb6ce9434f"
|
resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-2.0.3.tgz#d94a4648f9b1c179d64fa97291268bdb6ce9434f"
|
||||||
|
integrity sha1-2UpGSPmxwXnWT6lykSaL22zpQ08=
|
||||||
dependencies:
|
dependencies:
|
||||||
uc.micro "^1.0.1"
|
uc.micro "^1.0.1"
|
||||||
|
|
||||||
markdown-it@^8.3.1:
|
markdown-it@^8.3.1:
|
||||||
version "8.4.0"
|
version "8.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-8.4.0.tgz#e2400881bf171f7018ed1bd9da441dac8af6306d"
|
resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-8.4.0.tgz#e2400881bf171f7018ed1bd9da441dac8af6306d"
|
||||||
|
integrity sha512-tNuOCCfunY5v5uhcO2AUMArvKAyKMygX8tfup/JrgnsDqcCATQsAExBq7o5Ml9iMmO82bk6jYNLj6khcrl0JGA==
|
||||||
dependencies:
|
dependencies:
|
||||||
argparse "^1.0.7"
|
argparse "^1.0.7"
|
||||||
entities "~1.1.1"
|
entities "~1.1.1"
|
||||||
@@ -43,21 +50,26 @@ markdown-it@^8.3.1:
|
|||||||
mdurl@^1.0.1:
|
mdurl@^1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e"
|
resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e"
|
||||||
|
integrity sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=
|
||||||
|
|
||||||
parse5@^3.0.2:
|
parse5@^3.0.2:
|
||||||
version "3.0.2"
|
version "3.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/parse5/-/parse5-3.0.2.tgz#05eff57f0ef4577fb144a79f8b9a967a6cc44510"
|
resolved "https://registry.yarnpkg.com/parse5/-/parse5-3.0.2.tgz#05eff57f0ef4577fb144a79f8b9a967a6cc44510"
|
||||||
|
integrity sha1-Be/1fw70V3+xRKefi5qWemzERRA=
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/node" "^6.0.46"
|
"@types/node" "^6.0.46"
|
||||||
|
|
||||||
sprintf-js@~1.0.2:
|
sprintf-js@~1.0.2:
|
||||||
version "1.0.3"
|
version "1.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
|
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
|
||||||
|
integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=
|
||||||
|
|
||||||
uc.micro@^1.0.1, uc.micro@^1.0.3:
|
uc.micro@^1.0.1, uc.micro@^1.0.3:
|
||||||
version "1.0.3"
|
version "1.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.3.tgz#7ed50d5e0f9a9fb0a573379259f2a77458d50192"
|
resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.3.tgz#7ed50d5e0f9a9fb0a573379259f2a77458d50192"
|
||||||
|
integrity sha1-ftUNXg+an7ClczeSWfKndFjVAZI=
|
||||||
|
|
||||||
vscode-nls@^3.2.4:
|
vscode-nls@^3.2.4:
|
||||||
version "3.2.4"
|
version "3.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.4.tgz#2166b4183c8aea884d20727f5449e62be69fd398"
|
resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.4.tgz#2166b4183c8aea884d20727f5449e62be69fd398"
|
||||||
|
integrity sha512-FTjdqa4jDDoBjJqr36O8lmmZf/55kQ2w4ZY/+GL6K92fq765BqO3aYw21atnXUno/P04V5DWagNl4ybDIndJsw==
|
||||||
|
|||||||
@@ -5,34 +5,41 @@
|
|||||||
"@types/byline@4.2.31":
|
"@types/byline@4.2.31":
|
||||||
version "4.2.31"
|
version "4.2.31"
|
||||||
resolved "https://registry.yarnpkg.com/@types/byline/-/byline-4.2.31.tgz#0e61fcb9c03e047d21c4496554c7116297ab60cd"
|
resolved "https://registry.yarnpkg.com/@types/byline/-/byline-4.2.31.tgz#0e61fcb9c03e047d21c4496554c7116297ab60cd"
|
||||||
|
integrity sha1-DmH8ucA+BH0hxEllVMcRYperYM0=
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
|
|
||||||
"@types/file-type@^5.2.1":
|
"@types/file-type@^5.2.1":
|
||||||
version "5.2.1"
|
version "5.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/@types/file-type/-/file-type-5.2.1.tgz#e7af49e08187b6b7598509c5e416669d25fa3461"
|
resolved "https://registry.yarnpkg.com/@types/file-type/-/file-type-5.2.1.tgz#e7af49e08187b6b7598509c5e416669d25fa3461"
|
||||||
|
integrity sha512-Im0cJaIPJbbpuW91OrjXnqWPZCJK/tcFy2cFX+1qjG1gubgVZPPO9OVsTVAjotN4I1E6FAV0eIqt+rR8Y1c3iA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
|
|
||||||
"@types/mocha@2.2.43":
|
"@types/mocha@2.2.43":
|
||||||
version "2.2.43"
|
version "2.2.43"
|
||||||
resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-2.2.43.tgz#03c54589c43ad048cbcbfd63999b55d0424eec27"
|
resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-2.2.43.tgz#03c54589c43ad048cbcbfd63999b55d0424eec27"
|
||||||
|
integrity sha512-xNlAmH+lRJdUMXClMTI9Y0pRqIojdxfm7DHsIxoB2iTzu3fnPmSMEN8SsSx0cdwV36d02PWCWaDUoZPDSln+xw==
|
||||||
|
|
||||||
"@types/node@*":
|
"@types/node@*":
|
||||||
version "8.0.51"
|
version "8.0.51"
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.51.tgz#b31d716fb8d58eeb95c068a039b9b6292817d5fb"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.51.tgz#b31d716fb8d58eeb95c068a039b9b6292817d5fb"
|
||||||
|
integrity sha512-El3+WJk2D/ppWNd2X05aiP5l2k4EwF7KwheknQZls+I26eSICoWRhRIJ56jGgw2dqNGQ5LtNajmBU2ajS28EvQ==
|
||||||
|
|
||||||
"@types/node@7.0.43":
|
"@types/node@7.0.43":
|
||||||
version "7.0.43"
|
version "7.0.43"
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-7.0.43.tgz#a187e08495a075f200ca946079c914e1a5fe962c"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-7.0.43.tgz#a187e08495a075f200ca946079c914e1a5fe962c"
|
||||||
|
integrity sha512-7scYwwfHNppXvH/9JzakbVxk0o0QUILVk1Lv64GRaxwPuGpnF1QBiwdvhDpLcymb8BpomQL3KYoWKq3wUdDMhQ==
|
||||||
|
|
||||||
"@types/which@^1.0.28":
|
"@types/which@^1.0.28":
|
||||||
version "1.0.28"
|
version "1.0.28"
|
||||||
resolved "https://registry.yarnpkg.com/@types/which/-/which-1.0.28.tgz#016e387629b8817bed653fe32eab5d11279c8df6"
|
resolved "https://registry.yarnpkg.com/@types/which/-/which-1.0.28.tgz#016e387629b8817bed653fe32eab5d11279c8df6"
|
||||||
|
integrity sha1-AW44dim4gXvtZT/jLqtdESecjfY=
|
||||||
|
|
||||||
applicationinsights@1.0.1:
|
applicationinsights@1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-1.0.1.tgz#53446b830fe8d5d619eee2a278b31d3d25030927"
|
resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-1.0.1.tgz#53446b830fe8d5d619eee2a278b31d3d25030927"
|
||||||
|
integrity sha1-U0Rrgw/o1dYZ7uKieLMdPSUDCSc=
|
||||||
dependencies:
|
dependencies:
|
||||||
diagnostic-channel "0.2.0"
|
diagnostic-channel "0.2.0"
|
||||||
diagnostic-channel-publishers "0.2.1"
|
diagnostic-channel-publishers "0.2.1"
|
||||||
@@ -41,10 +48,12 @@ applicationinsights@1.0.1:
|
|||||||
balanced-match@^1.0.0:
|
balanced-match@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
|
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
|
||||||
|
integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
|
||||||
|
|
||||||
brace-expansion@^1.1.7:
|
brace-expansion@^1.1.7:
|
||||||
version "1.1.8"
|
version "1.1.8"
|
||||||
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292"
|
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292"
|
||||||
|
integrity sha1-wHshHHyVLsH479Uad+8NHTmQopI=
|
||||||
dependencies:
|
dependencies:
|
||||||
balanced-match "^1.0.0"
|
balanced-match "^1.0.0"
|
||||||
concat-map "0.0.1"
|
concat-map "0.0.1"
|
||||||
@@ -52,56 +61,68 @@ brace-expansion@^1.1.7:
|
|||||||
browser-stdout@1.3.0:
|
browser-stdout@1.3.0:
|
||||||
version "1.3.0"
|
version "1.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.0.tgz#f351d32969d32fa5d7a5567154263d928ae3bd1f"
|
resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.0.tgz#f351d32969d32fa5d7a5567154263d928ae3bd1f"
|
||||||
|
integrity sha1-81HTKWnTL6XXpVZxVCY9korjvR8=
|
||||||
|
|
||||||
byline@^5.0.0:
|
byline@^5.0.0:
|
||||||
version "5.0.0"
|
version "5.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/byline/-/byline-5.0.0.tgz#741c5216468eadc457b03410118ad77de8c1ddb1"
|
resolved "https://registry.yarnpkg.com/byline/-/byline-5.0.0.tgz#741c5216468eadc457b03410118ad77de8c1ddb1"
|
||||||
|
integrity sha1-dBxSFkaOrcRXsDQQEYrXfejB3bE=
|
||||||
|
|
||||||
commander@2.9.0:
|
commander@2.9.0:
|
||||||
version "2.9.0"
|
version "2.9.0"
|
||||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4"
|
resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4"
|
||||||
|
integrity sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=
|
||||||
dependencies:
|
dependencies:
|
||||||
graceful-readlink ">= 1.0.0"
|
graceful-readlink ">= 1.0.0"
|
||||||
|
|
||||||
concat-map@0.0.1:
|
concat-map@0.0.1:
|
||||||
version "0.0.1"
|
version "0.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
|
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
|
||||||
|
integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
|
||||||
|
|
||||||
debug@2.6.8:
|
debug@2.6.8:
|
||||||
version "2.6.8"
|
version "2.6.8"
|
||||||
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc"
|
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc"
|
||||||
|
integrity sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=
|
||||||
dependencies:
|
dependencies:
|
||||||
ms "2.0.0"
|
ms "2.0.0"
|
||||||
|
|
||||||
diagnostic-channel-publishers@0.2.1:
|
diagnostic-channel-publishers@0.2.1:
|
||||||
version "0.2.1"
|
version "0.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/diagnostic-channel-publishers/-/diagnostic-channel-publishers-0.2.1.tgz#8e2d607a8b6d79fe880b548bc58cc6beb288c4f3"
|
resolved "https://registry.yarnpkg.com/diagnostic-channel-publishers/-/diagnostic-channel-publishers-0.2.1.tgz#8e2d607a8b6d79fe880b548bc58cc6beb288c4f3"
|
||||||
|
integrity sha1-ji1geottef6IC1SLxYzGvrKIxPM=
|
||||||
|
|
||||||
diagnostic-channel@0.2.0:
|
diagnostic-channel@0.2.0:
|
||||||
version "0.2.0"
|
version "0.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/diagnostic-channel/-/diagnostic-channel-0.2.0.tgz#cc99af9612c23fb1fff13612c72f2cbfaa8d5a17"
|
resolved "https://registry.yarnpkg.com/diagnostic-channel/-/diagnostic-channel-0.2.0.tgz#cc99af9612c23fb1fff13612c72f2cbfaa8d5a17"
|
||||||
|
integrity sha1-zJmvlhLCP7H/8TYSxy8sv6qNWhc=
|
||||||
dependencies:
|
dependencies:
|
||||||
semver "^5.3.0"
|
semver "^5.3.0"
|
||||||
|
|
||||||
diff@3.2.0:
|
diff@3.2.0:
|
||||||
version "3.2.0"
|
version "3.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/diff/-/diff-3.2.0.tgz#c9ce393a4b7cbd0b058a725c93df299027868ff9"
|
resolved "https://registry.yarnpkg.com/diff/-/diff-3.2.0.tgz#c9ce393a4b7cbd0b058a725c93df299027868ff9"
|
||||||
|
integrity sha1-yc45Okt8vQsFinJck98pkCeGj/k=
|
||||||
|
|
||||||
escape-string-regexp@1.0.5:
|
escape-string-regexp@1.0.5:
|
||||||
version "1.0.5"
|
version "1.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
|
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
|
||||||
|
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
|
||||||
|
|
||||||
file-type@^7.2.0:
|
file-type@^7.2.0:
|
||||||
version "7.2.0"
|
version "7.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/file-type/-/file-type-7.2.0.tgz#113cfed52e1d6959ab80248906e2f25a8cdccb74"
|
resolved "https://registry.yarnpkg.com/file-type/-/file-type-7.2.0.tgz#113cfed52e1d6959ab80248906e2f25a8cdccb74"
|
||||||
|
integrity sha1-ETz+1S4daVmrgCSJBuLyWozcy3Q=
|
||||||
|
|
||||||
fs.realpath@^1.0.0:
|
fs.realpath@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
|
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
|
||||||
|
integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
|
||||||
|
|
||||||
glob@7.1.1:
|
glob@7.1.1:
|
||||||
version "7.1.1"
|
version "7.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8"
|
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8"
|
||||||
|
integrity sha1-gFIR3wT6rxxjo2ADBs31reULLsg=
|
||||||
dependencies:
|
dependencies:
|
||||||
fs.realpath "^1.0.0"
|
fs.realpath "^1.0.0"
|
||||||
inflight "^1.0.4"
|
inflight "^1.0.4"
|
||||||
@@ -113,26 +134,32 @@ glob@7.1.1:
|
|||||||
"graceful-readlink@>= 1.0.0":
|
"graceful-readlink@>= 1.0.0":
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725"
|
resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725"
|
||||||
|
integrity sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=
|
||||||
|
|
||||||
growl@1.9.2:
|
growl@1.9.2:
|
||||||
version "1.9.2"
|
version "1.9.2"
|
||||||
resolved "https://registry.yarnpkg.com/growl/-/growl-1.9.2.tgz#0ea7743715db8d8de2c5ede1775e1b45ac85c02f"
|
resolved "https://registry.yarnpkg.com/growl/-/growl-1.9.2.tgz#0ea7743715db8d8de2c5ede1775e1b45ac85c02f"
|
||||||
|
integrity sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=
|
||||||
|
|
||||||
has-flag@^1.0.0:
|
has-flag@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa"
|
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa"
|
||||||
|
integrity sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=
|
||||||
|
|
||||||
he@1.1.1:
|
he@1.1.1:
|
||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd"
|
resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd"
|
||||||
|
integrity sha1-k0EP0hsAlzUVH4howvJx80J+I/0=
|
||||||
|
|
||||||
iconv-lite@0.4.19:
|
iconv-lite@0.4.19:
|
||||||
version "0.4.19"
|
version "0.4.19"
|
||||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b"
|
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b"
|
||||||
|
integrity sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==
|
||||||
|
|
||||||
inflight@^1.0.4:
|
inflight@^1.0.4:
|
||||||
version "1.0.6"
|
version "1.0.6"
|
||||||
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
|
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
|
||||||
|
integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=
|
||||||
dependencies:
|
dependencies:
|
||||||
once "^1.3.0"
|
once "^1.3.0"
|
||||||
wrappy "1"
|
wrappy "1"
|
||||||
@@ -140,22 +167,27 @@ inflight@^1.0.4:
|
|||||||
inherits@2:
|
inherits@2:
|
||||||
version "2.0.3"
|
version "2.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
|
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
|
||||||
|
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
|
||||||
|
|
||||||
isexe@^2.0.0:
|
isexe@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
|
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
|
||||||
|
integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=
|
||||||
|
|
||||||
jschardet@^1.6.0:
|
jschardet@^1.6.0:
|
||||||
version "1.6.0"
|
version "1.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/jschardet/-/jschardet-1.6.0.tgz#c7d1a71edcff2839db2f9ec30fc5d5ebd3c1a678"
|
resolved "https://registry.yarnpkg.com/jschardet/-/jschardet-1.6.0.tgz#c7d1a71edcff2839db2f9ec30fc5d5ebd3c1a678"
|
||||||
|
integrity sha512-xYuhvQ7I9PDJIGBWev9xm0+SMSed3ZDBAmvVjbFR1ZRLAF+vlXcQu6cRI9uAlj81rzikElRVteehwV7DuX2ZmQ==
|
||||||
|
|
||||||
json3@3.3.2:
|
json3@3.3.2:
|
||||||
version "3.3.2"
|
version "3.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.2.tgz#3c0434743df93e2f5c42aee7b19bcb483575f4e1"
|
resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.2.tgz#3c0434743df93e2f5c42aee7b19bcb483575f4e1"
|
||||||
|
integrity sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=
|
||||||
|
|
||||||
lodash._baseassign@^3.0.0:
|
lodash._baseassign@^3.0.0:
|
||||||
version "3.2.0"
|
version "3.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz#8c38a099500f215ad09e59f1722fd0c52bfe0a4e"
|
resolved "https://registry.yarnpkg.com/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz#8c38a099500f215ad09e59f1722fd0c52bfe0a4e"
|
||||||
|
integrity sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=
|
||||||
dependencies:
|
dependencies:
|
||||||
lodash._basecopy "^3.0.0"
|
lodash._basecopy "^3.0.0"
|
||||||
lodash.keys "^3.0.0"
|
lodash.keys "^3.0.0"
|
||||||
@@ -163,22 +195,27 @@ lodash._baseassign@^3.0.0:
|
|||||||
lodash._basecopy@^3.0.0:
|
lodash._basecopy@^3.0.0:
|
||||||
version "3.0.1"
|
version "3.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz#8da0e6a876cf344c0ad8a54882111dd3c5c7ca36"
|
resolved "https://registry.yarnpkg.com/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz#8da0e6a876cf344c0ad8a54882111dd3c5c7ca36"
|
||||||
|
integrity sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=
|
||||||
|
|
||||||
lodash._basecreate@^3.0.0:
|
lodash._basecreate@^3.0.0:
|
||||||
version "3.0.3"
|
version "3.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz#1bc661614daa7fc311b7d03bf16806a0213cf821"
|
resolved "https://registry.yarnpkg.com/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz#1bc661614daa7fc311b7d03bf16806a0213cf821"
|
||||||
|
integrity sha1-G8ZhYU2qf8MRt9A78WgGoCE8+CE=
|
||||||
|
|
||||||
lodash._getnative@^3.0.0:
|
lodash._getnative@^3.0.0:
|
||||||
version "3.9.1"
|
version "3.9.1"
|
||||||
resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5"
|
resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5"
|
||||||
|
integrity sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=
|
||||||
|
|
||||||
lodash._isiterateecall@^3.0.0:
|
lodash._isiterateecall@^3.0.0:
|
||||||
version "3.0.9"
|
version "3.0.9"
|
||||||
resolved "https://registry.yarnpkg.com/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz#5203ad7ba425fae842460e696db9cf3e6aac057c"
|
resolved "https://registry.yarnpkg.com/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz#5203ad7ba425fae842460e696db9cf3e6aac057c"
|
||||||
|
integrity sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=
|
||||||
|
|
||||||
lodash.create@3.1.1:
|
lodash.create@3.1.1:
|
||||||
version "3.1.1"
|
version "3.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.create/-/lodash.create-3.1.1.tgz#d7f2849f0dbda7e04682bb8cd72ab022461debe7"
|
resolved "https://registry.yarnpkg.com/lodash.create/-/lodash.create-3.1.1.tgz#d7f2849f0dbda7e04682bb8cd72ab022461debe7"
|
||||||
|
integrity sha1-1/KEnw29p+BGgruM1yqwIkYd6+c=
|
||||||
dependencies:
|
dependencies:
|
||||||
lodash._baseassign "^3.0.0"
|
lodash._baseassign "^3.0.0"
|
||||||
lodash._basecreate "^3.0.0"
|
lodash._basecreate "^3.0.0"
|
||||||
@@ -187,14 +224,17 @@ lodash.create@3.1.1:
|
|||||||
lodash.isarguments@^3.0.0:
|
lodash.isarguments@^3.0.0:
|
||||||
version "3.1.0"
|
version "3.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a"
|
resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a"
|
||||||
|
integrity sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=
|
||||||
|
|
||||||
lodash.isarray@^3.0.0:
|
lodash.isarray@^3.0.0:
|
||||||
version "3.0.4"
|
version "3.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55"
|
resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55"
|
||||||
|
integrity sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=
|
||||||
|
|
||||||
lodash.keys@^3.0.0:
|
lodash.keys@^3.0.0:
|
||||||
version "3.1.2"
|
version "3.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-3.1.2.tgz#4dbc0472b156be50a0b286855d1bd0b0c656098a"
|
resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-3.1.2.tgz#4dbc0472b156be50a0b286855d1bd0b0c656098a"
|
||||||
|
integrity sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=
|
||||||
dependencies:
|
dependencies:
|
||||||
lodash._getnative "^3.0.0"
|
lodash._getnative "^3.0.0"
|
||||||
lodash.isarguments "^3.0.0"
|
lodash.isarguments "^3.0.0"
|
||||||
@@ -203,22 +243,26 @@ lodash.keys@^3.0.0:
|
|||||||
minimatch@^3.0.2:
|
minimatch@^3.0.2:
|
||||||
version "3.0.4"
|
version "3.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
|
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
|
||||||
|
integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
|
||||||
dependencies:
|
dependencies:
|
||||||
brace-expansion "^1.1.7"
|
brace-expansion "^1.1.7"
|
||||||
|
|
||||||
minimist@0.0.8:
|
minimist@0.0.8:
|
||||||
version "0.0.8"
|
version "0.0.8"
|
||||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
|
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
|
||||||
|
integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=
|
||||||
|
|
||||||
mkdirp@0.5.1:
|
mkdirp@0.5.1:
|
||||||
version "0.5.1"
|
version "0.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
|
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
|
||||||
|
integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=
|
||||||
dependencies:
|
dependencies:
|
||||||
minimist "0.0.8"
|
minimist "0.0.8"
|
||||||
|
|
||||||
mocha@^3.2.0:
|
mocha@^3.2.0:
|
||||||
version "3.5.3"
|
version "3.5.3"
|
||||||
resolved "https://registry.yarnpkg.com/mocha/-/mocha-3.5.3.tgz#1e0480fe36d2da5858d1eb6acc38418b26eaa20d"
|
resolved "https://registry.yarnpkg.com/mocha/-/mocha-3.5.3.tgz#1e0480fe36d2da5858d1eb6acc38418b26eaa20d"
|
||||||
|
integrity sha512-/6na001MJWEtYxHOV1WLfsmR4YIynkUEhBwzsb+fk2qmQ3iqsi258l/Q2MWHJMImAcNpZ8DEdYAK72NHoIQ9Eg==
|
||||||
dependencies:
|
dependencies:
|
||||||
browser-stdout "1.3.0"
|
browser-stdout "1.3.0"
|
||||||
commander "2.9.0"
|
commander "2.9.0"
|
||||||
@@ -236,47 +280,57 @@ mocha@^3.2.0:
|
|||||||
ms@2.0.0:
|
ms@2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
||||||
|
integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
|
||||||
|
|
||||||
once@^1.3.0:
|
once@^1.3.0:
|
||||||
version "1.4.0"
|
version "1.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
|
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
|
||||||
|
integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
|
||||||
dependencies:
|
dependencies:
|
||||||
wrappy "1"
|
wrappy "1"
|
||||||
|
|
||||||
path-is-absolute@^1.0.0:
|
path-is-absolute@^1.0.0:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
|
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
|
||||||
|
integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
|
||||||
|
|
||||||
semver@^5.3.0:
|
semver@^5.3.0:
|
||||||
version "5.5.0"
|
version "5.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab"
|
resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab"
|
||||||
|
integrity sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==
|
||||||
|
|
||||||
supports-color@3.1.2:
|
supports-color@3.1.2:
|
||||||
version "3.1.2"
|
version "3.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.1.2.tgz#72a262894d9d408b956ca05ff37b2ed8a6e2a2d5"
|
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.1.2.tgz#72a262894d9d408b956ca05ff37b2ed8a6e2a2d5"
|
||||||
|
integrity sha1-cqJiiU2dQIuVbKBf83su2KbiotU=
|
||||||
dependencies:
|
dependencies:
|
||||||
has-flag "^1.0.0"
|
has-flag "^1.0.0"
|
||||||
|
|
||||||
vscode-extension-telemetry@0.0.18:
|
vscode-extension-telemetry@0.0.18:
|
||||||
version "0.0.18"
|
version "0.0.18"
|
||||||
resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.0.18.tgz#602ba20d8c71453aa34533a291e7638f6e5c0327"
|
resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.0.18.tgz#602ba20d8c71453aa34533a291e7638f6e5c0327"
|
||||||
|
integrity sha512-Vw3Sr+dZwl+c6PlsUwrTtCOJkgrmvS3OUVDQGcmpXWAgq9xGq6as0K4pUx+aGqTjzLAESmWSrs6HlJm6J6Khcg==
|
||||||
dependencies:
|
dependencies:
|
||||||
applicationinsights "1.0.1"
|
applicationinsights "1.0.1"
|
||||||
|
|
||||||
vscode-nls@^3.2.4:
|
vscode-nls@^3.2.4:
|
||||||
version "3.2.4"
|
version "3.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.4.tgz#2166b4183c8aea884d20727f5449e62be69fd398"
|
resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.4.tgz#2166b4183c8aea884d20727f5449e62be69fd398"
|
||||||
|
integrity sha512-FTjdqa4jDDoBjJqr36O8lmmZf/55kQ2w4ZY/+GL6K92fq765BqO3aYw21atnXUno/P04V5DWagNl4ybDIndJsw==
|
||||||
|
|
||||||
which@^1.3.0:
|
which@^1.3.0:
|
||||||
version "1.3.0"
|
version "1.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/which/-/which-1.3.0.tgz#ff04bdfc010ee547d780bec38e1ac1c2777d253a"
|
resolved "https://registry.yarnpkg.com/which/-/which-1.3.0.tgz#ff04bdfc010ee547d780bec38e1ac1c2777d253a"
|
||||||
|
integrity sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==
|
||||||
dependencies:
|
dependencies:
|
||||||
isexe "^2.0.0"
|
isexe "^2.0.0"
|
||||||
|
|
||||||
wrappy@1:
|
wrappy@1:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
||||||
|
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
|
||||||
|
|
||||||
zone.js@0.7.6:
|
zone.js@0.7.6:
|
||||||
version "0.7.6"
|
version "0.7.6"
|
||||||
resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.7.6.tgz#fbbc39d3e0261d0986f1ba06306eb3aeb0d22009"
|
resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.7.6.tgz#fbbc39d3e0261d0986f1ba06306eb3aeb0d22009"
|
||||||
|
integrity sha1-+7w50+AmHQmG8boGMG6zrrDSIAk=
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user