Compare commits

...

106 Commits
1.3.6 ... 1.4.2

Author SHA1 Message Date
Karl Burtram
637dc9b9b2 Bump Azure Data Studio to 1.4.2 2019-01-18 09:48:44 -08:00
Karl Burtram
1de16d4715 Reset query messages for each execution (#3772) 2019-01-17 17:53:05 -08:00
Kevin Cunnane
49090d774d Null ref occurred when doing some UI interactions before the notebook model was set (#3769) 2019-01-17 14:41:56 -08:00
Karl Burtram
9a695b5cdd Reenable results stream by default (#3752) 2019-01-17 10:28:48 -08:00
Raj
e0339b50c0 #3753: User settings configuration - python installation path (#3754)
* #3753: User settings configuration - python installation path

* Text change

* #3753: Text change

* Message change
2019-01-16 16:29:06 -08:00
Karl Burtram
d0c584672f Fix Top Operations tab title (#3751) 2019-01-15 16:06:51 -08:00
Anthony Dresser
27816acaeb Remove custom splitview (#3467)
* working on options dialog

* working through options dialog

* trying to work through modifying options dialog

* working on converting scrollablesplitview

* fixed options working through profiler

* fix profiler

* fix account dialog

* trying to fix problems with splitpanel

* fix insights dialog

* moving through

* fix last list, need to verify looks and functionality

* fix look of account dialog

* formatting

* formatting

* working through scrollable bugs

* working on problem with view size

* fix margin issues

* fix styler for dialogs

* add panel styles to insights

* create instantiation issues

* fix test

* fix test

* remove unused code

* formatting

* working through insight dialog issues

* fix table updating

* remove console logs
2019-01-15 15:00:34 -08:00
Mustafa Sadedil
4de3cc8a09 Completed: Missing feature request: Save as XML (#3729)
* Save as XML feature added to grid

* Unrelated code removed
2019-01-15 14:36:42 -08:00
Karl Burtram
5c16ceb2fa Bump SQL Tools Service to pick up https://github.com/Microsoft/sqltoolsservice/pull/763 (#3748) 2019-01-15 14:12:20 -08:00
Raj
9db3f73413 Notebook Doesn't Prompt for Save even when isDirty #3568 (#3656)
This is temp fix until native save is implemented.
2019-01-15 11:41:05 -08:00
Chris LaFreniere
e0ceddce09 Notebooks: Add Placeholder Cell, Fix Link Styling (#3728)
* Placeholder cell to add new real cells

* Fix links in notebooks to show correct color, rely on angular ngif for placeholder

* Fix failing test where one cell was expected by default

* Remove unnecessary TODO
2019-01-14 17:29:06 -08:00
Chris LaFreniere
6dc4096299 Editor focus based on activeCell, create text in edit, scroll to active (#3725) 2019-01-14 16:39:36 -08:00
Chris LaFreniere
1fa03b5c74 Ensure we always get all providers (#3724) 2019-01-14 16:38:57 -08:00
Kevin Cunnane
f8f57a93c3 Fix #3736 Notebook: cannot connect to SQL big data cluster due to empty config.json file (#3738)
- Writing the config file in the core for now, will look to move to the extension in Feb release
2019-01-14 14:14:32 -08:00
Karl Burtram
960fe63312 Bump Data Protocol client to 0.2.11 (#3739) 2019-01-14 14:04:35 -08:00
Karl Burtram
7545b94128 Turn off "something went wrong" message (#3606) 2019-01-11 17:39:13 -08:00
Karl Burtram
1263a27c1c Fix date in change log to 2019 (#3726) 2019-01-11 16:55:18 -08:00
Anthony Dresser
e1c084d365 fix html formatting in grid (#3722) 2019-01-11 16:24:50 -08:00
Karl Burtram
7465ec0bbd Add connection dialog icon dark theme and HC styles (#3721) 2019-01-11 13:38:43 -08:00
Chris LaFreniere
17ed57836f Fix focus issue when opening notebooks (#3711) 2019-01-11 11:37:49 -08:00
Chris LaFreniere
d0acb51fd7 Fix contentManager undefined when builtin manager used (#3710)
* Fix for contentManager undefined for builtin manager

* Clean up code some more
2019-01-11 10:36:36 -08:00
Anthony Dresser
71c1ed6c49 Add state for column sizing (#3683)
* add state for column sizing

* work properly with auto size columns
2019-01-11 10:25:57 -08:00
AlexFsmn
bfb68254a4 Added context menu for DBs in explorer view to backup & restore db. (#2277)
* Added context menu for DBs in explorer view to backup & restore db.
Fixed bug where progress bar didn't complete on backup/restore menuclick
#2084

* Fix merge conflicts
2019-01-11 10:00:16 -08:00
Anthony Dresser
18f7662209 Duplicate Result sets (#3620)
* remove debouncing and echoing to fix rendering bug

* fix access of internal member

* fix issue with using splice rather than slice

* fix compile issues
2019-01-10 13:44:14 -08:00
Karl Burtram
a0d84f383c Generate temp files as not dirty (#3698)
* Generate temp files as not dirty

* Remove whitespace
2019-01-10 12:51:41 -08:00
Karl Burtram
1f447ae681 Add Idera extension to recommendation list (#3709) 2019-01-10 11:54:39 -08:00
Kevin Cunnane
8bd6691331 Added v3 Notebook format support (#3697)
* Added v3 format support
2019-01-09 17:00:56 -08:00
Chris LaFreniere
42afcf9322 Integrate first SQL Notebooks Bits into Master (#3679)
* First crack tsql notebook (no output rendered yet)

* getting messages back

* intellisense working first cell, no connection errors

* sql notebook cell output functioning

* Latest SQL noteobook changes

* Undo change to launch.json

* Plumbing providers through

* Kernels shown from multiple providers, can switch between them. No mementos yet

* Ensure we have a feature flag for SQL notebooks, ensure existing functionality still works

* Fix tslint duplicate imports issue

* Addressing PR comments

* second round of PR feedback to cleanup notebook service manager code

* merge latest from master
2019-01-09 14:58:57 -08:00
David Shiflet
3d3694bb8d Add --command command line argument (#3690) 2019-01-09 17:36:01 -05:00
Anthony Dresser
589b913960 Readd Top Operations (#3628)
* workin on top operations

* added top operations, changed default sorter to handle number string better
2019-01-09 13:52:38 -08:00
kisantia
7ba4f42494 Moving onValidityChanged listener to showPage() so that it gets added to pages that are added to the wizard after the initial start up (#3691) 2019-01-09 13:19:24 -08:00
Chris LaFreniere
c96118d2b5 Fix activeCell nullref issue (#3689) 2019-01-09 11:45:05 -08:00
Karl Burtram
0285d8cd38 Update readme for January release (#3595)
* Update readme for December release

* Fix spelling

* Update release date to 12/13

* added release note items and fixed a small misspell

* Update release date to Dec 18

* Update release date
2019-01-09 10:12:14 -08:00
Matt Irvine
ee87604a4d Save grid selection/vertical scroll when switching tabs (#3682) 2019-01-08 15:51:57 -08:00
Kevin Cunnane
2235ebaf20 Fix #3680 Notebooks: outputs with string arrays rendered incorrectly (#3681)
* Refactor JSON and format files to model and fix tabs -> spaces issues

This is in prep for some work to reuse these code files inside the model,
so pushing as its own PR to keep the next piece of work clean.

* Fix #3680 Notebooks: outputs with string arrays rendered incorrectly
- Add support for processing v4 format files loaded from disk
- Prep support for v3 notebooks by adding placeholder code for that

* Fix failing tests and add specific one for this bug

* Remove references to v5
2019-01-08 15:24:16 -08:00
Anthony Dresser
954d0d954f Auto Column Sizing (#2778)
* add auto column sizing

* add break for performance

* update with new library
2019-01-08 13:05:53 -08:00
Kevin Cunnane
e31747d087 Refactor JSON and format files to model and fix tabs -> spaces issues (#3675)
This is in prep for some work to reuse these code files inside the model,
so pushing as its own PR to keep the next piece of work clean.
2019-01-07 14:02:12 -08:00
Anthony Dresser
fc581253a4 Fix gap with result streaming (#3629)
* handle updating item sizing when being updated

* change back scrolling delay

* remove unused code
2019-01-07 12:58:00 -08:00
Chris LaFreniere
47c4609f23 Ensure we call Dispose() on NotebookModel when notebook component is destroyed (#3667) 2019-01-04 12:00:42 -08:00
Chris LaFreniere
2d52bc2a49 Notebooks: Fix Selection/Focus when New Cells Added (#3649)
* Improvemnents to Active Cell

* Fix minor spacing issue

* fix editor focus order

* Fix for add cell above/below

* cleanup logic to have activeCell logic all reside in notebook model
2019-01-02 15:20:05 -08:00
kisantia
5367101330 Fix database not getting set correctly in DacFx wizard deploy scenario (#3641)
* fix db not getting set correctly for deploy scenario if coming from import page

* removed space so that comments go directly after //
2018-12-19 18:45:40 -05:00
Matt Irvine
db145b4999 Run TSLint in Azure Pipelines (#3639) 2018-12-19 12:17:23 -08:00
Matt Irvine
7f950ddb80 Update edit data for result set streaming changes (#3634) 2018-12-18 11:07:26 -08:00
Vincent Feng
50e2251e74 Feature/extensible azure resource explorer (#3504)
Extensible Azure Resource Explorer
2018-12-18 15:44:08 +08:00
Karl Burtram
33d5455b6f Update Azure Data Studio to 1.4.1 2018-12-15 14:47:47 -08:00
khoiph1
ac018500cd Loc Update (#3548) 2018-12-13 09:53:22 -08:00
Aditya Bist
3e661db283 removed potentially PII (#3619) 2018-12-12 13:55:22 -08:00
Anthony Dresser
18fb78b3ec Account for different situations for stream setting (#3615)
* add cases for different situation

* default streaming setting false
2018-12-12 12:03:59 -08:00
Matt Irvine
58ff13d399 Fix some TSLint issues (#3605) 2018-12-11 16:27:32 -08:00
Anthony Dresser
0ac0175bb1 update table size on result set update (#3604) 2018-12-11 15:05:28 -08:00
Anthony Dresser
f39007cd2d wrong variable name (#3603) 2018-12-11 15:05:11 -08:00
Yurong He
2349aa4df8 Fixed #3596 by change URI.parse to URI.file to get the path (#3597) 2018-12-11 11:23:29 -08:00
Yurong He
a93a173183 The CSS class is overwritten by the previous change. Add it back (#3583) 2018-12-10 17:36:58 -08:00
Anthony Dresser
42e55dd2dd Result Streaming settings (#3537)
* add setting control for result streaming

* change default result streaming to true
2018-12-10 17:36:35 -08:00
Raj
ca3146d38f Filetype while prmopting for save #3552 (#3575) 2018-12-10 17:02:16 -08:00
Alan Ren
7f6cd514a5 Alanren/profiler search (#3525)
* further improve the search experience for profiler

* change the default value for parameter
2018-12-10 16:43:58 -08:00
Aditya Bist
88e24e92b5 Agent: features and suggestions (#3512)
* removed row highlighting overlap with scrollbars

* fixed more styling suggestions

* made async calls parallel and improved UI

* cleared style
2018-12-10 16:36:41 -08:00
Anthony Dresser
8b447e361f change cancelation in the async data loader to correctly cancel requests (#3516) 2018-12-10 16:29:02 -08:00
Chris LaFreniere
a92dd2d4e4 Fix for PySpark3 not being selected by default (#3554) 2018-12-10 16:26:57 -08:00
Karl Burtram
852ec44567 Fix DataTier wizard null ref looking up provider with no active connection (#3528) 2018-12-10 16:26:06 -08:00
Raj
b6e32cdeb4 Rename notebook editor (fixes #3521) (#3536)
* Rename notebook editor #3521

* Review comments #3521
2018-12-10 13:26:29 -08:00
Alan Yu
4bd264d9be Add feature request template (#3487)
* Add feature request template

Github recently added feature for issue templates to also automatically assign a label. By setting this template, we can have some guidance for users who want to ask a feature request instead of an issue.

We can also have this be the default within the product when a user clicks "request a missing feature" on the smiley face button

* update label
2018-12-10 11:40:39 -08:00
Karl Burtram
4a4b8574d0 Update Azure Data Studio to 1.3.8 2018-12-10 11:30:20 -08:00
Yurong He
ded073edd9 Added clear output to ToggleMoreAction and added it to markdown preview (#3535)
* Added toggleMoreActions to Markdown Preview only.
When it is in editor mode, only editor display ToggleMoreActions.

* Added clear output back
2018-12-10 11:17:35 -08:00
Alan Yu
568f95e7a3 Update SQL Server Import readme extension (#3519)
Added DacFx wizard text
2018-12-10 10:42:13 -08:00
Chris LaFreniere
5adcabc8de Add back Notebook Completion List IntelliSense (#3520)
* Static notebook intellisense working

* Intellisense dynamic cells working

* Remove launch.json erroneous change

* PR comments #1

* PR feedback to change a minor condition
2018-12-07 18:04:32 -08:00
Kevin Cunnane
e3bce7172c Handle delayed Notebook provider registration (#3526)
* Handle delayed Notebook provider registration
- Fixes #3197 Notebooks: builtin provider always used on reopen with notebook file visible
- Fixes #3414 Can't refresh kernel after connect to big data cluster

There are 3 parts to this fix:
- If no notebook provider other than the default is installed, we warn users and prompt to install the SQL2019 extension
- We wait on the extension host registration to complete before determining which provider to use
- We know that the extension registration of the provider instance will be after package.json is read, so if we wait after registration for 10 seconds to give this a chance to happen before returning a provider to the front end

* Remove launch.json change that was added accidentally

* Fix timeout not being the expected value

* Removed console log left in during debugging

* Remove unnecessary whitespace

* Fix unit test failure

* Name the registration better, and remove outdated comments
2018-12-07 17:56:21 -08:00
Raj
96fb618390 Notebook saves are broken #3432 (#3478)
* Notebook saves are broken #3432

* Misc change

* Save notebook uri to This

* Untitled notebook save including review comments #3432

* Cleanup

* Misc changes
2018-12-07 16:27:23 -08:00
Alan Yu
2d4fdcb661 Updated SQL Server Import extension readme
Added Data-Tier Application Wizard
2018-12-07 15:26:48 -08:00
Yurong He
7a84cff5b4 Fixed #3508 by removing the fixed height of toolbar (#3518) 2018-12-07 15:16:53 -08:00
Yurong He
2af627b704 Fixed #3497 (#3517) 2018-12-07 14:16:00 -08:00
Alan Ren
77fdf18686 improve the visual effect for selected card (#3509)
* improve the visual effect for selected card

* remove shadow for unselected card as per Smitha's suggestion

* fix the issue of status icon not changing when new theme selected
2018-12-07 14:13:52 -08:00
Yurong He
944a77fe42 Fixed #3287 adding loading-spinner to markdown cell (#3505) 2018-12-07 13:07:00 -08:00
Chris LaFreniere
049678b32e Change notebook width to 100 (#3423) 2018-12-07 12:22:19 -08:00
Kevin Cunnane
3325e4d854 Fix #3422 Notebooks opened from within ADS should be Trusted by default. (#3498) 2018-12-07 12:14:57 -08:00
Kevin Cunnane
1e90e88d4b Fix #3481 Notebook: Markdown coloring appears incorrect (#3499)
- Set markdown as language for markdown cell
- Fix issue where after loading language from cell metadata, always override it. This made the "language" feature irrelevant in the cell.
- Fixed tests with new behavior (assumption: cell-level language overrides notebook-level definition) and added new test to cover this too
2018-12-07 12:14:43 -08:00
Kevin Cunnane
8aeb33c98c Fix #3470 Notebook: Switching between Servers and File Explorer opens a duplicate notebook (#3500)
- We missed implementing matches functionality. This is required in order to skip reopening of the notebook
2018-12-07 12:14:26 -08:00
Yurong He
3b08721835 Fixed #3415 add padding-left/right 8px (#3462)
* Fixed #3415 add padding-left/right 8px

* We will keep CSS consistent for review and editing mode. So removed codes not used.
2018-12-07 09:02:20 -08:00
Karl Burtram
5a30878599 Bump agent, import, profiler extension versions 2018-12-06 18:11:11 -08:00
Karl Burtram
c8a8935db0 Bump SQL Tools Service to 1.5.0-alpha.63 2018-12-06 17:58:49 -08:00
Aditya Bist
ec196f57bb Agent feature - ability to start at step (#3483)
* added ability to start at certain step

* fixed width for scrollbar

* localized string
2018-12-06 17:43:26 -08:00
Matt Irvine
f7809ec3a7 Add ability to select AAD tenant when connecting (#3475) 2018-12-06 10:46:27 -08:00
Kevin Cunnane
71d3ec3616 Fix #3420 Analyze in notebook doesn't include text (#3482)
- Add edit API that can be used in the extension
- Separated document and editor classes out since this is the point those get big. I can refactor back in if needed to ease code review
- Based this off text editing APIs but tweaked for the fact this is a cell/array based set of edits
2018-12-06 10:33:32 -08:00
Yurong He
4a7cf8d870 Disable key binding for this release. (#3466) 2018-12-06 10:04:02 -08:00
Alan Ren
4bf8836c0a profiler extension bug fixes (#3490) 2018-12-06 09:58:33 -08:00
ranasaria
1ca36ee29c bumping to version 62 to toolsservice to pick up resutlstreaming backend fixes 2018-12-05 22:39:23 -08:00
Yurong He
3446ff88cf Fixed #3472 check SaveKernelInfo not null (#3473) 2018-12-05 14:21:14 -08:00
Chris LaFreniere
de5a91a13f Allow for Notebook Cell Unselection (#3460)
* Allow for cell unselection

* PR Feedback: use event.stopPropagation() when multiple events can fire

* Ensure markdown goes into Preview mode when  cell not selected
2018-12-05 13:22:56 -08:00
Karl Burtram
814cd73019 Dispose of query grid memory on tab switch (#3458) 2018-12-05 12:17:16 -08:00
Aditya Bist
c21611661b Agent feature usage metrics (#3346)
* agent feature usage metrics

* generalized feature telemetry via dialogs

* renamed eventName property to dialogName

* made dialogName an optional field
2018-12-05 11:01:46 -08:00
Yurong He
8f817ce689 Fixed Notebooks regression: invalid kernels aren't handled #3404 (#3447)
* Fixed the not supported kernel in saved notebook

* Resolve PR comment.
2018-12-05 10:54:37 -08:00
Aditya Bist
971b5111e7 Agent: Scrolling (#3427)
* change height calcs to proper tree height

* agent fixes

* fit step tree

* fix rendering issue

* fixed spinning wheel position

* added horizontal scrolling to steps tree

* removed typo
2018-12-05 10:16:39 -08:00
Chris LaFreniere
07069a64ae Tweak notebook cell box shadow/border-width (#3454) 2018-12-04 19:21:55 -08:00
Karl Burtram
6acea51f12 Bump SQL Tools to 1.5.0-alpha.61 2018-12-04 17:51:51 -08:00
Raj
7aa2dab307 Defaulting 'attach to' to localhost when no extension is installed #3419 (#3426) 2018-12-04 13:45:10 -08:00
Raj
3091be8f67 Set active editor with dirty value before and after save #3411 (#3417) 2018-12-04 13:21:13 -08:00
Kevin Cunnane
487531cc52 Fix build break due to rename of APIs prior to checkin of unit tests (#3418) 2018-12-04 10:42:32 -08:00
Yurong He
58bfcb4273 Fixed can't read indexof exception when no big data cluster connection and switch to PySpark3 (#3413) 2018-12-04 10:36:34 -08:00
Kevin Cunnane
8d8be27f22 Add basic notebook model tests (#3396)
- Ported from the extension
- Only adding tests that related to the internally implemented functionality, not to anything provider-specific.
2018-12-04 10:01:10 -08:00
Yurong He
27a978cba5 Fixed #3387 (#3401) 2018-12-04 09:41:31 -08:00
Chris LaFreniere
71b4e6afa4 Fix Notebook Code Cell Height when Lines Wrap (#3403)
* Fix Notebook Code Cell Height when Lines Wrap

* Addressing PR comments
2018-12-03 20:55:46 -08:00
Chris LaFreniere
e1f3b19c0c Ensure Selectboxes in Notebook Toolbar have Minimum Size (#3393)
* Setting minimum width on selectboxes in notebook toolbar

* Fix spacing
2018-12-03 20:31:08 -08:00
Kevin Cunnane
649c2aa5a6 Fix preview handling so the untitled notebooks are pinned by default (#3405) 2018-12-03 20:26:42 -08:00
Kevin Cunnane
cac8cc99e1 Notebook extensibility: Move New Notebook and configuration to an extension (#3382)
initial support for Notebook extensibility. Fixes #3148 , Fixes #3382.

## Design notes
The extensibility patterns are modeled after the VSCode Document and Editor APIs but need to be different since core editor concepts are different - for example Notebooks have cells, and cells have contents rather than editors which have text lines.

Most importantly, a lot of the code is based on the MainThreadDocumentsAndEditors class, with some related classes (the MainThreadDocuments, and MainThreadEditors) brought in too. Given our current limitations I felt moving to add 3 full sets of extension host API classes was overkill so am currently using one. Will see if we need to change this in the future based on what we add in the additional APIs

## Limitations
The current implementation is limited to visible editors, rather than all documents in the workspace. We are not following the `openDocument` -> `showDocument` pattern, but instead just supporting `showDocument` directly.

## Changes in this PR
- Renamed existing APIs to make clear that they were about notebook contents, not about notebook behavior
- Added new APIs for querying notebook documents and editors 
- Added new API for opening a notebook
- Moved `New Notebook` command to an extension, and added an `Open Notebook` command too
- Moved notebook feature flag to the extension

## Not covered in this PR
- Need to actually implement support for defining the provider and connection IDs for a notebook. this will be important to support New Notebook from a big data connection in Object Explorer
- Need to add APIs for adding cells, to support 
- Need to implement the metadata for getting full notebook contents. I've only implemented to key APIs needed to make this all work.
2018-12-03 18:50:44 -08:00
Raj
cb162b16f2 No kernel is shown when open a new notebook from command palette (#3374)
Fixes #3271. Ensure a provider is defined when opening through command palette
2018-12-03 18:12:25 -08:00
Karl Burtram
86e54ce145 Bump Azure Data Studio to 1.3.7 2018-12-03 17:43:12 -08:00
333 changed files with 54163 additions and 8450 deletions

View File

@@ -1,6 +1,10 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
<!-- Please search existing issues to avoid creating duplicates. -->

View 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.

24
.vscode/launch.json vendored
View File

@@ -92,6 +92,30 @@
"webRoot": "${workspaceFolder}",
"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",
"request": "launch",

View File

@@ -1,5 +1,27 @@
# 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

View File

@@ -9,12 +9,13 @@ Azure Data Studio is a data management tool that enables you to work with SQL Se
Platform | Link
-- | --
Windows Setup Installer | https://go.microsoft.com/fwlink/?linkid=2038320
Windows ZIP | https://go.microsoft.com/fwlink/?linkid=2038323
macOS ZIP | https://go.microsoft.com/fwlink/?linkid=2038327
Linux TAR.GZ | https://go.microsoft.com/fwlink/?linkid=2038332
Linux RPM | https://go.microsoft.com/fwlink/?linkid=2038401
Linux DEB | https://go.microsoft.com/fwlink/?linkid=2038405
Windows User Installer | https://go.microsoft.com/fwlink/?linkid=2049972
Windows System Installer | https://go.microsoft.com/fwlink/?linkid=2049975
Windows ZIP | https://go.microsoft.com/fwlink/?linkid=2050146
macOS ZIP | https://go.microsoft.com/fwlink/?linkid=2049981
Linux TAR.GZ | https://go.microsoft.com/fwlink/?linkid=2049986
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.
@@ -62,6 +63,10 @@ The [Microsoft Enterprise and Developer Privacy Statement](https://privacy.micro
## 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`
* 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`

View File

@@ -34,5 +34,9 @@ steps:
- task: PublishTestResults@2
inputs:
testResultsFiles: '**/test-results.xml'
condition: succeededOrFailed()
testResultsFiles: '**/test-results.xml'
condition: succeededOrFailed()
- script: |
yarn run tslint
displayName: 'Run TSLint'

View File

@@ -23,4 +23,8 @@ steps:
- task: PublishTestResults@2
inputs:
testResultsFiles: 'test-results.xml'
condition: succeededOrFailed()
condition: succeededOrFailed()
- script: |
yarn run tslint
displayName: 'Run TSLint'

View File

@@ -2,7 +2,7 @@
"name": "agent",
"displayName": "SQL Server Agent",
"description": "Manage and troubleshoot SQL Server Agent jobs",
"version": "0.35.1",
"version": "0.35.2",
"publisher": "Microsoft",
"preview": true,
"license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/master/LICENSE.txt",

View File

@@ -69,7 +69,7 @@ export class AlertData implements IAgentDialogData {
this.eventDescriptionKeyword = alertInfo.eventDescriptionKeyword;
this.eventSource = alertInfo.eventSource;
this.hasNotification = alertInfo.hasNotification;
this.includeEventDescription = alertInfo.includeEventDescription.toString();
this.includeEventDescription = alertInfo.includeEventDescription ? alertInfo.includeEventDescription.toString() : null;
this.isEnabled = alertInfo.isEnabled;
this.jobId = alertInfo.jobId;
this.lastOccurrenceDate = alertInfo.lastOccurrenceDate;
@@ -82,7 +82,7 @@ export class AlertData implements IAgentDialogData {
this.databaseName = alertInfo.databaseName;
this.countResetDate = alertInfo.countResetDate;
this.categoryName = alertInfo.categoryName;
this.alertType = alertInfo.alertType.toString();
this.alertType = alertInfo.alertType ? alertInfo.alertType.toString() : null;
this.wmiEventNamespace = alertInfo.wmiEventNamespace;
this.wmiEventQuery = alertInfo.wmiEventQuery;
}

View File

@@ -45,6 +45,7 @@ export class JobData implements IAgentDialogData {
public jobSchedules: sqlops.AgentJobScheduleInfo[];
public alerts: sqlops.AgentAlertInfo[];
public jobId: string;
public startStepId: number;
constructor(
ownerUri: string,
@@ -60,10 +61,11 @@ export class JobData implements IAgentDialogData {
this.category = jobInfo.category;
this.description = jobInfo.description;
this.enabled = jobInfo.enabled;
this.jobSteps = jobInfo.JobSteps;
this.jobSchedules = jobInfo.JobSchedules;
this.alerts = jobInfo.Alerts;
this.jobSteps = jobInfo.jobSteps;
this.jobSchedules = jobInfo.jobSchedules;
this.alerts = jobInfo.alerts;
this.jobId = jobInfo.jobId;
this.startStepId = jobInfo.startStepId;
}
}
@@ -141,17 +143,17 @@ export class JobData implements IAgentDialogData {
name: this.name,
owner: this.owner,
description: this.description,
EmailLevel: this.emailLevel,
PageLevel: this.pageLevel,
EventLogLevel: this.eventLogLevel,
DeleteLevel: this.deleteLevel,
OperatorToEmail: this.operatorToEmail,
OperatorToPage: this.operatorToPage,
emailLevel: this.emailLevel,
pageLevel: this.pageLevel,
eventLogLevel: this.eventLogLevel,
deleteLevel: this.deleteLevel,
operatorToEmail: this.operatorToEmail,
operatorToPage: this.operatorToPage,
enabled: this.enabled,
category: this.category,
Alerts: this.alerts,
JobSchedules: this.jobSchedules,
JobSteps: this.jobSteps,
alerts: this.alerts,
jobSchedules: this.jobSchedules,
jobSteps: this.jobSteps,
// The properties below are not collected from UI
// We could consider using a seperate class for create job request
//
@@ -166,7 +168,8 @@ export class JobData implements IAgentDialogData {
categoryType: 1, // LocalJob, hard-coding the value, corresponds to the target tab in SSMS
lastRun: '',
nextRun: '',
jobId: this.jobId
jobId: this.jobId,
startStepId: this.startStepId
};
}
}

View File

@@ -20,6 +20,9 @@ export abstract class AgentDialog<T extends IAgentDialogData> {
public readonly onSuccess: vscode.Event<T> = this._onSuccess.event;
public dialog: sqlops.window.modelviewdialog.Dialog;
// Dialog Name for Telemetry
public dialogName: 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);
public async openDialog() {
this.dialog = sqlops.window.modelviewdialog.createDialog(this.title);
public async openDialog(dialogName?: string) {
let event = dialogName ? dialogName : null;
this.dialog = sqlops.window.modelviewdialog.createDialog(this.title, event);
await this.model.initialize();

View File

@@ -116,6 +116,10 @@ export class AlertDialog extends AgentDialog<AlertData> {
private static readonly DelayMinutesTextBoxLabel: string = localize('alertDialog.DelayMinutes', 'Delay Minutes');
private static readonly DelaySecondsTextBoxLabel: string = localize('alertDialog.DelaySeconds', 'Delay Seconds');
// Event Name strings
private readonly NewAlertDialog = 'NewAlertDialogOpen';
private readonly EditAlertDialog = 'EditAlertDialogOpened';
// UI Components
private generalTab: sqlops.window.modelviewdialog.DialogTab;
private responseTab: sqlops.window.modelviewdialog.DialogTab;
@@ -149,6 +153,7 @@ export class AlertDialog extends AgentDialog<AlertData> {
private delayMinutesTextBox: sqlops.InputBoxComponent;
private delaySecondsTextBox: sqlops.InputBoxComponent;
private isEdit: boolean = false;
private databases: string[];
private jobModel: JobData;
public jobId: string;
@@ -166,6 +171,8 @@ export class AlertDialog extends AgentDialog<AlertData> {
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) {

View File

@@ -42,11 +42,12 @@ export class JobDialog extends AgentDialog<JobData> {
private readonly StepsTable_TypeColumnString: string = localize('jobDialog.type', 'Type');
private readonly StepsTable_SuccessColumnString: string = localize('jobDialog.onSuccess', 'On Success');
private readonly StepsTable_FailureColumnString: string = localize('jobDialog.onFailure', 'On Failure');
private readonly NewStepButtonString: string = localize('jobDialog.new', 'New...');
private readonly EditStepButtonString: string = localize('jobDialog.edit', 'Edit');
private readonly DeleteStepButtonString: string = localize('jobDialog.delete', 'Delete');
private readonly NewStepButtonString: string = localize('jobDialog.new', 'New Step');
private readonly EditStepButtonString: string = localize('jobDialog.edit', 'Edit Step');
private readonly DeleteStepButtonString: string = localize('jobDialog.delete', 'Delete Step');
private readonly MoveStepUpButtonString: string = localize('jobDialog.moveUp', '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
private readonly NotificationsTabTopLabelString: string = localize('jobDialog.notificationsTabTop', 'Actions to perform when the job completes');
@@ -67,6 +68,10 @@ export class JobDialog extends AgentDialog<JobData> {
private readonly AlertEnabledLabelString: string = localize('jobDialog.alertEnabledLabel', 'Enabled');
private readonly AlertTypeLabelString: string = localize('jobDialog.alertTypeLabel', 'Type');
// Event Name strings
private readonly NewJobDialogEvent: string = 'NewJobDialogOpened';
private readonly EditJobDialogEvent: string = 'EditJobDialogOpened';
// UI Components
private generalTab: sqlops.window.modelviewdialog.DialogTab;
private stepsTab: sqlops.window.modelviewdialog.DialogTab;
@@ -101,6 +106,7 @@ export class JobDialog extends AgentDialog<JobData> {
private eventLogConditionDropdown: sqlops.DropDownComponent;
private deleteJobCheckBox: sqlops.CheckBoxComponent;
private deleteJobConditionDropdown: sqlops.DropDownComponent;
private startStepDropdown: sqlops.DropDownComponent;
// Schedule tab controls
private schedulesTable: sqlops.TableComponent;
@@ -115,6 +121,7 @@ export class JobDialog extends AgentDialog<JobData> {
private steps: sqlops.AgentJobStepInfo[];
private schedules: sqlops.AgentJobScheduleInfo[];
private alerts: sqlops.AgentAlertInfo[] = [];
private startStepDropdownValues: sqlops.CategoryValue[] = [];
constructor(ownerUri: string, jobInfo: sqlops.AgentJobInfo = undefined) {
super(
@@ -125,6 +132,7 @@ export class JobDialog extends AgentDialog<JobData> {
this.schedules = this.model.jobSchedules ? this.model.jobSchedules : [];
this.alerts = this.model.alerts ? this.model.alerts : [];
this.isEdit = jobInfo ? true : false;
this.dialogName = this.isEdit ? this.EditJobDialogEvent : this.NewJobDialogEvent;
}
protected async initializeDialog() {
@@ -218,13 +226,20 @@ export class JobDialog extends AgentDialog<JobData> {
this.StepsTable_FailureColumnString
],
data: data,
height: 750
height: 650
}).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()
.withProperties({
label: this.MoveStepUpButtonString,
width: 80
width: 120
}).component();
this.moveStepDownButton = view.modelBuilder.button()
@@ -238,7 +253,7 @@ export class JobDialog extends AgentDialog<JobData> {
this.newStepButton = view.modelBuilder.button().withProperties({
label: this.NewStepButtonString,
width: 80
width: 140
}).component();
let stepDialog = new JobStepDialog(this.model.ownerUri, '' , this.model, null, true);
@@ -246,6 +261,11 @@ export class JobDialog extends AgentDialog<JobData> {
let stepInfo = JobStepData.convertToAgentJobStepInfo(step);
this.steps.push(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;
});
this.newStepButton.onDidClick((e)=>{
if (this.nameTextBox.value && this.nameTextBox.value.length > 0) {
@@ -258,12 +278,12 @@ export class JobDialog extends AgentDialog<JobData> {
this.editStepButton = view.modelBuilder.button().withProperties({
label: this.EditStepButtonString,
width: 80
width: 140
}).component();
this.deleteStepButton = view.modelBuilder.button().withProperties({
label: this.DeleteStepButtonString,
width: 80
width: 140
}).component();
this.stepsTable.enabled = false;
@@ -271,41 +291,31 @@ export class JobDialog extends AgentDialog<JobData> {
this.deleteStepButton.enabled = false;
this.moveStepUpButton.onDidClick(() => {
if (this.stepsTable.selectedRows.length === 1) {
let rowNumber = this.stepsTable.selectedRows[0];
// if it's not the first step
if (rowNumber !== 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;
}
}
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.moveStepDownButton.onDidClick(() => {
if (this.stepsTable.selectedRows.length === 1) {
let rowNumber = this.stepsTable.selectedRows[0];
// if it's not the last step
if (this.steps.length !== rowNumber + 1) {
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;
}
}
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.editStepButton.onDidClick(() => {
@@ -321,6 +331,12 @@ export class JobDialog extends AgentDialog<JobData> {
}
}
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();
}
@@ -337,30 +353,52 @@ export class JobDialog extends AgentDialog<JobData> {
delete steps[rowNumber];
let data = this.convertStepsToData(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(() => {
this.stepsTable.onRowSelected((row) => {
// only let edit or delete steps if there's
// one step selection
if (this.stepsTable.selectedRows.length === 1) {
this.moveStepUpButton.enabled = true;
this.moveStepDownButton.enabled = true;
let rowNumber = this.stepsTable.selectedRows[0];
// 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.editStepButton.enabled = true;
}
});
let formModel = view.modelBuilder.formContainer()
.withFormItems([{
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([
{
component: this.stepsTable,
title: this.JobStepsTopLabelString,
actions: [this.moveStepUpButton, this.moveStepDownButton, this.newStepButton, this.editStepButton, this.deleteStepButton]
}]).withLayout({ width: '100%' }).component();
title: this.JobStepsTopLabelString
},
{
component: stepMoveContainer,
title: this.StartStepDropdownString
},
{
component: stepsDialogContainer,
title: ''
}
]).withLayout({ width: '100%' }).component();
await view.initializeModel(formModel);
this.setConditionDropdownSelectedValue(this.startStepDropdown, this.model.startStepId);
});
}
@@ -623,6 +661,7 @@ export class JobDialog extends AgentDialog<JobData> {
this.model.pageLevel = this.getActualConditionValue(this.pagerCheckBox, this.pagerConditionDropdown);
this.model.eventLogLevel = this.getActualConditionValue(this.eventLogCheckBox, this.eventLogConditionDropdown);
this.model.deleteLevel = this.getActualConditionValue(this.deleteJobCheckBox, this.deleteJobConditionDropdown);
this.model.startStepId = +this.getDropdownValue(this.startStepDropdown);
if (!this.model.jobSteps) {
this.model.jobSteps = [];
}

View File

@@ -67,6 +67,9 @@ export class JobStepDialog extends AgentDialog<JobStepData> {
private readonly QuitJobReportingSuccess: string = localize('jobStepDialog.quitJobSuccess', 'Quit the job reporting success');
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
// Dialogs
@@ -131,6 +134,7 @@ export class JobStepDialog extends AgentDialog<JobStepData> {
this.jobModel = jobModel;
this.jobName = this.jobName ? this.jobName : this.jobModel.name;
this.server = server;
this.dialogName = this.isEdit ? this.EditStepDialog : this.NewStepDialog;
}
private initializeUIComponents() {

View File

@@ -43,6 +43,10 @@ export class OperatorDialog extends AgentDialog<OperatorData> {
private static readonly AlertEmailColumnLabel: string = localize('createOperator.AlertEmailColumnLabel', 'E-mail');
private static readonly AlertPagerColumnLabel: string = localize('createOperator.AlertPagerColumnLabel', 'Pager');
// Event strings
private readonly NewOperatorDialog = 'NewOperatorDialogOpened';
private readonly EditOperatorDialog = 'EditOperatorDialogOpened';
// UI Components
private generalTab: sqlops.window.modelviewdialog.DialogTab;
private notificationsTab: sqlops.window.modelviewdialog.DialogTab;
@@ -68,12 +72,15 @@ export class OperatorDialog extends AgentDialog<OperatorData> {
// Notification tab controls
private alertsTable: sqlops.TableComponent;
private isEdit: boolean = false;
constructor(ownerUri: string, operatorInfo: sqlops.AgentOperatorInfo = undefined) {
super(
ownerUri,
new OperatorData(ownerUri, operatorInfo),
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) {

View File

@@ -36,6 +36,9 @@ export class ProxyDialog extends AgentDialog<ProxyData> {
private static readonly PowerShellLabel: string = localize('createProxy.PowerShell', 'PowerShell');
private static readonly SubSystemHeadingLabel: string = localize('createProxy.subSystemHeading', 'Active to the following subsytems');
private readonly NewProxyDialog = 'NewProxyDialogOpened';
private readonly EditProxyDialog = 'EditProxyDialogOpened';
// UI Components
private generalTab: sqlops.window.modelviewdialog.DialogTab;
@@ -56,6 +59,7 @@ export class ProxyDialog extends AgentDialog<ProxyData> {
private powershellCheckBox: sqlops.CheckBoxComponent;
private credentials: sqlops.CredentialInfo[];
private isEdit: boolean = false;
constructor(ownerUri: string, proxyInfo: sqlops.AgentProxyInfo = undefined, credentials: sqlops.CredentialInfo[]) {
super(
@@ -63,6 +67,8 @@ export class ProxyDialog extends AgentDialog<ProxyData> {
new ProxyData(ownerUri, proxyInfo),
proxyInfo ? ProxyDialog.EditDialogTitle : ProxyDialog.CreateDialogTitle);
this.credentials = credentials;
this.isEdit = proxyInfo ? true : false;
this.dialogName = this.isEdit ? this.EditProxyDialog : this.NewProxyDialog;
}
protected async initializeDialog(dialog: sqlops.window.modelviewdialog.Dialog) {

View File

@@ -40,13 +40,13 @@ export class MainController {
public activate(): void {
vscode.commands.registerCommand('agent.openJobDialog', (ownerUri: string, jobInfo: sqlops.AgentJobInfo) => {
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.openDialog();
dialog.dialogName ? dialog.openDialog(dialog.dialogName) : dialog.openDialog();
});
});
vscode.commands.registerCommand('agent.openPickScheduleDialog', (ownerUri: string, jobName: string) => {
@@ -57,17 +57,16 @@ export class MainController {
AgentUtils.getAgentService().then((agentService) => {
let jobData: JobData = new JobData(ownerUri, jobInfo, agentService);
let dialog = new AlertDialog(ownerUri, jobData, alertInfo, false);
dialog.openDialog();
dialog.dialogName ? dialog.openDialog(dialog.dialogName) : dialog.openDialog();
});
});
vscode.commands.registerCommand('agent.openOperatorDialog', (ownerUri: string, operatorInfo: sqlops.AgentOperatorInfo) => {
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[]) => {
let dialog = new ProxyDialog(ownerUri, proxyInfo, credentials);
dialog.openDialog();
MainController.showNotYetImplemented();
dialog.dialogName ? dialog.openDialog(dialog.dialogName) : dialog.openDialog();
});
}

View File

@@ -17,12 +17,12 @@
"configuration": [
{
"type": "object",
"title": "%azure.config.title%",
"title": "%azure.resource.config.title%",
"properties": {
"azureResource.resourceFilter": {
"azure.resource.config.filter": {
"type": "array",
"default": null,
"description": "%azure.resourceFilter.description%"
"description": "%azure.resource.config.filter.description%"
}
}
},
@@ -61,39 +61,47 @@
"category": "Azure Accounts"
},
{
"command": "azureresource.refreshall",
"title": "%azureresource.refreshall%",
"command": "azure.resource.refreshall",
"title": "%azure.resource.refreshall.title%",
"icon": {
"dark": "resources/dark/refresh_inverse.svg",
"light": "resources/light/refresh.svg"
}
},
{
"command": "azureresource.refresh",
"title": "%azureresource.refresh%",
"command": "azure.resource.refresh",
"title": "%azure.resource.refresh.title%",
"icon": {
"dark": "resources/dark/refresh_inverse.svg",
"light": "resources/light/refresh.svg"
}
},
{
"command": "azureresource.signin",
"title": "%azureresource.signin%"
"command": "azure.resource.signin",
"title": "%azure.resource.signin.title%"
},
{
"command": "azureresource.connectsqldb",
"title": "%azureresource.connectsqldb%",
"command": "azure.resource.selectsubscriptions",
"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": {
"dark": "resources/dark/connect_to_inverse.svg",
"light": "resources/light/connect_to.svg"
}
},
{
"command": "azureresource.selectsubscriptions",
"title": "%azureresource.selectsubscriptions%",
"command": "azure.resource.connectsqldb",
"title": "%azure.resource.connectsqldb.title%",
"icon": {
"dark": "resources/dark/filter_inverse.svg",
"light": "resources/light/filter.svg"
"dark": "resources/dark/connect_to_inverse.svg",
"light": "resources/light/connect_to.svg"
}
}
],
@@ -110,46 +118,47 @@
"azureResource": [
{
"id": "azureResourceExplorer",
"name": "%azure.resourceExplorer.title%"
"name": "%azure.resource.explorer.title%"
}
]
},
"menus": {
"view/title": [
{
"command": "azureresource.refreshall",
"command": "azure.resource.refreshall",
"when": "view == azureResourceExplorer",
"group": "navigation@1"
}
],
"view/item/context": [
{
"command": "azureresource.connectsqldb",
"when": "viewItem =~ /^azureResource\\.itemType\\.database(?:Server){0,1}$/",
"group": "1azureresource@1"
},
{
"command": "azureresource.connectsqldb",
"when": "viewItem =~ /^azureResource\\.itemType\\.database(?:Server){0,1}$/",
"command": "azure.resource.selectsubscriptions",
"when": "viewItem == azure.resource.itemType.account",
"group": "inline"
},
{
"command": "azureresource.selectsubscriptions",
"when": "viewItem == azureResource.itemType.account",
"command": "azure.resource.refresh",
"when": "viewItem =~ /^azure\\.resource\\.itemType\\.(?:account|subscription|databaseContainer|databaseServerContainer)$/",
"group": "inline"
},
{
"command": "azureresource.refresh",
"when": "viewItem =~ /^azureResource\\.itemType\\.(?:account|subscription|databaseContainer|databaseServerContainer)$/",
"command": "azure.resource.connectsqlserver",
"when": "viewItem == azure.resource.itemType.databaseServer",
"group": "inline"
},
{
"command": "azure.resource.connectsqldb",
"when": "viewItem == azure.resource.itemType.database",
"group": "inline"
}
]
}
},
"hasAzureResourceProviders": true
},
"dependencies": {
"request": "2.88.0",
"azure-arm-resource": "^7.0.0",
"azure-arm-sql": "^5.0.1",
"request": "2.88.0",
"vscode-nls": "^4.0.0"
},
"devDependencies": {
@@ -157,6 +166,7 @@
"@types/node": "^8.0.24",
"mocha": "^5.2.0",
"should": "^13.2.1",
"vscode": "^1.1.26",
"typemoq": "^2.1.0"
}
}
}

View File

@@ -1,16 +1,20 @@
{
"azure.displayName": "Azure (Core)",
"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.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",
"config.enablePublicCloudDescription": "Should Azure public cloud integration be enabled",
"config.enableUsGovCloudDescription": "Should US Government Azure cloud (Fairfax) integration be enabled",
"config.enableChinaCloudDescription": "Should Azure China integration be enabled",

View 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;
}
}

View File

@@ -6,35 +6,48 @@
'use strict';
import { window, QuickPickItem } from 'vscode';
import * as sqlops from 'sqlops';
import { generateGuid } from './utils';
import { ApiWrapper } from '../apiWrapper';
import { TreeNode } from '../treeNodes';
import { AzureResource } from 'sqlops';
import { TokenCredentials } from 'ms-rest';
import { AppContext } from '../appContext';
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 { AzureResourceDatabaseServerTreeNode } from './tree/databaseServerTreeNode';
import { AzureResourceDatabaseTreeNode } from './tree/databaseTreeNode';
import { AzureResourceAccountTreeNode } from './tree/accountTreeNode';
import { AzureResourceServicePool } from './servicePool';
import { AzureResourceSubscription } from './models';
import { IAzureResourceSubscriptionService, IAzureResourceSubscriptionFilterService } from '../azureResource/interfaces';
import { AzureResourceServiceNames } from './constants';
export function registerAzureResourceCommands(apiWrapper: ApiWrapper, tree: AzureResourceTreeProvider): void {
apiWrapper.registerCommand('azureresource.selectsubscriptions', async (node?: TreeNode) => {
export function registerAzureResourceCommands(appContext: AppContext, tree: AzureResourceTreeProvider): void {
appContext.apiWrapper.registerCommand('azure.resource.selectsubscriptions', async (node?: TreeNode) => {
if (!(node instanceof AzureResourceAccountTreeNode)) {
return;
}
const subscriptionService = appContext.getService<IAzureResourceSubscriptionService>(AzureResourceServiceNames.subscriptionService);
const subscriptionFilterService = appContext.getService<IAzureResourceSubscriptionFilterService>(AzureResourceServiceNames.subscriptionFilterService);
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();
if (!subscriptions || subscriptions.length === 0) {
const credentials = await servicePool.credentialService.getCredentials(accountNode.account, sqlops.AzureResource.ResourceManagement);
subscriptions = await servicePool.subscriptionService.getSubscriptions(accountNode.account, credentials);
for (const tenant of this.account.properties.tenants) {
const token = tokens[tenant.id].token;
const tokenType = tokens[tenant.id].tokenType;
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[] = [];
if (selectedSubscriptions.length > 0) {
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));
}
interface SubscriptionQuickPickItem extends QuickPickItem {
subscription: AzureResourceSubscription;
interface AzureResourceSubscriptionQuickPickItem extends QuickPickItem {
subscription: azureResource.AzureResourceSubscription;
}
const subscriptionItems: SubscriptionQuickPickItem[] = subscriptions.map((subscription) => {
const subscriptionQuickPickItems: AzureResourceSubscriptionQuickPickItem[] = subscriptions.map((subscription) => {
return {
label: subscription.name,
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 }));
if (pickedSubscriptionItems && pickedSubscriptionItems.length > 0) {
const selectedSubscriptionQuickPickItems = (await window.showQuickPick(subscriptionQuickPickItems, { canPickMany: true }));
if (selectedSubscriptionQuickPickItems && selectedSubscriptionQuickPickItems.length > 0) {
tree.refresh(node, false);
const pickedSubscriptions = pickedSubscriptionItems.map((subscriptionItem) => subscriptionItem.subscription);
await servicePool.subscriptionFilterService.saveSelectedSubscriptions(accountNode.account, pickedSubscriptions);
selectedSubscriptions = selectedSubscriptionQuickPickItems.map((subscriptionItem) => subscriptionItem.subscription);
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);
});
apiWrapper.registerCommand('azureresource.connectsqldb', async (node?: TreeNode) => {
let connectionProfile: sqlops.IConnectionProfile = {
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');
appContext.apiWrapper.registerCommand('azure.resource.signin', async (node?: TreeNode) => {
appContext.apiWrapper.executeCommand('sql.action.accounts.manageLinkedAccount');
});
}

View File

@@ -6,11 +6,19 @@
'use strict';
export enum AzureResourceItemType {
account = 'azureResource.itemType.account',
subscription = 'azureResource.itemType.subscription',
databaseContainer = 'azureResource.itemType.databaseContainer',
database = 'azureResource.itemType.database',
databaseServerContainer = 'azureResource.itemType.databaseServerContainer',
databaseServer = 'azureResource.itemType.databaseServer',
message = 'azureResource.itemType.message'
account = 'azure.resource.itemType.account',
subscription = 'azure.resource.itemType.subscription',
databaseContainer = 'azure.resource.itemType.databaseContainer',
database = 'azure.resource.itemType.database',
databaseServerContainer = 'azure.resource.itemType.databaseServerContainer',
databaseServer = 'azure.resource.itemType.databaseServer',
message = 'azure.resource.itemType.message'
}
export enum AzureResourceServiceNames {
cacheService = 'AzureResourceCacheService',
accountService = 'AzureResourceAccountService',
subscriptionService = 'AzureResourceSubscriptionService',
subscriptionFilterService = 'AzureResourceSubscriptionFilterService',
tenantService = 'AzureResourceTenantService'
}

View File

@@ -8,7 +8,7 @@
export class AzureResourceCredentialError extends Error {
constructor(
message: string,
public innerError: Error
public readonly innerError: Error
) {
super(message);
}

View File

@@ -6,49 +6,40 @@
'use strict';
import { ServiceClientCredentials } from 'ms-rest';
import * as sqlops from 'sqlops';
import { Account, DidChangeAccountsParams } from 'sqlops';
import { Event } from 'vscode';
import { AzureResourceSubscription, AzureResourceDatabaseServer, AzureResourceDatabase } from './models';
import { azureResource } from './azure-resource';
export interface IAzureResourceAccountService {
getAccounts(): Promise<sqlops.Account[]>;
getAccounts(): Promise<Account[]>;
readonly onDidChangeAccounts: Event<sqlops.DidChangeAccountsParams>;
}
export interface IAzureResourceCredentialService {
getCredentials(account: sqlops.Account, resource: sqlops.AzureResource): Promise<ServiceClientCredentials[]>;
readonly onDidChangeAccounts: Event<DidChangeAccountsParams>;
}
export interface IAzureResourceSubscriptionService {
getSubscriptions(account: sqlops.Account, credentials: ServiceClientCredentials[]): Promise<AzureResourceSubscription[]>;
getSubscriptions(account: Account, credential: ServiceClientCredentials): Promise<azureResource.AzureResourceSubscription[]>;
}
export interface IAzureResourceSubscriptionFilterService {
getSelectedSubscriptions(account: sqlops.Account): Promise<AzureResourceSubscription[]>;
getSelectedSubscriptions(account: Account): Promise<azureResource.AzureResourceSubscription[]>;
saveSelectedSubscriptions(account: sqlops.Account, selectedSubscriptions: AzureResourceSubscription[]): Promise<void>;
}
export interface IAzureResourceDatabaseServerService {
getDatabaseServers(subscription: AzureResourceSubscription, credentials: ServiceClientCredentials[]): Promise<AzureResourceDatabaseServer[]>;
}
export interface IAzureResourceDatabaseService {
getDatabases(subscription: AzureResourceSubscription, credentials: ServiceClientCredentials[]): Promise<AzureResourceDatabase[]>;
saveSelectedSubscriptions(account: Account, selectedSubscriptions: azureResource.AzureResourceSubscription[]): Promise<void>;
}
export interface IAzureResourceCacheService {
generateKey(id: string): string;
get<T>(key: string): T | undefined;
update<T>(key: string, value: T): void;
}
export interface IAzureResourceContextService {
getAbsolutePath(relativePath: string): string;
executeCommand(commandId: string, ...args: any[]): void;
showErrorMessage(errorMessage: string): void;
export interface IAzureResourceTenantService {
getTenantId(subscription: azureResource.AzureResourceSubscription): Promise<string>;
}
export interface IAzureResourceNodeWithProviderId {
resourceProviderId: string;
resourceNode: azureResource.IAzureResourceNode;
}

View File

@@ -7,9 +7,9 @@
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
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 {
public constructor(

View File

@@ -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');
}
});
}

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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');
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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');
}
});
}

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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');
}

View File

@@ -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;
}

View File

@@ -5,21 +5,9 @@
'use strict';
export interface AzureResourceSubscription {
id: string;
name: string;
}
export interface AzureResourceDatabaseServer {
name: string;
fullName: string;
loginName: string;
defaultDatabaseName: string;
}
export interface AzureResourceDatabase {
name: string;
serverName: string;
serverFullName: string;
loginName: string;
}
}

View 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();
}

View 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.');
}

View File

@@ -1,35 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'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();
}

View File

@@ -5,21 +5,30 @@
'use strict';
import { ExtensionContext } from "vscode";
import { ExtensionContext } from 'vscode';
import { IAzureResourceCacheService } from "../interfaces";
import { IAzureResourceCacheService } from '../interfaces';
export class AzureResourceCacheService implements IAzureResourceCacheService {
public constructor(
public readonly context: ExtensionContext
context: ExtensionContext
) {
this._context = context;
}
public get<T>(key: string): T | undefined {
return this.context.workspaceState.get(key);
public generateKey(id: string): string {
return `${AzureResourceCacheService.cacheKeyPrefix}.${id}`;
}
public get<T>(key: string): T | undefined {
return this._context.workspaceState.get(key);
}
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';
}

View File

@@ -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;
}

View File

@@ -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 * as sqlops 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: sqlops.Account, resource: sqlops.AzureResource): Promise<ServiceClientCredentials[]> {
try {
let credentials: TokenCredentials[] = [];
let tokens = await this._apiWrapper.getSecurityToken(account, resource);
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;
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -8,11 +8,11 @@
import { WorkspaceConfiguration, ConfigurationTarget } from 'vscode';
import { Account } from 'sqlops';
import { azureResource } from '../azure-resource';
import { IAzureResourceSubscriptionFilterService, IAzureResourceCacheService } from '../interfaces';
import { AzureResourceSubscription } from '../models';
interface AzureResourceSelectedSubscriptionsCache {
selectedSubscriptions: { [accountId: string]: AzureResourceSubscription[]};
selectedSubscriptions: { [accountId: string]: azureResource.AzureResourceSubscription[]};
}
export class AzureResourceSubscriptionFilterService implements IAzureResourceSubscriptionFilterService {
@@ -20,12 +20,14 @@ export class AzureResourceSubscriptionFilterService implements IAzureResourceSub
cacheService: IAzureResourceCacheService
) {
this._cacheService = cacheService;
this._cacheKey = this._cacheService.generateKey('selectedSubscriptions');
}
public async getSelectedSubscriptions(account: Account): Promise<AzureResourceSubscription[]> {
let selectedSubscriptions: AzureResourceSubscription[] = [];
public async getSelectedSubscriptions(account: Account): Promise<azureResource.AzureResourceSubscription[]> {
let selectedSubscriptions: azureResource.AzureResourceSubscription[] = [];
const cache = this._cacheService.get<AzureResourceSelectedSubscriptionsCache>(AzureResourceSubscriptionFilterService.CacheKey);
const cache = this._cacheService.get<AzureResourceSelectedSubscriptionsCache>(this._cacheKey);
if (cache) {
selectedSubscriptions = cache.selectedSubscriptions[account.key.accountId];
}
@@ -33,10 +35,10 @@ export class AzureResourceSubscriptionFilterService implements IAzureResourceSub
return selectedSubscriptions;
}
public async saveSelectedSubscriptions(account: Account, selectedSubscriptions: AzureResourceSubscription[]): Promise<void> {
let selectedSubscriptionsCache: { [accountId: string]: AzureResourceSubscription[]} = {};
public async saveSelectedSubscriptions(account: Account, selectedSubscriptions: azureResource.AzureResourceSubscription[]): Promise<void> {
let selectedSubscriptionsCache: { [accountId: string]: azureResource.AzureResourceSubscription[]} = {};
const cache = this._cacheService.get<AzureResourceSelectedSubscriptionsCache>(AzureResourceSubscriptionFilterService.CacheKey);
const cache = this._cacheService.get<AzureResourceSelectedSubscriptionsCache>(this._cacheKey);
if (cache) {
selectedSubscriptionsCache = cache.selectedSubscriptions;
}
@@ -47,14 +49,14 @@ export class AzureResourceSubscriptionFilterService implements IAzureResourceSub
selectedSubscriptionsCache[account.key.accountId] = selectedSubscriptions;
this._cacheService.update<AzureResourceSelectedSubscriptionsCache>(AzureResourceSubscriptionFilterService.CacheKey, { selectedSubscriptions: selectedSubscriptionsCache });
this._cacheService.update<AzureResourceSelectedSubscriptionsCache>(this._cacheKey, { selectedSubscriptions: selectedSubscriptionsCache });
const filters: string[] = [];
for (const accountId in selectedSubscriptionsCache) {
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;
if (resourceFilterConfig) {
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 _cacheService: IAzureResourceCacheService = undefined;
private _cacheKey: string = undefined;
private static readonly FilterConfigName = 'resourceFilter';
private static readonly CacheKey = 'azureResource.cache.selectedSubscriptions';
private static readonly filterConfigName = 'azure.resource.config.filter';
}

View File

@@ -9,24 +9,19 @@ import { Account } from 'sqlops';
import { ServiceClientCredentials } from 'ms-rest';
import { SubscriptionClient } from 'azure-arm-resource';
import { azureResource } from '../azure-resource';
import { IAzureResourceSubscriptionService } from '../interfaces';
import { AzureResourceSubscription } from '../models';
export class AzureResourceSubscriptionService implements IAzureResourceSubscriptionService {
public async getSubscriptions(account: Account, credentials: ServiceClientCredentials[]): Promise<AzureResourceSubscription[]> {
let subscriptions: AzureResourceSubscription[] = [];
for (let cred of credentials) {
let subClient = new SubscriptionClient.SubscriptionClient(cred);
try {
let subs = await subClient.subscriptions.list();
subs.forEach((sub) => subscriptions.push({
id: sub.subscriptionId,
name: sub.displayName
}));
} catch (error) {
// Swallow the exception here.
}
}
public async getSubscriptions(account: Account, credential: ServiceClientCredentials): Promise<azureResource.AzureResourceSubscription[]> {
const subscriptions: azureResource.AzureResourceSubscription[] = [];
const subClient = new SubscriptionClient.SubscriptionClient(credential);
const subs = await subClient.subscriptions.list();
subs.forEach((sub) => subscriptions.push({
id: sub.subscriptionId,
name: sub.displayName
}));
return subscriptions;
}

View File

@@ -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;
}
}

View File

@@ -7,10 +7,10 @@
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
import { NodeInfo } from 'sqlops';
import { TreeNode } from '../../treeNodes';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
import { TreeNode } from '../treeNode';
import { AzureResourceItemType } from '../constants';
export class AzureResourceAccountNotSignedInTreeNode extends TreeNode {
@@ -19,11 +19,11 @@ export class AzureResourceAccountNotSignedInTreeNode extends TreeNode {
}
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.command = {
title: AzureResourceAccountNotSignedInTreeNode.SignInLabel,
command: 'azureresource.signin',
title: AzureResourceAccountNotSignedInTreeNode.signInLabel,
command: 'azure.resource.signin',
arguments: [this]
};
return item;
@@ -31,7 +31,7 @@ export class AzureResourceAccountNotSignedInTreeNode extends TreeNode {
public getNodeInfo(): NodeInfo {
return {
label: AzureResourceAccountNotSignedInTreeNode.SignInLabel,
label: AzureResourceAccountNotSignedInTreeNode.signInLabel,
isLeaf: true,
errorMessage: undefined,
metadata: undefined,
@@ -47,5 +47,5 @@ export class AzureResourceAccountNotSignedInTreeNode extends TreeNode {
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 ...');
}

View File

@@ -6,44 +6,59 @@
'use strict';
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
import { Account, NodeInfo } from 'sqlops';
import { TreeNode } from '../../treeNodes';
import { Account, NodeInfo, AzureResource } from 'sqlops';
import { TokenCredentials } from 'ms-rest';
import { AppContext } from '../../appContext';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
import { azureResource } from '../azure-resource';
import { TreeNode } from '../treeNode';
import { AzureResourceCredentialError } from '../errors';
import { AzureResourceContainerTreeNodeBase } from './baseTreeNodes';
import { AzureResourceItemType } from '../constants';
import { AzureResourceItemType, AzureResourceServiceNames } from '../constants';
import { AzureResourceSubscriptionTreeNode } from './subscriptionTreeNode';
import { AzureResourceMessageTreeNode } from './messageTreeNode';
import { AzureResourceMessageTreeNode } from '../messageTreeNode';
import { AzureResourceErrorMessageUtil } from '../utils';
import { AzureResourceSubscription } from '../models';
import { IAzureResourceTreeChangeHandler } from './treeProvider';
import { IAzureResourceTreeChangeHandler } from './treeChangeHandler';
import { IAzureResourceSubscriptionService, IAzureResourceSubscriptionFilterService, IAzureResourceTenantService } from '../../azureResource/interfaces';
export class AzureResourceAccountTreeNode extends AzureResourceContainerTreeNodeBase {
public constructor(
account: Account,
public readonly account: Account,
appContext: AppContext,
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.setCacheKey(`${this._id}.subscriptions`);
this._label = this.generateLabel();
}
public async getChildren(): Promise<TreeNode[]> {
try {
let subscriptions: AzureResourceSubscription[] = [];
let subscriptions: azureResource.AzureResourceSubscription[] = [];
if (this._isClearingCache) {
const credentials = await this.getCredentials();
subscriptions = (await this.servicePool.subscriptionService.getSubscriptions(this.account, credentials)) || <AzureResourceSubscription[]>[];
try {
const tokens = await this.appContext.apiWrapper.getSecurityToken(this.account, AzureResource.ResourceManagement);
let cache = this.getCache<AzureResourceSubscriptionsCache>();
if (!cache) {
cache = { subscriptions: { } };
for (const tenant of this.account.properties.tenants) {
const token = tokens[tenant.id].token;
const tokenType = tokens[tenant.id].tokenType;
subscriptions.push(...(await this._subscriptionService.getSubscriptions(this.account, new TokenCredentials(token, tokenType)) || <azureResource.AzureResourceSubscription[]>[]));
}
} catch (error) {
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);
}
cache.subscriptions[this.account.key.accountId] = subscriptions;
this.updateCache<AzureResourceSubscriptionsCache>(cache);
this.updateCache<azureResource.AzureResourceSubscription[]>(subscriptions);
this._isClearingCache = false;
} else {
@@ -52,8 +67,8 @@ export class AzureResourceAccountTreeNode extends AzureResourceContainerTreeNode
this._totalSubscriptionCount = subscriptions.length;
let selectedSubscriptions = await this.servicePool.subscriptionFilterService.getSelectedSubscriptions(this.account);
let selectedSubscriptionIds = (selectedSubscriptions || <AzureResourceSubscription[]>[]).map((subscription) => subscription.id);
const selectedSubscriptions = await this._subscriptionFilterService.getSelectedSubscriptions(this.account);
const selectedSubscriptionIds = (selectedSubscriptions || <azureResource.AzureResourceSubscription[]>[]).map((subscription) => subscription.id);
if (selectedSubscriptionIds.length > 0) {
subscriptions = subscriptions.filter((subscription) => selectedSubscriptionIds.indexOf(subscription.id) !== -1);
this._selectedSubscriptionCount = selectedSubscriptionIds.length;
@@ -65,31 +80,36 @@ export class AzureResourceAccountTreeNode extends AzureResourceContainerTreeNode
this.refreshLabel();
if (subscriptions.length === 0) {
return [AzureResourceMessageTreeNode.create(AzureResourceAccountTreeNode.NoSubscriptions, this)];
return [AzureResourceMessageTreeNode.create(AzureResourceAccountTreeNode.noSubscriptionsLabel, this)];
} 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) {
return [AzureResourceMessageTreeNode.create(AzureResourceErrorMessageUtil.getErrorMessage(error), this)];
if (error instanceof AzureResourceCredentialError) {
this.appContext.apiWrapper.showErrorMessage(error.message);
this.appContext.apiWrapper.executeCommand('azure.resource.signin');
} else {
return [AzureResourceMessageTreeNode.create(AzureResourceErrorMessageUtil.getErrorMessage(error), this)];
}
}
}
public async getCachedSubscriptions(): Promise<AzureResourceSubscription[]> {
const subscriptions: AzureResourceSubscription[] = [];
const cache = this.getCache<AzureResourceSubscriptionsCache>();
if (cache) {
subscriptions.push(...cache.subscriptions[this.account.key.accountId]);
}
return subscriptions;
public async getCachedSubscriptions(): Promise<azureResource.AzureResourceSubscription[]> {
return this.getCache<azureResource.AzureResourceSubscription[]>();
}
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.contextValue = AzureResourceItemType.account;
item.iconPath = {
dark: this.servicePool.contextService.getAbsolutePath('resources/dark/account_inverse.svg'),
light: this.servicePool.contextService.getAbsolutePath('resources/light/account.svg')
dark: this.appContext.extensionContext.asAbsolutePath('resources/dark/account_inverse.svg'),
light: this.appContext.extensionContext.asAbsolutePath('resources/light/account.svg')
};
return item;
}
@@ -128,10 +148,6 @@ export class AzureResourceAccountTreeNode extends AzureResourceContainerTreeNode
}
}
protected get cacheKey(): string {
return 'azureResource.cache.subscriptions';
}
private generateLabel(): string {
let label = `${this.account.displayInfo.displayName} (${this.account.key.accountId})`;
@@ -142,14 +158,14 @@ export class AzureResourceAccountTreeNode extends AzureResourceContainerTreeNode
return label;
}
private _subscriptionService: IAzureResourceSubscriptionService = undefined;
private _subscriptionFilterService: IAzureResourceSubscriptionFilterService = undefined;
private _tenantService: IAzureResourceTenantService = undefined;
private _id: string = undefined;
private _label: string = undefined;
private _totalSubscriptionCount = 0;
private _selectedSubscriptionCount = 0;
private static readonly NoSubscriptions = localize('azureResource.tree.accountTreeNode.noSubscriptions', 'No Subscriptions found.');
}
interface AzureResourceSubscriptionsCache {
subscriptions: { [accountId: string]: AzureResourceSubscription[] };
}
private static readonly noSubscriptionsLabel = localize('azure.resource.tree.accountTreeNode.noSubscriptionsLabel', 'No Subscriptions found.');
}

View File

@@ -5,16 +5,16 @@
'use strict';
import * as sqlops from 'sqlops';
import { ServiceClientCredentials } from 'ms-rest';
import { TreeNode } from '../../treeNodes';
import { AppContext } from '../../appContext';
import { AzureResourceServicePool } from '../servicePool';
import { AzureResourceCredentialError } from '../errors';
import { TreeNode } from '../treeNode';
import { IAzureResourceTreeChangeHandler } from './treeChangeHandler';
import { IAzureResourceCacheService } from '../../azureResource/interfaces';
import { AzureResourceServiceNames } from '../constants';
export abstract class AzureResourceTreeNodeBase extends TreeNode {
public constructor(
public readonly appContext: AppContext,
public readonly treeChangeHandler: IAzureResourceTreeChangeHandler,
parent: TreeNode
) {
@@ -22,17 +22,17 @@ export abstract class AzureResourceTreeNodeBase extends TreeNode {
this.parent = parent;
}
public readonly servicePool = AzureResourceServicePool.getInstance();
}
export abstract class AzureResourceContainerTreeNodeBase extends AzureResourceTreeNodeBase {
public constructor(
public readonly account: sqlops.Account,
appContext: AppContext,
treeChangeHandler: IAzureResourceTreeChangeHandler,
parent: TreeNode
) {
super(treeChangeHandler, parent);
super(appContext, treeChangeHandler, parent);
this._cacheService = this.appContext.getService<IAzureResourceCacheService>(AzureResourceServiceNames.cacheService);
}
public clearCache(): void {
@@ -43,29 +43,19 @@ export abstract class AzureResourceContainerTreeNodeBase extends AzureResourceTr
return this._isClearingCache;
}
protected async getCredentials(): Promise<ServiceClientCredentials[]> {
try {
return await this.servicePool.credentialService.getCredentials(this.account, sqlops.AzureResource.ResourceManagement);
} catch (error) {
if (error instanceof AzureResourceCredentialError) {
this.servicePool.contextService.showErrorMessage(error.message);
this.servicePool.contextService.executeCommand('azureresource.signin');
} else {
throw error;
}
}
}
protected setCacheKey(id: string): void {
this._cacheKey = this._cacheService.generateKey(id);
}
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 {
return this.servicePool.cacheService.get<T>(this.cacheKey);
return this._cacheService.get<T>(this._cacheKey);
}
protected abstract get cacheKey(): string;
protected _isClearingCache = true;
private _cacheService: IAzureResourceCacheService = undefined;
private _cacheKey: string = undefined;
}

View File

@@ -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[] };
}

View File

@@ -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[] };
}

View File

@@ -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}`;
}
}

View File

@@ -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;
}

View File

@@ -7,38 +7,66 @@
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
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 { AzureResourceDatabaseContainerTreeNode } from './databaseContainerTreeNode';
import { AzureResourceDatabaseServerContainerTreeNode } from './databaseServerContainerTreeNode';
import { AzureResourceSubscription } from '../models';
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 readonly subscription: AzureResourceSubscription,
account: Account,
public readonly account: Account,
public readonly subscription: azureResource.AzureResourceSubscription,
public readonly tenatId: string,
appContext: AppContext,
treeChangeHandler: IAzureResourceTreeChangeHandler,
parent: TreeNode
) {
super(treeChangeHandler, parent);
super(appContext, treeChangeHandler, parent);
this._children.push(new AzureResourceDatabaseContainerTreeNode(subscription, account, treeChangeHandler, this));
this._children.push(new AzureResourceDatabaseServerContainerTreeNode(subscription, account, treeChangeHandler, this));
this._id = `account_${this.account.key.accountId}.subscription_${this.subscription.id}.tenant_${this.tenatId}`;
this.setCacheKey(`${this._id}.resources`);
}
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> {
let item = new TreeItem(this.subscription.name, TreeItemCollapsibleState.Collapsed);
const item = new TreeItem(this.subscription.name, TreeItemCollapsibleState.Collapsed);
item.contextValue = AzureResourceItemType.subscription;
item.iconPath = {
dark: this.servicePool.contextService.getAbsolutePath('resources/dark/subscription_inverse.svg'),
light: this.servicePool.contextService.getAbsolutePath('resources/light/subscription.svg')
dark: this.appContext.extensionContext.asAbsolutePath('resources/dark/subscription_inverse.svg'),
light: this.appContext.extensionContext.asAbsolutePath('resources/light/subscription.svg')
};
return item;
}
@@ -58,8 +86,10 @@ export class AzureResourceSubscriptionTreeNode extends AzureResourceTreeNodeBase
}
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.');
}

View File

@@ -5,7 +5,7 @@
'use strict';
import { TreeNode } from '../../treeNodes';
import { TreeNode } from '../treeNode';
export interface IAzureResourceTreeChangeHandler {
notifyNodeChanged(node: TreeNode): void;

View File

@@ -6,26 +6,25 @@
'use strict';
import { TreeDataProvider, EventEmitter, Event, TreeItem } from 'vscode';
import { DidChangeAccountsParams } from 'sqlops';
import { TreeNode } from '../../treeNodes';
import { setInterval, clearInterval } from 'timers';
import { AppContext } from '../../appContext';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
import { AzureResourceServicePool } from '../servicePool';
import { TreeNode } from '../treeNode';
import { AzureResourceAccountTreeNode } from './accountTreeNode';
import { AzureResourceAccountNotSignedInTreeNode } from './accountNotSignedInTreeNode';
import { AzureResourceMessageTreeNode } from './messageTreeNode';
import { AzureResourceContainerTreeNodeBase, AzureResourceTreeNodeBase } from './baseTreeNodes';
import { AzureResourceMessageTreeNode } from '../messageTreeNode';
import { AzureResourceContainerTreeNodeBase } from './baseTreeNodes';
import { AzureResourceErrorMessageUtil } from '../utils';
export interface IAzureResourceTreeChangeHandler {
notifyNodeChanged(node: TreeNode): void;
}
import { IAzureResourceTreeChangeHandler } from './treeChangeHandler';
import { IAzureResourceAccountService } from '../../azureResource/interfaces';
import { AzureResourceServiceNames } from '../constants';
export class AzureResourceTreeProvider implements TreeDataProvider<TreeNode>, IAzureResourceTreeChangeHandler {
public constructor() {
AzureResourceServicePool.getInstance().accountService.onDidChangeAccounts((e: DidChangeAccountsParams) => { this._onDidChangeTreeData.fire(undefined); });
public constructor(
public readonly appContext: AppContext
) {
}
public async getChildren(element?: TreeNode): Promise<TreeNode[]> {
@@ -37,7 +36,7 @@ export class AzureResourceTreeProvider implements TreeDataProvider<TreeNode>, IA
this._loadingTimer = setInterval(async () => {
try {
// 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
this.isSystemInitialized = true;
@@ -51,16 +50,16 @@ export class AzureResourceTreeProvider implements TreeDataProvider<TreeNode>, IA
// System not initialized yet
this.isSystemInitialized = false;
}
}, AzureResourceTreeProvider.LoadingTimerInterval);
}, AzureResourceTreeProvider.loadingTimerInterval);
return [AzureResourceMessageTreeNode.create(AzureResourceTreeProvider.Loading, undefined)];
return [AzureResourceMessageTreeNode.create(AzureResourceTreeProvider.loadingLabel, undefined)];
}
try {
const accounts = await AzureResourceServicePool.getInstance().accountService.getAccounts();
const accounts = await this.appContext.getService<IAzureResourceAccountService>(AzureResourceServiceNames.accountService).getAccounts();
if (accounts && accounts.length > 0) {
return accounts.map((account) => new AzureResourceAccountTreeNode(account, this));
return accounts.map((account) => new AzureResourceAccountTreeNode(account, this.appContext, this));
} else {
return [new AzureResourceAccountNotSignedInTreeNode()];
}
@@ -96,6 +95,6 @@ export class AzureResourceTreeProvider implements TreeDataProvider<TreeNode>, IA
private _loadingTimer: NodeJS.Timer = undefined;
private _onDidChangeTreeData = new EventEmitter<TreeNode>();
private static readonly Loading = localize('azureResource.tree.treeProvider.loading', 'Loading ...');
private static readonly LoadingTimerInterval = 5000;
private static readonly loadingLabel = localize('azure.resource.tree.treeProvider.loadingLabel', 'Loading ...');
private static readonly loadingTimerInterval = 5000;
}

View File

@@ -11,16 +11,6 @@ import * as vscode from 'vscode';
type TreeNodePredicate = (node: TreeNode) => boolean;
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 {
let path = undefined;
if (this.parent) {
@@ -65,13 +55,23 @@ export abstract class TreeNode {
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
*/
public abstract get nodePathValue(): string;
abstract getChildren(refreshChildren: boolean): TreeNode[] | Promise<TreeNode[]>;
abstract getTreeItem(): vscode.TreeItem | Promise<vscode.TreeItem>;
abstract getNodeInfo(): sqlops.NodeInfo;
private _parent: TreeNode = undefined;
}

View File

@@ -12,10 +12,9 @@ export function getErrorMessage(error: Error | string): string {
return (error instanceof Error) ? error.message : error;
}
export class AzureResourceErrorMessageUtil {
public static getErrorMessage(error: Error | string): string {
return localize('azureResource.error', 'Error: {0}', getErrorMessage(error));
return localize('azure.resource.error', 'Error: {0}', getErrorMessage(error));
}
}

View File

@@ -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 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';
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));
this.appContext.getService<IAzureResourceAccountService>(AzureResourceServiceNames.accountService).onDidChangeAccounts((e: DidChangeAccountsParams) => { azureResourceTree.notifyNodeChanged(undefined); });
registerAzureResourceCommands(this.appContext, azureResourceTree);
registerAzureResourceDatabaseServerCommands(this.appContext);
registerAzureResourceDatabaseCommands(this.appContext);
return Promise.resolve(true);
}
public deactivate(): void {
}
}

View File

@@ -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);
}
}

View File

@@ -6,12 +6,17 @@ import * as path from 'path';
import * as os from 'os';
import * as constants from './constants';
import MainController from './controllers/mainController';
import AzureResourceController from './controllers/azureResourceController';
import { AppContext } from './appContext';
import ControllerBase from './controllers/controllerBase';
import { ApiWrapper } from './apiWrapper';
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[] = [];
@@ -35,7 +40,8 @@ export function getDefaultLogLocation() {
// this method is called when your extension is activated
// your extension is activated the very first time the command is executed
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>[] = [];
// Create the folder for storing the token caches
@@ -56,21 +62,19 @@ export function activate(extensionContext: vscode.ExtensionContext) {
extensionContext.subscriptions.push(accountProviderService);
accountProviderService.activate();
// Start the main controller
let mainController = new MainController(appContext);
controllers.push(mainController);
extensionContext.subscriptions.push(mainController);
activations.push(mainController.activate());
const azureResourceController = new AzureResourceController(appContext);
controllers.push(azureResourceController);
extensionContext.subscriptions.push(azureResourceController);
activations.push(azureResourceController.activate());
return Promise.all(activations)
.then((results: boolean[]) => {
for (let result of results) {
if (!result) {
return false;
}
}
return true;
});
return {
provideResources() {
return [
new AzureResourceDatabaseServerProvider(new AzureResourceDatabaseServerService(), apiWrapper, extensionContext),
new AzureResourceDatabaseProvider(new AzureResourceDatabaseService(), apiWrapper, extensionContext)
];
}
};
}
// this method is called when your extension is deactivated

View File

@@ -9,8 +9,8 @@ import * as should from 'should';
import * as vscode from 'vscode';
import 'mocha';
import { AzureResourceItemType } from '../../../azureResource/constants';
import { AzureResourceMessageTreeNode } from '../../../azureResource/tree/messageTreeNode';
import { AzureResourceItemType } from '../../azureResource/constants';
import { AzureResourceMessageTreeNode } from '../../azureResource/messageTreeNode';
describe('AzureResourceMessageTreeNode.info', function(): void {
it('Should be correct when created.', async function(): Promise<void> {

View File

@@ -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);
}
});
});

View File

@@ -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);
}
});
});

View File

@@ -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();
});
});

View File

@@ -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);
});
});

View File

@@ -26,7 +26,7 @@ describe('AzureResourceAccountNotSignedInTreeNode.info', function(): void {
should(treeItem.collapsibleState).equal(vscode.TreeItemCollapsibleState.None);
should(treeItem.command).not.undefined();
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();
should(nodeInfo.isLeaf).true();

View File

@@ -10,35 +10,38 @@ import * as TypeMoq from 'typemoq';
import * as sqlops from 'sqlops';
import * as vscode from 'vscode';
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 {
IAzureResourceCacheService,
IAzureResourceContextService,
IAzureResourceCredentialService,
IAzureResourceSubscriptionService,
IAzureResourceSubscriptionFilterService
IAzureResourceSubscriptionFilterService,
IAzureResourceTenantService
} from '../../../azureResource/interfaces';
import { IAzureResourceTreeChangeHandler } from '../../../azureResource/tree/treeChangeHandler';
import { AzureResourceAccountTreeNode } from '../../../azureResource/tree/accountTreeNode';
import { AzureResourceSubscription } from '../../../azureResource/models';
import { AzureResourceSubscriptionTreeNode } from '../../../azureResource/tree/subscriptionTreeNode';
import { AzureResourceItemType } from '../../../azureResource/constants';
import { AzureResourceMessageTreeNode } from '../../../azureResource/tree/messageTreeNode';
import { AzureResourceItemType, AzureResourceServiceNames } from '../../../azureResource/constants';
import { AzureResourceMessageTreeNode } from '../../../azureResource/messageTreeNode';
import { ApiWrapper } from '../../../apiWrapper';
import { generateGuid } from '../../../azureResource/utils';
// Mock services
const mockServicePool = AzureResourceServicePool.getInstance();
let mockExtensionContext: TypeMoq.IMock<vscode.ExtensionContext>;
let mockApiWrapper: TypeMoq.IMock<ApiWrapper>;
let mockCacheService: TypeMoq.IMock<IAzureResourceCacheService>;
let mockContextService: TypeMoq.IMock<IAzureResourceContextService>;
let mockCredentialService: TypeMoq.IMock<IAzureResourceCredentialService>;
let mockSubscriptionService: TypeMoq.IMock<IAzureResourceSubscriptionService>;
let mockSubscriptionFilterService: TypeMoq.IMock<IAzureResourceSubscriptionFilterService>;
let mockTenantService: TypeMoq.IMock<IAzureResourceTenantService>;
let mockAppContext: AppContext;
let mockTreeChangeHandler: TypeMoq.IMock<IAzureResourceTreeChangeHandler>;
// Mock test data
const mockTenantId = 'mock_tenant_id';
const mockAccount: sqlops.Account = {
key: {
accountId: 'mock_account',
@@ -49,51 +52,68 @@ const mockAccount: sqlops.Account = {
accountType: 'Microsoft',
contextualDisplayName: 'test'
},
properties: undefined,
properties: {
tenants: [
{
id: mockTenantId
}
]
},
isStale: false
};
const mockCredential = TypeMoq.Mock.ofType<ServiceClientCredentials>().object;
const mockCredentials = [mockCredential];
const mockSubscription1: AzureResourceSubscription = {
const mockSubscription1: azureResource.AzureResourceSubscription = {
id: 'mock_subscription_1',
name: 'mock subscription 1'
};
const mockSubscription2: AzureResourceSubscription = {
const mockSubscription2: azureResource.AzureResourceSubscription = {
id: 'mock_subscription_2',
name: 'mock subscription 2'
};
const mockSubscriptions = [mockSubscription1, mockSubscription2];
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 {
beforeEach(() => {
mockContextService = TypeMoq.Mock.ofType<IAzureResourceContextService>();
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
mockApiWrapper = TypeMoq.Mock.ofType<ApiWrapper>();
mockCacheService = TypeMoq.Mock.ofType<IAzureResourceCacheService>();
mockCredentialService = TypeMoq.Mock.ofType<IAzureResourceCredentialService>();
mockSubscriptionService = TypeMoq.Mock.ofType<IAzureResourceSubscriptionService>();
mockSubscriptionFilterService = TypeMoq.Mock.ofType<IAzureResourceSubscriptionFilterService>();
mockTenantService = TypeMoq.Mock.ofType<IAzureResourceTenantService>();
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
mockSubscriptionCache = { subscriptions: {} };
mockSubscriptionCache = [];
mockServicePool.contextService = mockContextService.object;
mockServicePool.cacheService = mockCacheService.object;
mockServicePool.credentialService = mockCredentialService.object;
mockServicePool.subscriptionService = mockSubscriptionService.object;
mockServicePool.subscriptionFilterService = mockSubscriptionFilterService.object;
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);
mockCredentialService.setup((o) => o.getCredentials(mockAccount, sqlops.AzureResource.ResourceManagement)).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.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> {
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockTreeChangeHandler.object);
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
const accountTreeNodeId = `account_${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> {
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));
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();
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> {
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));
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();
should(treeItem.label).equal(accountTreeNodeLabel);
@@ -150,36 +176,41 @@ describe('AzureResourceAccountTreeNode.info', function(): void {
describe('AzureResourceAccountTreeNode.getChildren', function(): void {
beforeEach(() => {
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
mockApiWrapper = TypeMoq.Mock.ofType<ApiWrapper>();
mockCacheService = TypeMoq.Mock.ofType<IAzureResourceCacheService>();
mockCredentialService = TypeMoq.Mock.ofType<IAzureResourceCredentialService>();
mockSubscriptionService = TypeMoq.Mock.ofType<IAzureResourceSubscriptionService>();
mockSubscriptionFilterService = TypeMoq.Mock.ofType<IAzureResourceSubscriptionFilterService>();
mockTenantService = TypeMoq.Mock.ofType<IAzureResourceTenantService>();
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
mockSubscriptionCache = { subscriptions: {} };
mockSubscriptionCache = [];
mockServicePool.cacheService = mockCacheService.object;
mockServicePool.credentialService = mockCredentialService.object;
mockServicePool.subscriptionService = mockSubscriptionService.object;
mockServicePool.subscriptionFilterService = mockSubscriptionFilterService.object;
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);
mockCredentialService.setup((o) => o.getCredentials(mockAccount, sqlops.AzureResource.ResourceManagement)).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.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> {
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, mockCredentials)).returns(() => Promise.resolve(mockSubscriptions));
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => Promise.resolve(undefined));
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, mockCredential)).returns(() => Promise.resolve(mockSubscriptions));
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();
mockCredentialService.verify((o) => o.getCredentials(mockAccount, sqlops.AzureResource.ResourceManagement), TypeMoq.Times.once());
mockSubscriptionService.verify((o) => o.getSubscriptions(mockAccount, mockCredentials), TypeMoq.Times.once());
mockCacheService.verify((o) => o.get(TypeMoq.It.isAnyString()), TypeMoq.Times.once());
mockApiWrapper.verify((o) => o.getSecurityToken(mockAccount, sqlops.AzureResource.ResourceManagement), TypeMoq.Times.once());
mockSubscriptionService.verify((o) => o.getSubscriptions(mockAccount, mockCredential), 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());
mockSubscriptionFilterService.verify((o) => o.getSelectedSubscriptions(mockAccount), TypeMoq.Times.once());
@@ -192,43 +223,42 @@ describe('AzureResourceAccountTreeNode.getChildren', function(): void {
should(children).Array();
should(children.length).equal(mockSubscriptions.length);
should(Object.keys(mockSubscriptionCache.subscriptions)).deepEqual([mockAccount.key.accountId]);
should(mockSubscriptionCache.subscriptions[mockAccount.key.accountId]).deepEqual(mockSubscriptions);
should(mockSubscriptionCache).deepEqual(mockSubscriptions);
for (let ix = 0; ix < mockSubscriptions.length; ix++) {
const child = children[ix];
const subscription = mockSubscriptions[ix];
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> {
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));
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockTreeChangeHandler.object);
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
await accountTreeNode.getChildren();
const children = await accountTreeNode.getChildren();
mockCredentialService.verify((o) => o.getCredentials(mockAccount, sqlops.AzureResource.ResourceManagement), TypeMoq.Times.exactly(1));
mockSubscriptionService.verify((o) => o.getSubscriptions(mockAccount, 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));
mockApiWrapper.verify((o) => o.getSecurityToken(mockAccount, sqlops.AzureResource.ResourceManagement), 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.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++) {
should(children[ix].nodePathValue).equal(`subscription_${mockSubscriptionCache.subscriptions[mockAccount.key.accountId][ix].id}`);
for (let ix = 0; ix < mockSubscriptionCache.length; ix++) {
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> {
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();
@@ -242,10 +272,10 @@ describe('AzureResourceAccountTreeNode.getChildren', function(): 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));
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockTreeChangeHandler.object);
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
const children = await accountTreeNode.getChildren();
@@ -255,23 +285,25 @@ describe('AzureResourceAccountTreeNode.getChildren', function(): void {
should(children.length).equal(mockFilteredSubscriptions.length);
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> {
const mockError = 'Test error';
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, mockCredentials)).returns(() => { throw new Error(mockError); });
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, mockCredential)).returns(() => Promise.resolve(mockSubscriptions));
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();
mockCredentialService.verify((o) => o.getCredentials(mockAccount, sqlops.AzureResource.ResourceManagement), TypeMoq.Times.once());
mockSubscriptionService.verify((o) => o.getSubscriptions(mockAccount, mockCredentials), TypeMoq.Times.once());
mockApiWrapper.verify((o) => o.getSecurityToken(mockAccount, sqlops.AzureResource.ResourceManagement), 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.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.never());
mockSubscriptionFilterService.verify((o) => o.getSelectedSubscriptions(mockAccount), TypeMoq.Times.never());
mockCacheService.verify((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.once());
should(children).Array();
should(children.length).equal(1);
@@ -283,12 +315,33 @@ describe('AzureResourceAccountTreeNode.getChildren', function(): void {
describe('AzureResourceAccountTreeNode.clearCache', function() : void {
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>();
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> {
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockTreeChangeHandler.object);
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
accountTreeNode.clearCache();
should(accountTreeNode.isClearingCache).true();
});
});
});

View File

@@ -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, sqlops.AzureResource.ResourceManagement)).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, sqlops.AzureResource.ResourceManagement), 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, sqlops.AzureResource.ResourceManagement), 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, sqlops.AzureResource.ResourceManagement), 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();
});
});

View File

@@ -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, sqlops.AzureResource.ResourceManagement)).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, sqlops.AzureResource.ResourceManagement), 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, sqlops.AzureResource.ResourceManagement), 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, sqlops.AzureResource.ResourceManagement), 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();
});
});

View File

@@ -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);
});
});

View File

@@ -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);
});
});

View File

@@ -10,20 +10,24 @@ import * as TypeMoq from 'typemoq';
import * as sqlops from 'sqlops';
import * as vscode from 'vscode';
import 'mocha';
import { AppContext } from '../../../appContext';
import { ApiWrapper } from '../../../apiWrapper';
import { AzureResourceServicePool } from '../../../azureResource/servicePool';
import { IAzureResourceContextService } from '../../../azureResource/interfaces';
import { azureResource } from '../../../azureResource/azure-resource';
import { IAzureResourceTreeChangeHandler } from '../../../azureResource/tree/treeChangeHandler';
import { AzureResourceSubscription } from '../../../azureResource/models';
import { AzureResourceSubscriptionTreeNode } from '../../../azureResource/tree/subscriptionTreeNode';
import { AzureResourceDatabaseContainerTreeNode } from '../../../azureResource/tree/databaseContainerTreeNode';
import { AzureResourceDatabaseServerContainerTreeNode } from '../../../azureResource/tree/databaseServerContainerTreeNode';
import { AzureResourceItemType } from '../../../azureResource/constants';
import { AzureResourceItemType, AzureResourceServiceNames } from '../../../azureResource/constants';
import { AzureResourceService } from '../../../azureResource/resourceService';
import { AzureResourceResourceTreeNode } from '../../../azureResource/resourceTreeNode';
import { IAzureResourceCacheService } from '../../../azureResource/interfaces';
import { generateGuid } from '../../../azureResource/utils';
// 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>;
@@ -42,24 +46,60 @@ const mockAccount: sqlops.Account = {
isStale: false
};
const mockSubscription: AzureResourceSubscription = {
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('AzureResourceSubscriptionTreeNode.info', function(): void {
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>();
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> {
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();
should(treeItem.label).equal(mockSubscription.name);
@@ -76,16 +116,52 @@ describe('AzureResourceSubscriptionTreeNode.info', function(): void {
describe('AzureResourceSubscriptionTreeNode.getChildren', function(): void {
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>();
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> {
const subscriptionTreeNode = new AzureResourceSubscriptionTreeNode(mockSubscription, mockAccount, mockTreeChangeHandler.object, undefined);
it('Should return resource containers.', async function(): Promise<void> {
const subscriptionTreeNode = new AzureResourceSubscriptionTreeNode(mockAccount, mockSubscription, mockTenantId, mockAppContext, mockTreeChangeHandler.object, undefined);
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.length).equal(2);
should(children[0]).instanceof(AzureResourceDatabaseContainerTreeNode);
should(children[1]).instanceof(AzureResourceDatabaseServerContainerTreeNode);
should(children.length).equal(expectedChildren.length);
for (const child of children) {
should(child).instanceOf(AzureResourceResourceTreeNode);
}
});
});

View File

@@ -5,21 +5,28 @@
'use strict';
import * as vscode from 'vscode';
import * as should from 'should';
import * as TypeMoq from 'typemoq';
import * as sqlops from 'sqlops';
import 'mocha';
import { AppContext } from '../../../appContext';
import { ApiWrapper } from '../../../apiWrapper';
import { AzureResourceServicePool } from '../../../azureResource/servicePool';
import { IAzureResourceAccountService } from '../../../azureResource/interfaces';
import { IAzureResourceCacheService, IAzureResourceAccountService } from '../../../azureResource/interfaces';
import { AzureResourceTreeProvider } from '../../../azureResource/tree/treeProvider';
import { AzureResourceAccountTreeNode } from '../../../azureResource/tree/accountTreeNode';
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
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>;
// Mock test data
@@ -53,15 +60,23 @@ const mockAccounts = [mockAccount1, mockAccount2];
describe('AzureResourceTreeProvider.getChildren', function(): void {
beforeEach(() => {
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
mockApiWrapper = TypeMoq.Mock.ofType<ApiWrapper>();
mockCacheService = TypeMoq.Mock.ofType<IAzureResourceCacheService>();
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> {
mockAccountService.setup((o) => o.getAccounts()).returns(() => Promise.resolve(mockAccounts));
const treeProvider = new AzureResourceTreeProvider();
const treeProvider = new AzureResourceTreeProvider(mockAppContext);
treeProvider.isSystemInitialized = true;
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> {
mockAccountService.setup((o) => o.getAccounts()).returns(() => Promise.resolve(undefined));
const treeProvider = new AzureResourceTreeProvider();
const treeProvider = new AzureResourceTreeProvider(mockAppContext);
treeProvider.isSystemInitialized = true;
const children = await treeProvider.getChildren(undefined);
@@ -97,7 +112,7 @@ describe('AzureResourceTreeProvider.getChildren', function(): void {
const mockAccountError = 'Test account error';
mockAccountService.setup((o) => o.getAccounts()).returns(() => { throw new Error(mockAccountError); });
const treeProvider = new AzureResourceTreeProvider();
const treeProvider = new AzureResourceTreeProvider(mockAppContext);
treeProvider.isSystemInitialized = true;
const children = await treeProvider.getChildren(undefined);

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +1,50 @@
# Microsoft SQL Server Import for Azure Data Studio
Microsoft SQL Server Import for Azure Data Studio is a simple way to copy data from a flat file (.csv, .txt, .json) to a SQL Server table. Checkout below the reasons for using the Import Flat File wizard, how to find this wizard, and a simple example.
Microsoft SQL Server Import for Azure Data Studio includes two wizards:
- [Import Flat File Wizard](#import-flat-file-wizard-preview)
- [Data-tier Application Wizard.](#data-tier-application-wizard-preview)
## Import Flat File Wizard *(preview)*
**The Import Flat File Wizard** is a simple way to copy data from a flat file (.csv, .txt, .json) to a SQL Server table. Checkout below the reasons for using the Import Flat File wizard, how to find this wizard, and a simple example.
This experience is currently in its initial preview. Please report issues and feature requests [here.](https://github.com/microsoft/azuredatastudio/issues)
<img src="https://user-images.githubusercontent.com/30873802/43433347-c958ed28-942b-11e8-8bbc-f4f2529c3978.png" width="800px" />
### Requirements
* This wizard requires an active connection to a SQL Server instance to start.
* This wizard only works on .txt and .csv files.
### How do I start the Import Flat File wizard?
* The main entry point for the wizard is to right click a database in the Object Explorer, and click **Import wizard**.
* If a user is connected to a SQL Server instance, the user can also press **Ctrl**+**I** to start the wizard.
### Why would I use the Import Flat File wizard?
This wizard was created to improve the current import experience leveraging an intelligent framework known as Program Synthesis using Examples ([PROSE](https://microsoft.github.io/prose/)). For a user without specialized domain knowledge, importing data can often be a complex, error prone, and tedious task. This wizard streamlines the import process as simple as selecting an input file and unique table name, and the PROSE framework handles the rest.
PROSE analyzes data patterns in your input file to infer column names, types, delimiters, and more. This framework learns the structure of the file and does all of the hard work so users don't have to.
Please note that the PROSE binary components used by this extension are licensed under the [MICROSOFT SQL TOOLS IMPORT FLAT FILE EULA](https://raw.githubusercontent.com/Microsoft/azuredatastudio/master/extensions/import/Microsoft_SQL_Server_Import_Extension_and_Tools_Import_Flat_File_Preview.docx).
## Data-tier Application Wizard *(preview)*
**The Data-tier Application Wizard** provides an easy to use experience to deploy and extract .dacpac files and import and export .bacpac files.
This experience is currently in its initial preview. Please report issues and feature requests [here.](https://github.com/microsoft/azuredatastudio/issues)
<img src="https://user-images.githubusercontent.com/30873802/49676289-f2df6880-fa2d-11e8-8bfa-6213b7734075.png" width="800px" />
### Requirements
* This wizard requires an active connection to a SQL Server instance to start.
### How do I start the Data-tier Application wizard?
* The main entry point for the wizard is to right click a database in the Object Explorer, and click **Data-tier Application wizard**.
* If a user is connected to a SQL Server instance, the user can also start the wizard from the command palette (Ctrl+Shift+P) by searching for **Data-tier Application wizard.**
### Why would I use the Data-tier Application wizard?
This wizard was created to add the ability to extract and deploy .dacpac files and import and export .bacpac files in Azure Data Studio.
To learn more about Data-Tier Applications and working with dacpac and bacpac files, [you can read more here.](https://docs.microsoft.com/en-us/sql/relational-databases/data-tier-applications/data-tier-applications?view=sql-server-2017)
## License
Copyright (c) Microsoft Corporation. All rights reserved.
@@ -12,21 +53,6 @@ Licensed under the [MICROSOFT SQL SERVER IMPORT EXTENSION EULA](https://raw.gith
> Note: Microsoft SQL Server Import for Azure Data Studio extension contains the Microsoft SQL Tools Import Flat File component which is also licensed under the above EULA.
## Requirements
* This wizard requires an active connection to a SQL Server instance to start.
* This wizard only works on .txt and .csv files.
## How do I start the Flat File Import wizard?
* The main entry point for the wizard is to right click a database in the Object Explorer, and click **Import wizard**.
* If a user is connected to a SQL Server instance, the user can also press **Ctrl**+**I** to start the wizard.
## Why would I use the Flat File Import wizard?
This wizard was created to improve the current import experience leveraging an intelligent framework known as Program Synthesis using Examples ([PROSE](https://microsoft.github.io/prose/)). For a user without specialized domain knowledge, importing data can often be a complex, error prone, and tedious task. This wizard streamlines the import process as simple as selecting an input file and unique table name, and the PROSE framework handles the rest.
PROSE analyzes data patterns in your input file to infer column names, types, delimiters, and more. This framework learns the structure of the file and does all of the hard work so users don't have to.
Please note that the PROSE binary components used by this extension are licensed under the [MICROSOFT SQL TOOLS IMPORT FLAT FILE EULA](https://raw.githubusercontent.com/Microsoft/azuredatastudio/master/extensions/import/Microsoft_SQL_Server_Import_Extension_and_Tools_Import_Flat_File_Preview.docx).
## Code of Conduct
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.

View File

@@ -2,7 +2,7 @@
"name": "import",
"displayName": "SQL Server Import",
"description": "SQL Server Import for Azure Data Studio supports importing CSV or JSON files into SQL Server.",
"version": "0.4.2",
"version": "0.5.0",
"publisher": "Microsoft",
"preview": true,
"engines": {
@@ -67,7 +67,7 @@
}
},
"dependencies": {
"dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#0.2.10",
"dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#0.2.11",
"opener": "^1.4.3",
"service-downloader": "github:anthonydresser/service-downloader#0.1.5",
"vscode-extension-telemetry": "0.0.18",

View File

@@ -202,7 +202,7 @@ export class DataTierApplicationWizard {
}
private async deploy() {
let service = await DataTierApplicationWizard.getService();
let service = await DataTierApplicationWizard.getService(this.model.server.providerName);
let ownerUri = await sqlops.connection.getUriForConnection(this.model.server.connectionId);
let result = await service.deployDacpac(this.model.filePath, this.model.database, this.model.upgradeExisting, ownerUri, sqlops.TaskExecutionMode.execute);
@@ -213,7 +213,7 @@ export class DataTierApplicationWizard {
}
private async extract() {
let service = await DataTierApplicationWizard.getService();
let service = await DataTierApplicationWizard.getService(this.model.server.providerName);
let ownerUri = await sqlops.connection.getUriForConnection(this.model.server.connectionId);
let result = await service.extractDacpac(this.model.database, this.model.filePath, this.model.database, this.model.version, ownerUri, sqlops.TaskExecutionMode.execute);
@@ -224,7 +224,7 @@ export class DataTierApplicationWizard {
}
private async export() {
let service = await DataTierApplicationWizard.getService();
let service = await DataTierApplicationWizard.getService(this.model.server.providerName);
let ownerUri = await sqlops.connection.getUriForConnection(this.model.server.connectionId);
let result = await service.exportBacpac(this.model.database, this.model.filePath, ownerUri, sqlops.TaskExecutionMode.execute);
@@ -235,7 +235,7 @@ export class DataTierApplicationWizard {
}
private async import() {
let service = await DataTierApplicationWizard.getService();
let service = await DataTierApplicationWizard.getService(this.model.server.providerName);
let ownerUri = await sqlops.connection.getUriForConnection(this.model.server.connectionId);
let result = await service.importBacpac(this.model.filePath, this.model.database, ownerUri, sqlops.TaskExecutionMode.execute);
@@ -245,9 +245,8 @@ export class DataTierApplicationWizard {
}
}
public static async getService(): Promise<sqlops.DacFxServicesProvider> {
let currentConnection = await sqlops.connection.getCurrentConnection();
let service = sqlops.dataprotocol.getProvider<sqlops.DacFxServicesProvider>(currentConnection.providerName, sqlops.DataProviderType.DacFxServicesProvider);
private static async getService(providerName: string): Promise<sqlops.DacFxServicesProvider> {
let service = sqlops.dataprotocol.getProvider<sqlops.DacFxServicesProvider>(providerName, sqlops.DataProviderType.DacFxServicesProvider);
return service;
}
}

View File

@@ -32,7 +32,7 @@ export class DeployConfigPage extends DacFxConfigPage {
}
async start(): Promise<boolean> {
let serverComponent = await this.createServerDropdown(true);
let serverComponent = await this.createServerDropdown(true);
let fileBrowserComponent = await this.createFileBrowser();
this.databaseComponent = await this.createDatabaseTextBox();
this.databaseComponent.title = localize('dacFx.databaseNameTextBox', 'Database Name');
@@ -131,7 +131,7 @@ export class DeployConfigPage extends DacFxConfigPage {
this.model.database = this.databaseTextBox.value;
});
// Initialize with upgrade existing true
//Initialize with upgrade existing true
upgradeRadioButton.checked = true;
this.model.upgradeExisting = true;
@@ -149,10 +149,10 @@ export class DeployConfigPage extends DacFxConfigPage {
}
protected async createDeployDatabaseDropdown(): Promise<sqlops.FormComponent> {
this.databaseDropdown = this.view.modelBuilder.dropDown().withProperties({
this.databaseDropdown = this.view.modelBuilder.dropDown().withProperties({
required: true
}).component();
// Handle database changes
//Handle database changes
this.databaseDropdown.onValueChanged(async () => {
this.model.database = (<sqlops.CategoryValue>this.databaseDropdown.value).name;
});
@@ -172,7 +172,8 @@ export class DeployConfigPage extends DacFxConfigPage {
}
let values = await this.getDatabaseValues();
if (this.model.database === undefined) {
//set the database to the first dropdown value if upgrading, otherwise it should get set to the textbox value
if (this.model.upgradeExisting) {
this.model.database = values[0].name;
}

View File

@@ -5,14 +5,12 @@
agent-base@4, agent-base@^4.1.0:
version "4.2.1"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9"
integrity sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==
dependencies:
es6-promisify "^5.0.0"
applicationinsights@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-1.0.1.tgz#53446b830fe8d5d619eee2a278b31d3d25030927"
integrity sha1-U0Rrgw/o1dYZ7uKieLMdPSUDCSc=
dependencies:
diagnostic-channel "0.2.0"
diagnostic-channel-publishers "0.2.1"
@@ -21,12 +19,10 @@ applicationinsights@1.0.1:
base64-js@0.0.8:
version "0.0.8"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-0.0.8.tgz#1101e9544f4a76b1bc3b26d452ca96d7a35e7978"
integrity sha1-EQHpVE9KdrG8OybUUsqW16NeeXg=
bl@^1.0.0:
version "1.2.2"
resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.2.tgz#a160911717103c07410cef63ef51b397c025af9c"
integrity sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==
dependencies:
readable-stream "^2.3.5"
safe-buffer "^5.1.1"
@@ -34,12 +30,10 @@ bl@^1.0.0:
buffer-alloc-unsafe@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0"
integrity sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==
buffer-alloc@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/buffer-alloc/-/buffer-alloc-1.2.0.tgz#890dd90d923a873e08e10e5fd51a57e5b7cce0ec"
integrity sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==
dependencies:
buffer-alloc-unsafe "^1.1.0"
buffer-fill "^1.0.0"
@@ -47,17 +41,14 @@ buffer-alloc@^1.2.0:
buffer-crc32@~0.2.3:
version "0.2.13"
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=
buffer-fill@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c"
integrity sha1-+PeLdniYiO858gXNY39o5wISKyw=
buffer@^3.0.1:
version "3.6.0"
resolved "https://registry.yarnpkg.com/buffer/-/buffer-3.6.0.tgz#a72c936f77b96bf52f5f7e7b467180628551defb"
integrity sha1-pyyTb3e5a/UvX357RnGAYoVR3vs=
dependencies:
base64-js "0.0.8"
ieee754 "^1.1.4"
@@ -66,39 +57,34 @@ buffer@^3.0.1:
commander@~2.8.1:
version "2.8.1"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.8.1.tgz#06be367febfda0c330aa1e2a072d3dc9762425d4"
integrity sha1-Br42f+v9oMMwqh4qBy09yXYkJdQ=
dependencies:
graceful-readlink ">= 1.0.0"
core-util-is@~1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
"dataprotocol-client@github:Microsoft/sqlops-dataprotocolclient#0.2.10":
version "0.2.10"
resolved "https://codeload.github.com/Microsoft/sqlops-dataprotocolclient/tar.gz/4de3f7caf0eba54159911b977ddb4f5d7c0a9ca8"
"dataprotocol-client@github:Microsoft/sqlops-dataprotocolclient#0.2.11":
version "0.2.11"
resolved "https://codeload.github.com/Microsoft/sqlops-dataprotocolclient/tar.gz/bc80d2226699d23f45a2ec26129cbcdee4781ca9"
dependencies:
vscode-languageclient "3.5.1"
debug@3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
dependencies:
ms "2.0.0"
debug@^3.1.0:
version "3.2.6"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
dependencies:
ms "^2.1.1"
decompress-tar@^4.0.0, decompress-tar@^4.1.0, decompress-tar@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/decompress-tar/-/decompress-tar-4.1.1.tgz#718cbd3fcb16209716e70a26b84e7ba4592e5af1"
integrity sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ==
dependencies:
file-type "^5.2.0"
is-stream "^1.1.0"
@@ -107,7 +93,6 @@ decompress-tar@^4.0.0, decompress-tar@^4.1.0, decompress-tar@^4.1.1:
decompress-tarbz2@^4.0.0:
version "4.1.1"
resolved "https://registry.yarnpkg.com/decompress-tarbz2/-/decompress-tarbz2-4.1.1.tgz#3082a5b880ea4043816349f378b56c516be1a39b"
integrity sha512-s88xLzf1r81ICXLAVQVzaN6ZmX4A6U4z2nMbOwobxkLoIIfjVMBg7TeguTUXkKeXni795B6y5rnvDw7rxhAq9A==
dependencies:
decompress-tar "^4.1.0"
file-type "^6.1.0"
@@ -118,7 +103,6 @@ decompress-tarbz2@^4.0.0:
decompress-targz@^4.0.0:
version "4.1.1"
resolved "https://registry.yarnpkg.com/decompress-targz/-/decompress-targz-4.1.1.tgz#c09bc35c4d11f3de09f2d2da53e9de23e7ce1eee"
integrity sha512-4z81Znfr6chWnRDNfFNqLwPvm4db3WuZkqV+UgXQzSngG3CEKdBkw5jrv3axjjL96glyiiKjsxJG3X6WBZwX3w==
dependencies:
decompress-tar "^4.1.1"
file-type "^5.2.0"
@@ -127,7 +111,6 @@ decompress-targz@^4.0.0:
decompress-unzip@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/decompress-unzip/-/decompress-unzip-4.0.1.tgz#deaaccdfd14aeaf85578f733ae8210f9b4848f69"
integrity sha1-3qrM39FK6vhVePczroIQ+bSEj2k=
dependencies:
file-type "^3.8.0"
get-stream "^2.2.0"
@@ -137,7 +120,6 @@ decompress-unzip@^4.0.1:
decompress@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/decompress/-/decompress-4.2.0.tgz#7aedd85427e5a92dacfe55674a7c505e96d01f9d"
integrity sha1-eu3YVCflqS2s/lVnSnxQXpbQH50=
dependencies:
decompress-tar "^4.0.0"
decompress-tarbz2 "^4.0.0"
@@ -151,70 +133,58 @@ decompress@^4.2.0:
diagnostic-channel-publishers@0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/diagnostic-channel-publishers/-/diagnostic-channel-publishers-0.2.1.tgz#8e2d607a8b6d79fe880b548bc58cc6beb288c4f3"
integrity sha1-ji1geottef6IC1SLxYzGvrKIxPM=
diagnostic-channel@0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/diagnostic-channel/-/diagnostic-channel-0.2.0.tgz#cc99af9612c23fb1fff13612c72f2cbfaa8d5a17"
integrity sha1-zJmvlhLCP7H/8TYSxy8sv6qNWhc=
dependencies:
semver "^5.3.0"
end-of-stream@^1.0.0:
version "1.4.1"
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43"
integrity sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==
dependencies:
once "^1.4.0"
es6-promise@^4.0.3:
version "4.2.5"
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.5.tgz#da6d0d5692efb461e082c14817fe2427d8f5d054"
integrity sha512-n6wvpdE43VFtJq+lUDYDBFUwV8TZbuGXLV4D6wKafg13ldznKsyEvatubnmUe31zcvelSzOHF+XbaT+Bl9ObDg==
es6-promisify@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203"
integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=
dependencies:
es6-promise "^4.0.3"
eventemitter2@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-5.0.1.tgz#6197a095d5fb6b57e8942f6fd7eaad63a09c9452"
integrity sha1-YZegldX7a1folC9v1+qtY6CclFI=
fd-slicer@~1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e"
integrity sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=
dependencies:
pend "~1.2.0"
file-type@^3.8.0:
version "3.9.0"
resolved "https://registry.yarnpkg.com/file-type/-/file-type-3.9.0.tgz#257a078384d1db8087bc449d107d52a52672b9e9"
integrity sha1-JXoHg4TR24CHvESdEH1SpSZyuek=
file-type@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/file-type/-/file-type-5.2.0.tgz#2ddbea7c73ffe36368dfae49dc338c058c2b8ad6"
integrity sha1-LdvqfHP/42No365J3DOMBYwritY=
file-type@^6.1.0:
version "6.2.0"
resolved "https://registry.yarnpkg.com/file-type/-/file-type-6.2.0.tgz#e50cd75d356ffed4e306dc4f5bcf52a79903a919"
integrity sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg==
fs-constants@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==
get-stream@^2.2.0:
version "2.3.1"
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-2.3.1.tgz#5f38f93f346009666ee0150a054167f91bdd95de"
integrity sha1-Xzj5PzRgCWZu4BUKBUFn+Rvdld4=
dependencies:
object-assign "^4.0.1"
pinkie-promise "^2.0.0"
@@ -222,17 +192,14 @@ get-stream@^2.2.0:
graceful-fs@^4.1.10:
version "4.1.15"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00"
integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==
"graceful-readlink@>= 1.0.0":
version "1.0.1"
resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725"
integrity sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=
http-proxy-agent@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz#e4821beef5b2142a2026bd73926fe537631c5405"
integrity sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==
dependencies:
agent-base "4"
debug "3.1.0"
@@ -240,7 +207,6 @@ http-proxy-agent@^2.1.0:
https-proxy-agent@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz#51552970fa04d723e04c56d04178c3f92592bbc0"
integrity sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ==
dependencies:
agent-base "^4.1.0"
debug "^3.1.0"
@@ -248,115 +214,94 @@ https-proxy-agent@^2.2.1:
ieee754@^1.1.4:
version "1.1.12"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.12.tgz#50bf24e5b9c8bb98af4964c941cdb0918da7b60b"
integrity sha512-GguP+DRY+pJ3soyIiGPTvdiVXjZ+DbXOxGpXn3eMvNW4x4irjqXm4wHKscC+TfxSJ0yw/S1F24tqdMNsMZTiLA==
inherits@~2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
is-natural-number@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/is-natural-number/-/is-natural-number-4.0.1.tgz#ab9d76e1db4ced51e35de0c72ebecf09f734cde8"
integrity sha1-q5124dtM7VHjXeDHLr7PCfc0zeg=
is-stream@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ=
isarray@^1.0.0, isarray@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
make-dir@^1.0.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c"
integrity sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==
dependencies:
pify "^3.0.0"
minimist@0.0.8:
version "0.0.8"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=
mkdirp@^0.5.1:
version "0.5.1"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=
dependencies:
minimist "0.0.8"
ms@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
ms@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"
integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==
object-assign@^4.0.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
once@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
dependencies:
wrappy "1"
opener@^1.4.3:
version "1.5.1"
resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.1.tgz#6d2f0e77f1a0af0032aca716c2c1fbb8e7e8abed"
integrity sha512-goYSy5c2UXE4Ra1xixabeVh1guIX/ZV/YokJksb6q2lubWu6UbvPQ20p542/sFIll1nl8JnCyK9oBaOcCWXwvA==
os-tmpdir@~1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=
pend@~1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA=
pify@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw=
pify@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176"
integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=
pinkie-promise@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa"
integrity sha1-ITXW36ejWMBprJsXh3YogihFD/o=
dependencies:
pinkie "^2.0.0"
pinkie@^2.0.0:
version "2.0.4"
resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"
integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA=
process-nextick-args@~2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa"
integrity sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==
readable-stream@^2.3.0, readable-stream@^2.3.5:
version "2.3.6"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf"
integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==
dependencies:
core-util-is "~1.0.0"
inherits "~2.0.3"
@@ -369,19 +314,16 @@ readable-stream@^2.3.0, readable-stream@^2.3.5:
safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
version "5.1.2"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
seek-bzip@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/seek-bzip/-/seek-bzip-1.0.5.tgz#cfe917cb3d274bcffac792758af53173eb1fabdc"
integrity sha1-z+kXyz0nS8/6x5J1ivUxc+sfq9w=
dependencies:
commander "~2.8.1"
semver@^5.3.0:
version "5.6.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004"
integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==
"service-downloader@github:anthonydresser/service-downloader#0.1.5":
version "0.1.5"
@@ -397,21 +339,18 @@ semver@^5.3.0:
string_decoder@~1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
dependencies:
safe-buffer "~5.1.0"
strip-dirs@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/strip-dirs/-/strip-dirs-2.1.0.tgz#4987736264fc344cf20f6c34aca9d13d1d4ed6c5"
integrity sha512-JOCxOeKLm2CAS73y/U4ZeZPTkE+gNVCzKt7Eox84Iej1LT/2pTWYpZKJuxwQpvX1LiZb1xokNR7RLfuBAa7T3g==
dependencies:
is-natural-number "^4.0.1"
tar-stream@^1.5.2:
version "1.6.2"
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.6.2.tgz#8ea55dab37972253d9a9af90fdcd559ae435c555"
integrity sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==
dependencies:
bl "^1.0.0"
buffer-alloc "^1.2.0"
@@ -424,24 +363,20 @@ tar-stream@^1.5.2:
through@^2.3.6:
version "2.3.8"
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=
tmp@^0.0.33:
version "0.0.33"
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==
dependencies:
os-tmpdir "~1.0.2"
to-buffer@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/to-buffer/-/to-buffer-1.1.1.tgz#493bd48f62d7c43fcded313a03dcadb2e1213a80"
integrity sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==
unbzip2-stream@^1.0.9:
version "1.3.1"
resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.3.1.tgz#7854da51622a7e63624221196357803b552966a1"
integrity sha512-fIZnvdjblYs7Cru/xC6tCPVhz7JkYcVQQkePwMLyQELzYTds2Xn8QefPVnvdVhhZqubxNA1cASXEH5wcK0Bucw==
dependencies:
buffer "^3.0.1"
through "^2.3.6"
@@ -449,31 +384,26 @@ unbzip2-stream@^1.0.9:
util-deprecate@~1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
vscode-extension-telemetry@0.0.18:
version "0.0.18"
resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.0.18.tgz#602ba20d8c71453aa34533a291e7638f6e5c0327"
integrity sha512-Vw3Sr+dZwl+c6PlsUwrTtCOJkgrmvS3OUVDQGcmpXWAgq9xGq6as0K4pUx+aGqTjzLAESmWSrs6HlJm6J6Khcg==
dependencies:
applicationinsights "1.0.1"
vscode-jsonrpc@3.5.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-3.5.0.tgz#87239d9e166b2d7352245b8a813597804c1d63aa"
integrity sha1-hyOdnhZrLXNSJFuKgTWXgEwdY6o=
vscode-languageclient@3.5.1:
version "3.5.1"
resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-3.5.1.tgz#c78e582459c24e58f88020dfa34065e976186a98"
integrity sha512-GTQ+hSq/o4c/y6GYmyP9XNrVoIu0NFZ67KltSkqN+tO0eUNDIlrVNX+3DJzzyLhSsrctuGzuYWm3t87mNAcBmQ==
dependencies:
vscode-languageserver-protocol "3.5.1"
vscode-languageserver-protocol@3.5.1:
version "3.5.1"
resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.5.1.tgz#5144a3a9eeccbd83fe2745bd4ed75fad6cc45f0d"
integrity sha512-1fPDIwsAv1difCV+8daOrJEGunClNJWqnUHq/ncWrjhitKWXgGmRCjlwZ3gDUTt54yRcvXz1PXJDaRNvNH6pYA==
dependencies:
vscode-jsonrpc "3.5.0"
vscode-languageserver-types "3.5.0"
@@ -481,27 +411,22 @@ vscode-languageserver-protocol@3.5.1:
vscode-languageserver-types@3.5.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.5.0.tgz#e48d79962f0b8e02de955e3f524908e2b19c0374"
integrity sha1-5I15li8LjgLelV4/UkkI4rGcA3Q=
vscode-nls@^3.2.1:
version "3.2.5"
resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.5.tgz#25520c1955108036dec607c85e00a522f247f1a4"
integrity sha512-ITtoh3V4AkWXMmp3TB97vsMaHRgHhsSFPsUdzlueSL+dRZbSNTZeOmdQv60kjCV306ghPxhDeoNUEm3+EZMuyw==
wrappy@1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
xtend@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
integrity sha1-pcbVMr5lbiPbgg77lDofBJmNY68=
yauzl@^2.4.2:
version "2.10.0"
resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"
integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=
dependencies:
buffer-crc32 "~0.2.3"
fd-slicer "~1.1.0"
@@ -509,4 +434,3 @@ yauzl@^2.4.2:
zone.js@0.7.6:
version "0.7.6"
resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.7.6.tgz#fbbc39d3e0261d0986f1ba06306eb3aeb0d22009"
integrity sha1-+7w50+AmHQmG8boGMG6zrrDSIAk=

View File

@@ -18,7 +18,7 @@
"update-grammar": "node ../../build/npm/update-grammar.js Microsoft/vscode-mssql syntaxes/SQL.plist ./syntaxes/sql.tmLanguage.json"
},
"dependencies": {
"dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#0.2.10",
"dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#0.2.11",
"opener": "^1.4.3",
"service-downloader": "github:anthonydresser/service-downloader#0.1.5",
"vscode-extension-telemetry": "^0.0.15"

View File

@@ -1,6 +1,6 @@
{
"downloadUrl": "https://github.com/Microsoft/sqltoolsservice/releases/download/v{#version#}/microsoft.sqltools.servicelayer-{#fileName#}",
"version": "1.5.0-alpha.60",
"version": "1.5.0-alpha.65",
"downloadFileNames": {
"Windows_86": "win-x86-netcoreapp2.2.zip",
"Windows_64": "win-x64-netcoreapp2.2.zip",

View File

@@ -5,14 +5,12 @@
agent-base@4, agent-base@^4.1.0:
version "4.2.1"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9"
integrity sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==
dependencies:
es6-promisify "^5.0.0"
applicationinsights@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-1.0.1.tgz#53446b830fe8d5d619eee2a278b31d3d25030927"
integrity sha1-U0Rrgw/o1dYZ7uKieLMdPSUDCSc=
dependencies:
diagnostic-channel "0.2.0"
diagnostic-channel-publishers "0.2.1"
@@ -21,12 +19,10 @@ applicationinsights@1.0.1:
base64-js@0.0.8:
version "0.0.8"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-0.0.8.tgz#1101e9544f4a76b1bc3b26d452ca96d7a35e7978"
integrity sha1-EQHpVE9KdrG8OybUUsqW16NeeXg=
bl@^1.0.0:
version "1.2.2"
resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.2.tgz#a160911717103c07410cef63ef51b397c025af9c"
integrity sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==
dependencies:
readable-stream "^2.3.5"
safe-buffer "^5.1.1"
@@ -34,12 +30,10 @@ bl@^1.0.0:
buffer-alloc-unsafe@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0"
integrity sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==
buffer-alloc@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/buffer-alloc/-/buffer-alloc-1.2.0.tgz#890dd90d923a873e08e10e5fd51a57e5b7cce0ec"
integrity sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==
dependencies:
buffer-alloc-unsafe "^1.1.0"
buffer-fill "^1.0.0"
@@ -47,17 +41,14 @@ buffer-alloc@^1.2.0:
buffer-crc32@~0.2.3:
version "0.2.13"
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=
buffer-fill@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c"
integrity sha1-+PeLdniYiO858gXNY39o5wISKyw=
buffer@^3.0.1:
version "3.6.0"
resolved "https://registry.yarnpkg.com/buffer/-/buffer-3.6.0.tgz#a72c936f77b96bf52f5f7e7b467180628551defb"
integrity sha1-pyyTb3e5a/UvX357RnGAYoVR3vs=
dependencies:
base64-js "0.0.8"
ieee754 "^1.1.4"
@@ -66,39 +57,34 @@ buffer@^3.0.1:
commander@~2.8.1:
version "2.8.1"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.8.1.tgz#06be367febfda0c330aa1e2a072d3dc9762425d4"
integrity sha1-Br42f+v9oMMwqh4qBy09yXYkJdQ=
dependencies:
graceful-readlink ">= 1.0.0"
core-util-is@~1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
"dataprotocol-client@github:Microsoft/sqlops-dataprotocolclient#0.2.10":
version "0.2.10"
resolved "https://codeload.github.com/Microsoft/sqlops-dataprotocolclient/tar.gz/4de3f7caf0eba54159911b977ddb4f5d7c0a9ca8"
"dataprotocol-client@github:Microsoft/sqlops-dataprotocolclient#0.2.11":
version "0.2.11"
resolved "https://codeload.github.com/Microsoft/sqlops-dataprotocolclient/tar.gz/bc80d2226699d23f45a2ec26129cbcdee4781ca9"
dependencies:
vscode-languageclient "3.5.1"
debug@3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
dependencies:
ms "2.0.0"
debug@^3.1.0:
version "3.2.6"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
dependencies:
ms "^2.1.1"
decompress-tar@^4.0.0, decompress-tar@^4.1.0, decompress-tar@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/decompress-tar/-/decompress-tar-4.1.1.tgz#718cbd3fcb16209716e70a26b84e7ba4592e5af1"
integrity sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ==
dependencies:
file-type "^5.2.0"
is-stream "^1.1.0"
@@ -107,7 +93,6 @@ decompress-tar@^4.0.0, decompress-tar@^4.1.0, decompress-tar@^4.1.1:
decompress-tarbz2@^4.0.0:
version "4.1.1"
resolved "https://registry.yarnpkg.com/decompress-tarbz2/-/decompress-tarbz2-4.1.1.tgz#3082a5b880ea4043816349f378b56c516be1a39b"
integrity sha512-s88xLzf1r81ICXLAVQVzaN6ZmX4A6U4z2nMbOwobxkLoIIfjVMBg7TeguTUXkKeXni795B6y5rnvDw7rxhAq9A==
dependencies:
decompress-tar "^4.1.0"
file-type "^6.1.0"
@@ -118,7 +103,6 @@ decompress-tarbz2@^4.0.0:
decompress-targz@^4.0.0:
version "4.1.1"
resolved "https://registry.yarnpkg.com/decompress-targz/-/decompress-targz-4.1.1.tgz#c09bc35c4d11f3de09f2d2da53e9de23e7ce1eee"
integrity sha512-4z81Znfr6chWnRDNfFNqLwPvm4db3WuZkqV+UgXQzSngG3CEKdBkw5jrv3axjjL96glyiiKjsxJG3X6WBZwX3w==
dependencies:
decompress-tar "^4.1.1"
file-type "^5.2.0"
@@ -127,7 +111,6 @@ decompress-targz@^4.0.0:
decompress-unzip@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/decompress-unzip/-/decompress-unzip-4.0.1.tgz#deaaccdfd14aeaf85578f733ae8210f9b4848f69"
integrity sha1-3qrM39FK6vhVePczroIQ+bSEj2k=
dependencies:
file-type "^3.8.0"
get-stream "^2.2.0"
@@ -137,7 +120,6 @@ decompress-unzip@^4.0.1:
decompress@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/decompress/-/decompress-4.2.0.tgz#7aedd85427e5a92dacfe55674a7c505e96d01f9d"
integrity sha1-eu3YVCflqS2s/lVnSnxQXpbQH50=
dependencies:
decompress-tar "^4.0.0"
decompress-tarbz2 "^4.0.0"
@@ -151,70 +133,58 @@ decompress@^4.2.0:
diagnostic-channel-publishers@0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/diagnostic-channel-publishers/-/diagnostic-channel-publishers-0.2.1.tgz#8e2d607a8b6d79fe880b548bc58cc6beb288c4f3"
integrity sha1-ji1geottef6IC1SLxYzGvrKIxPM=
diagnostic-channel@0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/diagnostic-channel/-/diagnostic-channel-0.2.0.tgz#cc99af9612c23fb1fff13612c72f2cbfaa8d5a17"
integrity sha1-zJmvlhLCP7H/8TYSxy8sv6qNWhc=
dependencies:
semver "^5.3.0"
end-of-stream@^1.0.0:
version "1.4.1"
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43"
integrity sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==
dependencies:
once "^1.4.0"
es6-promise@^4.0.3:
version "4.2.5"
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.5.tgz#da6d0d5692efb461e082c14817fe2427d8f5d054"
integrity sha512-n6wvpdE43VFtJq+lUDYDBFUwV8TZbuGXLV4D6wKafg13ldznKsyEvatubnmUe31zcvelSzOHF+XbaT+Bl9ObDg==
es6-promisify@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203"
integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=
dependencies:
es6-promise "^4.0.3"
eventemitter2@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-5.0.1.tgz#6197a095d5fb6b57e8942f6fd7eaad63a09c9452"
integrity sha1-YZegldX7a1folC9v1+qtY6CclFI=
fd-slicer@~1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e"
integrity sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=
dependencies:
pend "~1.2.0"
file-type@^3.8.0:
version "3.9.0"
resolved "https://registry.yarnpkg.com/file-type/-/file-type-3.9.0.tgz#257a078384d1db8087bc449d107d52a52672b9e9"
integrity sha1-JXoHg4TR24CHvESdEH1SpSZyuek=
file-type@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/file-type/-/file-type-5.2.0.tgz#2ddbea7c73ffe36368dfae49dc338c058c2b8ad6"
integrity sha1-LdvqfHP/42No365J3DOMBYwritY=
file-type@^6.1.0:
version "6.2.0"
resolved "https://registry.yarnpkg.com/file-type/-/file-type-6.2.0.tgz#e50cd75d356ffed4e306dc4f5bcf52a79903a919"
integrity sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg==
fs-constants@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==
get-stream@^2.2.0:
version "2.3.1"
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-2.3.1.tgz#5f38f93f346009666ee0150a054167f91bdd95de"
integrity sha1-Xzj5PzRgCWZu4BUKBUFn+Rvdld4=
dependencies:
object-assign "^4.0.1"
pinkie-promise "^2.0.0"
@@ -222,17 +192,14 @@ get-stream@^2.2.0:
graceful-fs@^4.1.10:
version "4.1.15"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00"
integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==
"graceful-readlink@>= 1.0.0":
version "1.0.1"
resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725"
integrity sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=
http-proxy-agent@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz#e4821beef5b2142a2026bd73926fe537631c5405"
integrity sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==
dependencies:
agent-base "4"
debug "3.1.0"
@@ -240,7 +207,6 @@ http-proxy-agent@^2.1.0:
https-proxy-agent@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz#51552970fa04d723e04c56d04178c3f92592bbc0"
integrity sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ==
dependencies:
agent-base "^4.1.0"
debug "^3.1.0"
@@ -248,115 +214,94 @@ https-proxy-agent@^2.2.1:
ieee754@^1.1.4:
version "1.1.12"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.12.tgz#50bf24e5b9c8bb98af4964c941cdb0918da7b60b"
integrity sha512-GguP+DRY+pJ3soyIiGPTvdiVXjZ+DbXOxGpXn3eMvNW4x4irjqXm4wHKscC+TfxSJ0yw/S1F24tqdMNsMZTiLA==
inherits@~2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
is-natural-number@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/is-natural-number/-/is-natural-number-4.0.1.tgz#ab9d76e1db4ced51e35de0c72ebecf09f734cde8"
integrity sha1-q5124dtM7VHjXeDHLr7PCfc0zeg=
is-stream@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ=
isarray@^1.0.0, isarray@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
make-dir@^1.0.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c"
integrity sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==
dependencies:
pify "^3.0.0"
minimist@0.0.8:
version "0.0.8"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=
mkdirp@^0.5.1:
version "0.5.1"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=
dependencies:
minimist "0.0.8"
ms@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
ms@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"
integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==
object-assign@^4.0.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
once@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
dependencies:
wrappy "1"
opener@^1.4.3:
version "1.5.1"
resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.1.tgz#6d2f0e77f1a0af0032aca716c2c1fbb8e7e8abed"
integrity sha512-goYSy5c2UXE4Ra1xixabeVh1guIX/ZV/YokJksb6q2lubWu6UbvPQ20p542/sFIll1nl8JnCyK9oBaOcCWXwvA==
os-tmpdir@~1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=
pend@~1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA=
pify@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw=
pify@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176"
integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=
pinkie-promise@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa"
integrity sha1-ITXW36ejWMBprJsXh3YogihFD/o=
dependencies:
pinkie "^2.0.0"
pinkie@^2.0.0:
version "2.0.4"
resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"
integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA=
process-nextick-args@~2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa"
integrity sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==
readable-stream@^2.3.0, readable-stream@^2.3.5:
version "2.3.6"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf"
integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==
dependencies:
core-util-is "~1.0.0"
inherits "~2.0.3"
@@ -369,19 +314,16 @@ readable-stream@^2.3.0, readable-stream@^2.3.5:
safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
version "5.1.2"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
seek-bzip@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/seek-bzip/-/seek-bzip-1.0.5.tgz#cfe917cb3d274bcffac792758af53173eb1fabdc"
integrity sha1-z+kXyz0nS8/6x5J1ivUxc+sfq9w=
dependencies:
commander "~2.8.1"
semver@^5.3.0:
version "5.6.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004"
integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==
"service-downloader@github:anthonydresser/service-downloader#0.1.5":
version "0.1.5"
@@ -397,21 +339,18 @@ semver@^5.3.0:
string_decoder@~1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
dependencies:
safe-buffer "~5.1.0"
strip-dirs@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/strip-dirs/-/strip-dirs-2.1.0.tgz#4987736264fc344cf20f6c34aca9d13d1d4ed6c5"
integrity sha512-JOCxOeKLm2CAS73y/U4ZeZPTkE+gNVCzKt7Eox84Iej1LT/2pTWYpZKJuxwQpvX1LiZb1xokNR7RLfuBAa7T3g==
dependencies:
is-natural-number "^4.0.1"
tar-stream@^1.5.2:
version "1.6.2"
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.6.2.tgz#8ea55dab37972253d9a9af90fdcd559ae435c555"
integrity sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==
dependencies:
bl "^1.0.0"
buffer-alloc "^1.2.0"
@@ -424,24 +363,20 @@ tar-stream@^1.5.2:
through@^2.3.6:
version "2.3.8"
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=
tmp@^0.0.33:
version "0.0.33"
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==
dependencies:
os-tmpdir "~1.0.2"
to-buffer@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/to-buffer/-/to-buffer-1.1.1.tgz#493bd48f62d7c43fcded313a03dcadb2e1213a80"
integrity sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==
unbzip2-stream@^1.0.9:
version "1.3.1"
resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.3.1.tgz#7854da51622a7e63624221196357803b552966a1"
integrity sha512-fIZnvdjblYs7Cru/xC6tCPVhz7JkYcVQQkePwMLyQELzYTds2Xn8QefPVnvdVhhZqubxNA1cASXEH5wcK0Bucw==
dependencies:
buffer "^3.0.1"
through "^2.3.6"
@@ -449,31 +384,26 @@ unbzip2-stream@^1.0.9:
util-deprecate@~1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
vscode-extension-telemetry@^0.0.15:
version "0.0.15"
resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.0.15.tgz#685c32f3b67e8fb85ba689c1d7f88ff90ff87856"
integrity sha512-Yf6dL9r2x2GISI1xh22XsAaydSTQG/4aBitu8sGBwGr42n2TyOsIXGtXSDgqQBNZgYD6+P1EHqrrzetn9ekWTQ==
dependencies:
applicationinsights "1.0.1"
vscode-jsonrpc@3.5.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-3.5.0.tgz#87239d9e166b2d7352245b8a813597804c1d63aa"
integrity sha1-hyOdnhZrLXNSJFuKgTWXgEwdY6o=
vscode-languageclient@3.5.1:
version "3.5.1"
resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-3.5.1.tgz#c78e582459c24e58f88020dfa34065e976186a98"
integrity sha512-GTQ+hSq/o4c/y6GYmyP9XNrVoIu0NFZ67KltSkqN+tO0eUNDIlrVNX+3DJzzyLhSsrctuGzuYWm3t87mNAcBmQ==
dependencies:
vscode-languageserver-protocol "3.5.1"
vscode-languageserver-protocol@3.5.1:
version "3.5.1"
resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.5.1.tgz#5144a3a9eeccbd83fe2745bd4ed75fad6cc45f0d"
integrity sha512-1fPDIwsAv1difCV+8daOrJEGunClNJWqnUHq/ncWrjhitKWXgGmRCjlwZ3gDUTt54yRcvXz1PXJDaRNvNH6pYA==
dependencies:
vscode-jsonrpc "3.5.0"
vscode-languageserver-types "3.5.0"
@@ -481,22 +411,18 @@ vscode-languageserver-protocol@3.5.1:
vscode-languageserver-types@3.5.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.5.0.tgz#e48d79962f0b8e02de955e3f524908e2b19c0374"
integrity sha1-5I15li8LjgLelV4/UkkI4rGcA3Q=
wrappy@1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
xtend@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
integrity sha1-pcbVMr5lbiPbgg77lDofBJmNY68=
yauzl@^2.4.2:
version "2.10.0"
resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"
integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=
dependencies:
buffer-crc32 "~0.2.3"
fd-slicer "~1.1.0"
@@ -504,4 +430,3 @@ yauzl@^2.4.2:
zone.js@0.7.6:
version "0.7.6"
resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.7.6.tgz#fbbc39d3e0261d0986f1ba06306eb3aeb0d22009"
integrity sha1-+7w50+AmHQmG8boGMG6zrrDSIAk=

View File

@@ -0,0 +1,17 @@
# Notebook extension for Azure Data Studio
Welcome to the Notebook extension for Azure Data Studio! This extension supports core notebook functionality including configuration settings, actions such as New / Open Notebook, and more.
## Code of Conduct
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
## Privacy Statement
The [Microsoft Enterprise and Developer Privacy Statement](https://privacy.microsoft.com/en-us/privacystatement) describes the privacy statement of this software.
## License
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the [Source EULA](https://raw.githubusercontent.com/Microsoft/azuredatastudio/master/LICENSE.txt).

View File

@@ -0,0 +1,76 @@
{
"name": "notebook",
"displayName": "%displayName%",
"description": "%description%",
"version": "0.1.0",
"publisher": "Microsoft",
"engines": {
"vscode": "*",
"sqlops": "*"
},
"main": "./out/extension",
"activationEvents": [
"*"
],
"contributes": {
"configuration": {
"type": "object",
"title": "%notebook.configuration.title%",
"properties": {
"notebook.enabled": {
"type": "boolean",
"default": true,
"description": "%notebook.enabled.description%"
},
"notebook.pythonPath": {
"type": "string",
"default": "",
"description": "%notebook.pythonPath.description%"
}
}
},
"commands": [
{
"command": "notebook.command.new",
"title": "%notebook.command.new%",
"icon": {
"dark": "resources/dark/new_notebook_inverse.svg",
"light": "resources/light/new_notebook.svg"
}
},
{
"command": "notebook.command.open",
"title": "%notebook.command.open%",
"icon": {
"dark": "resources/dark/open_notebook_inverse.svg",
"light": "resources/light/open_notebook.svg"
}
}
],
"menus": {
"commandPalette": [
{
"command": "notebook.command.new",
"when": "config.notebook.enabled"
},
{
"command": "notebook.command.open",
"when": "config.notebook.enabled"
}
]
},
"keybindings": [
{
"command": "notebook.command.new",
"key": "Ctrl+Shift+N",
"when": "config.notebook.enabled"
}
]
},
"dependencies": {
"vscode-nls": "^4.0.0"
},
"devDependencies": {
"@types/node": "8.0.33"
}
}

View File

@@ -0,0 +1,9 @@
{
"displayName": "Notebook Core Extensions",
"description": "Defines the Data-procotol based Notebook contribution and many Notebook commands and contributions.",
"notebook.configuration.title": "Notebook configuration",
"notebook.enabled.description": "Enable viewing notebook files using built-in notebook editor.",
"notebook.pythonPath.description": "Local path to python installation used by Notebooks.",
"notebook.command.new": "New Notebook",
"notebook.command.open": "Open Notebook"
}

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#fff;}.cls-2{fill:#388a34;}</style></defs><title>new_notebook_inverse</title><path class="cls-1" d="M11.87,1.24V.33H9.13A3.78,3.78,0,0,0,7.92.52a3.48,3.48,0,0,0-1.07.58A3.6,3.6,0,0,0,5.78.52,3.78,3.78,0,0,0,4.57.33H1.83v.91H0V13.1H9.67v-.91H7a4,4,0,0,1,.47-.39A2.39,2.39,0,0,1,8,11.52a2.2,2.2,0,0,1,.53-.18,2.93,2.93,0,0,1,.61-.06h2.74V2.15h.91V9h.91V1.24Zm-9.13,0H4.57a3,3,0,0,1,1,.17,2.58,2.58,0,0,1,.85.49v8.93a3.94,3.94,0,0,0-.88-.35,3.73,3.73,0,0,0-.94-.12H2.74Zm-1.82,11v-10h.91v9.13H4.57a2.93,2.93,0,0,1,.61.06,2.55,2.55,0,0,1,.53.18,2.68,2.68,0,0,1,.49.28,3.29,3.29,0,0,1,.46.39Zm8.21-1.83a3.73,3.73,0,0,0-.94.12,4.22,4.22,0,0,0-.89.35V1.9a2.74,2.74,0,0,1,.86-.49,2.91,2.91,0,0,1,1-.17H11v9.12ZM12.87,10v2.2h-2.2v.91h3V10Z"/><polygon class="cls-2" points="16 12.19 16 13.13 13.8 13.13 13.8 15.33 12.87 15.33 12.87 13.13 10.67 13.13 10.67 12.19 12.87 12.19 12.87 9.99 13.8 9.99 13.8 12.19 16 12.19"/><path class="cls-2" d="M13.8,12.19V10h-.93v2.2h-2.2v.94h2.2v2.2h.93v-2.2H16v-.94Z"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#fff;}.cls-2{fill:#0095d7;}</style></defs><title>open_notebook_inverse</title><path class="cls-1" d="M12.55,4.21l-.08-.11h-.56l-.69.06a1.54,1.54,0,0,0-.23.29v8.69H9.18a3.32,3.32,0,0,0-.93.13,3.34,3.34,0,0,0-.87.34V4.76a2.88,2.88,0,0,1,.43-.31A5.58,5.58,0,0,1,8.29,3.3a2.63,2.63,0,0,0-.3.09A3.62,3.62,0,0,0,6.93,4a3.68,3.68,0,0,0-1.07-.57A3.58,3.58,0,0,0,4.67,3.2H2v.9H.15V15.85H13.72V5.48ZM2.86,4.1H4.67a2.61,2.61,0,0,1,1,.17,2.32,2.32,0,0,1,.86.49v8.85a3.27,3.27,0,0,0-.88-.34,3.22,3.22,0,0,0-.93-.13H2.86ZM1,15V5H2v9H4.67a3.94,3.94,0,0,1,.61.06,3.2,3.2,0,0,1,.52.18,4.19,4.19,0,0,1,.49.29,2.28,2.28,0,0,1,.45.39ZM12.8,15H7.11a2.7,2.7,0,0,1,.47-.39A2.83,2.83,0,0,1,8,14.28a3.42,3.42,0,0,1,.54-.18A3.81,3.81,0,0,1,9.18,14h2.73V5h.89Z"/><polygon class="cls-2" points="13.2 3.56 13.2 3.58 13.19 3.57 13.2 3.56"/><path class="cls-2" d="M13.19,3.57h0v0Z"/><polygon class="cls-2" points="13.2 3.56 13.2 3.58 13.19 3.57 13.2 3.56"/><polygon class="cls-2" points="14.21 1.65 14.19 1.65 14.19 1.63 14.21 1.65"/><path class="cls-2" d="M15.91,2.1,14.2,3.81l-.38.38-.62-.61v0l1-1H12.79a3.35,3.35,0,0,0-1.09.26h0a3.94,3.94,0,0,0-.86.52l-.24.21s0,0,0,0a3.3,3.3,0,0,0-.51.67,3.1,3.1,0,0,0-.26.47A3.41,3.41,0,0,0,9.5,6.11H8.6a4.68,4.68,0,0,1,.16-1.19A4.74,4.74,0,0,1,9,4.26a2.21,2.21,0,0,1,.2-.41,4.66,4.66,0,0,1,.36-.51c.1-.13.22-.26.34-.39a4.14,4.14,0,0,1,.66-.53,1.19,1.19,0,0,1,.23-.16,2.79,2.79,0,0,1,.34-.18l.31-.13.42-.14a4.32,4.32,0,0,1,1.19-.16h1.15l-1-1L13.82,0Z"/></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#388a34;}</style></defs><title>new_notebook</title><path d="M11.86,1.24V.33H9.13A3.78,3.78,0,0,0,7.91.52a3.48,3.48,0,0,0-1.07.58A3.6,3.6,0,0,0,5.78.52,3.78,3.78,0,0,0,4.57.33H1.83v.91H0V13.1H9.66v-.91H7a4,4,0,0,1,.47-.39A2.39,2.39,0,0,1,8,11.52a2.2,2.2,0,0,1,.53-.18,2.93,2.93,0,0,1,.61-.06h2.74V2.15h.91V9h.91V1.24Zm-9.13,0H4.57a3,3,0,0,1,1,.17,2.58,2.58,0,0,1,.85.49v8.93a3.94,3.94,0,0,0-.88-.35,3.73,3.73,0,0,0-.94-.12H2.73Zm-1.82,11v-10h.91v9.13H4.57a2.93,2.93,0,0,1,.61.06,2.55,2.55,0,0,1,.53.18,2.68,2.68,0,0,1,.49.28,3.29,3.29,0,0,1,.46.39Zm8.21-1.83a3.73,3.73,0,0,0-.94.12,4.22,4.22,0,0,0-.89.35V1.9a2.74,2.74,0,0,1,.86-.49,2.91,2.91,0,0,1,1-.17h1.82v9.12ZM12.86,10v2.2h-2.2v.91h3V10Z"/><polygon class="cls-1" points="15.99 12.19 15.99 13.13 13.79 13.13 13.79 15.33 12.87 15.33 12.87 13.13 10.66 13.13 10.66 12.19 12.87 12.19 12.87 9.99 13.79 9.99 13.79 12.19 15.99 12.19"/><path class="cls-1" d="M13.79,12.19V10h-.93v2.2h-2.2v.94h2.2v2.2h.93v-2.2H16v-.94Z"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#00539c;}</style></defs><title>open_notebook</title><path d="M12.4,4.21l-.08-.11h-.56l-.69.06a1.54,1.54,0,0,0-.23.29v8.69H9a3.32,3.32,0,0,0-.93.13,3.34,3.34,0,0,0-.87.34V4.76a2.88,2.88,0,0,1,.43-.31A5.58,5.58,0,0,1,8.14,3.3a2.63,2.63,0,0,0-.3.09A3.62,3.62,0,0,0,6.78,4a3.68,3.68,0,0,0-1.07-.57A3.58,3.58,0,0,0,4.52,3.2H1.81v.9H0V15.85H13.57V5.48ZM2.71,4.1H4.52a2.61,2.61,0,0,1,1,.17,2.32,2.32,0,0,1,.86.49v8.85a3.27,3.27,0,0,0-.88-.34,3.22,3.22,0,0,0-.93-.13H2.71ZM.9,15V5h.91v9H4.52a3.94,3.94,0,0,1,.61.06,3.2,3.2,0,0,1,.52.18,4.19,4.19,0,0,1,.49.29,2.28,2.28,0,0,1,.45.39Zm11.75,0H7a2.7,2.7,0,0,1,.47-.39,2.83,2.83,0,0,1,.47-.29,3.42,3.42,0,0,1,.54-.18A3.81,3.81,0,0,1,9,14h2.73V5h.89Z"/><polygon class="cls-1" points="13.05 3.56 13.05 3.58 13.04 3.57 13.05 3.56"/><path class="cls-1" d="M13,3.57h0v0Z"/><polygon class="cls-1" points="13.05 3.56 13.05 3.58 13.04 3.57 13.05 3.56"/><polygon class="cls-1" points="14.06 1.65 14.04 1.65 14.04 1.63 14.06 1.65"/><path class="cls-1" d="M15.76,2.1,14,3.81l-.38.38L13,3.58v0l1-1H12.64a3.35,3.35,0,0,0-1.09.26h0a3.94,3.94,0,0,0-.86.52l-.24.21s0,0,0,0a3.3,3.3,0,0,0-.51.67,3.1,3.1,0,0,0-.26.47,3.41,3.41,0,0,0-.27,1.39h-.9a4.68,4.68,0,0,1,.16-1.19,4.74,4.74,0,0,1,.25-.66,2.21,2.21,0,0,1,.2-.41,4.66,4.66,0,0,1,.36-.51c.1-.13.22-.26.34-.39a4.14,4.14,0,0,1,.66-.53,1.19,1.19,0,0,1,.23-.16A2.79,2.79,0,0,1,11,2.08l.31-.13.42-.14a4.32,4.32,0,0,1,1.19-.16h1.15l-1-1L13.67,0Z"/></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,50 @@
/*---------------------------------------------------------------------------------------------
* 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 vscode from 'vscode';
import * as sqlops from 'sqlops';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
let counter = 0;
export function activate(extensionContext: vscode.ExtensionContext) {
extensionContext.subscriptions.push(vscode.commands.registerCommand('notebook.command.new', () => {
let title = `Untitled-${counter++}`;
let untitledUri = vscode.Uri.parse(`untitled:${title}`);
sqlops.nb.showNotebookDocument(untitledUri).then(success => {
}, (err: Error) => {
vscode.window.showErrorMessage(err.message);
});
}));
extensionContext.subscriptions.push(vscode.commands.registerCommand('notebook.command.open', () => {
openNotebook();
}));
}
async function openNotebook(): Promise<void> {
try {
let filter = {};
// TODO support querying valid notebook file types
filter[localize('notebookFiles', 'Notebooks')] = ['ipynb'];
let file = await vscode.window.showOpenDialog({
filters: filter
});
if (file) {
let doc = await vscode.workspace.openTextDocument(file[0]);
vscode.window.showTextDocument(doc);
}
} catch (err) {
vscode.window.showErrorMessage(err);
}
}
// this method is called when your extension is deactivated
export function deactivate() {
}

View File

@@ -0,0 +1,9 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/// <reference path='../../../../src/sql/sqlops.d.ts'/>
/// <reference path='../../../../src/sql/sqlops.proposed.d.ts'/>
/// <reference path='../../../../src/vs/vscode.d.ts'/>
/// <reference types='@types/node'/>

View File

@@ -0,0 +1,22 @@
{
"compileOnSave": true,
"compilerOptions": {
"module": "commonjs",
"target": "es6",
"outDir": "./out",
"lib": [
"es6", "es2015.promise"
],
"typeRoots": [
"./node_modules/@types"
],
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"moduleResolution": "node",
"declaration": true
},
"exclude": [
"node_modules"
]
}

View File

@@ -0,0 +1,13 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@types/node@8.0.33":
version "8.0.33"
resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.33.tgz#1126e94374014e54478092830704f6ea89df04cd"
integrity sha512-vmCdO8Bm1ExT+FWfC9sd9r4jwqM7o97gGy2WBshkkXbf/2nLAJQUrZfIhw27yVOtLUev6kSZc4cav/46KbDd8A==
vscode-nls@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.0.0.tgz#4001c8a6caba5cedb23a9c5ce1090395c0e44002"
integrity sha512-qCfdzcH+0LgQnBpZA53bA32kzp9rpq/f66Som577ObeuDlFIrtbEJ+A/+CCxjIh4G8dpJYNCKIsxpRAHIfsbNw==

View File

@@ -2,7 +2,7 @@
"name": "profiler",
"displayName": "SQL Server Profiler",
"description": "SQL Server Profiler for Azure Data Studio",
"version": "0.5.1",
"version": "0.6.0",
"publisher": "Microsoft",
"preview": true,
"license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/master/LICENSE.txt",

View File

@@ -1,6 +1,6 @@
{
"name": "azuredatastudio",
"version": "1.3.6",
"version": "1.4.2",
"distro": "8c3e97e3425cc9814496472ab73e076de2ba99ee",
"author": {
"name": "Microsoft Corporation"
@@ -23,7 +23,8 @@
"smoketest": "cd test/smoke && node test/index.js",
"monaco-compile-check": "tsc -p src/tsconfig.monaco.json --noEmit",
"download-builtin-extensions": "node build/lib/builtInExtensions.js",
"check-monaco-editor-compilation": "tsc -p src/tsconfig.monaco.json --noEmit"
"check-monaco-editor-compilation": "tsc -p src/tsconfig.monaco.json --noEmit",
"tslint": "node node_modules/tslint/bin/tslint -c tslint-gci.json -p src/tsconfig.json"
},
"dependencies": {
"@angular/animations": "~4.1.3",
@@ -90,6 +91,7 @@
"@types/mocha": "2.2.39",
"@types/sanitize-html": "^1.18.2",
"@types/semver": "5.3.30",
"@types/should": "^13.0.0",
"@types/sinon": "1.16.34",
"@types/winreg": "^1.2.30",
"asar": "^0.14.0",
@@ -148,9 +150,12 @@
"queue": "3.0.6",
"remap-istanbul": "^0.6.4",
"rimraf": "^2.2.8",
"should": "^13.2.3",
"sinon": "^1.17.2",
"source-map": "^0.4.4",
"temp-write": "^3.4.0",
"tslint": "^5.9.1",
"tslint-microsoft-contrib": "^6.0.0",
"typemoq": "^0.3.2",
"typescript": "2.9.2",
"typescript-formatter": "7.1.0",

View File

@@ -42,7 +42,8 @@
"Microsoft.server-report",
"Microsoft.sql-vnext",
"Microsoft.whoisactive",
"Redgate.sql-search"
"Redgate.sql-search",
"IDERA.sqldm-performance-insights"
],
"extensionsGallery": {
"serviceUrl": "https://sqlopsextensions.blob.core.windows.net/marketplace/v1/extensionsGallery.json"

File diff suppressed because it is too large Load Diff

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