Compare commits

...

87 Commits

Author SHA1 Message Date
Karl Burtram
2acd37a1b1 distro (#10509) (#10513)
Co-authored-by: Anthony Dresser <andresse@microsoft.com>
2020-05-19 16:07:13 -07:00
Karl Burtram
85786b48c3 fix styles to apply to error message correctly (#10505) (#10512)
Co-authored-by: Anthony Dresser <andresse@microsoft.com>
2020-05-19 15:44:41 -07:00
Maddy
acd6257bae set ignoreOverrides false for ipynb and sql files (#10499) (#10502)
* set ignoreOverrides false for ipynb and sql files

* toLowerCase

* null checks added
2020-05-19 13:29:46 -07:00
Maddy
f007d707b6 open viewlet on showPreview (#10474) (#10497)
* open viewlet on shoePreview

* on showPreview reveal explorer
2020-05-19 12:55:28 -07:00
Maddy
b118b4bc7a vbump (#10423) (#10498) 2020-05-19 12:45:23 -07:00
Amir Omidi
f3edece70b Fixes the DE icons (#10478) (#10481)
* Fixes the DE icons

* Fixes the commands
2020-05-18 15:04:10 -07:00
Charles Gagnon
4de376c357 Fix sql language colorization (#10472) 2020-05-18 15:03:49 -07:00
Karl Burtram
0753b63ad0 Fix extension manager single-click (#10473) (#10475)
* Fix extension manager single-click

* Port over additional change to fix same issue in git viewlet

* Remove unneeded param to match vscode source
2020-05-18 10:38:24 -07:00
Alan Ren
0523190fbb fix explorer widget dark theme (#10454)
* force use inherit colors

* comments
2020-05-16 10:29:05 -07:00
Anthony Dresser
de994972db modify git edits to make webpack not be stupid (#10442) (#10452) 2020-05-16 10:28:34 -07:00
Charles Gagnon
3b06473c49 remove config update (#10440) (#10444)
(cherry picked from commit ef4ab4a59f)
2020-05-16 10:26:20 -07:00
Charles Gagnon
bb75143282 Fix link styling for text components (#10449) (#10460)
(cherry picked from commit 0f10f44755)
2020-05-16 10:22:32 -07:00
Chris LaFreniere
3857f11dc9 Notebook model ready sooner (#10420) (#10441) 2020-05-15 13:17:35 -07:00
Karl Burtram
0918d93a18 Properly initialize scenario recommendations (#10434) (#10437) 2020-05-15 11:40:44 -07:00
Karl Burtram
25d96d041e clear buffer when a batch is completed (#10382) (#10436)
Co-authored-by: Anthony Dresser <andresse@microsoft.com>
2020-05-15 11:37:53 -07:00
Karl Burtram
0dc88501cf Turn-off file extension recommendations (#10431) (#10433) 2020-05-15 11:07:51 -07:00
Amir Omidi
bac7eccbaf Replace the ID of the generated connection profile with the one through edit data (#10413) (#10432) 2020-05-15 11:02:29 -07:00
Anthony Dresser
585d609ebb ensure we can still write version infomation if we don't recompile (#10365) (#10401) 2020-05-15 08:41:33 -07:00
Anthony Dresser
168385b6f1 Remove web smoke tests (#10380) (#10402)
* remove smoke tests

* use run_tests
2020-05-15 08:41:10 -07:00
Charles Gagnon
eb4612100d Fix message branding VS Code -> ADS (#10407) (#10410)
* Fix message branding VS Code -> ADS

* Add edit comment

(cherry picked from commit e95650da89)
2020-05-15 08:40:36 -07:00
Alan Ren
d89ce8f9ec create a new connection (#10412) (#10418) 2020-05-14 17:45:56 -07:00
Alan Ren
d84dd31491 set context (#10391) (#10399) 2020-05-14 16:29:10 -07:00
Alan Ren
b6632547a2 use listdatabases for sqlondemand (#10398) (#10408)
* use listdatabases for sqlondemand

* comments

* use enum
2020-05-14 16:24:36 -07:00
Cory Rivera
79669f073c Uncheck unselected radio buttons in Configure Python wizard. (#10344) (#10361) 2020-05-14 10:43:00 -07:00
Charles Gagnon
e078e3bc48 Enabling Script as Create for SqlOnDemand (#10362) (#10364)
* Update scriptingUtils.ts

* Update scripting.contribution.ts

(cherry picked from commit ab374e362a)

Co-authored-by: alljamziMicrosoft <65255541+alljamziMicrosoft@users.noreply.github.com>
2020-05-13 23:15:49 -07:00
Anthony Dresser
72d035be98 Remove request promise for request-light (#10346) (#10356)
* remove request promise for request-light

* make sure data is a string not a json

* add configure call

* remove unused reference

* more fixes

* Move configure calls

* clean up

* fix name

* move test deps as dev

Co-authored-by: chgagnon <chgagnon@microsoft.com>

Co-authored-by: chgagnon <chgagnon@microsoft.com>
2020-05-13 13:39:27 -07:00
Maddy
f6e7b56946 opening as text document launches json (#10323) 2020-05-08 23:28:28 -07:00
Charles Gagnon
2893659983 Hyperlink Component improvements (#10330)
* Hyperlink Component improvements

* Remove hyperlink CSS styles
2020-05-08 16:49:07 -07:00
Amir Omidi
f220f6486a Stops caching the AAD token (#10251) 2020-05-08 16:46:18 -07:00
Cory Rivera
f94a9d0d58 Update Configure Python dialog to allow packages to be installed for specific kernels. (#10286) 2020-05-08 16:02:59 -07:00
Charles Gagnon
9bcd7cdd80 Fix tabbed panel not updating correctly when layout updated (#10328)
* Fix tabbed panel not updating correctly when layout updated

* Add comment
2020-05-08 15:56:59 -07:00
Chris LaFreniere
5e1978e29f Ensure ipynb is associated with editor (#10055) 2020-05-08 14:50:48 -07:00
Chris LaFreniere
e4614582cd Cleanup nbformat, more common tests (#10311) 2020-05-08 14:50:24 -07:00
Chris LaFreniere
0fbc3d1cb6 Get rid of red herrings (#10309) 2020-05-08 14:49:49 -07:00
Alan Ren
3a70bea70d fix button column accessibility issue (#10307)
* fix accessibility issue

* comment
2020-05-08 14:24:03 -07:00
Kim Santiago
ea84e60fa0 Add a few dacpac extension tests (#10322)
* add controller test

* Add a few wizard tests

* Addressing comment

* fix compile error
2020-05-08 14:04:11 -07:00
Kim Santiago
10ac5fdfab Revert "Dacpac extension tests (#10305)" (#10321)
This reverts commit 1c621da4c7.
2020-05-08 10:32:22 -07:00
Kim Santiago
1c621da4c7 Dacpac extension tests (#10305)
* add controller test

* Add a few wizard tests

* Addressing comment
2020-05-08 10:11:51 -07:00
Charles Gagnon
6e5fc9c495 Add ModelView method SetItemLayout (#10306)
* Add ModelView method SetItemLayout

* Remove extra line break
2020-05-08 08:38:36 -07:00
Anthony Dresser
e3daec38c6 Merge branch 'ads-master-vscode-2020-05-08T03-58-31' 2020-05-08 00:58:57 -07:00
Anthony Dresser
11636f8fc3 remove crash reporting 2020-05-07 22:34:46 -07:00
Anthony Dresser
8c08d5117b revert workflow changes 2020-05-07 21:02:48 -07:00
ADS Merger
fa62ec1f34 Merge from vscode 7653d836944892f83ce9e1f95c1204bafa1aec31 2020-05-08 03:58:34 +00:00
Chris LaFreniere
dac1970c43 Notebooks: Refactor Empty Session Classes (#10265)
* Cleanup empty sessionmanager

* Cleanup

* PR Feedback import noKernel from notebookActions
2020-05-07 16:20:14 -07:00
Alan Ren
e27a57715c set provider (#10302) 2020-05-07 13:46:04 -07:00
Amir Omidi
e6ca724571 Add new error message to console (#10303) 2020-05-07 13:28:45 -07:00
Vladimir Chernov
57d2ceec9d mssql sqlAssessmentService (#10299)
mssql sqlAssessmentService
2020-05-07 21:35:44 +03:00
Alan Ren
d87855cf1f force the isDirty flag to always be false (#10295) 2020-05-07 10:08:22 -07:00
Jeff Trimmer
1656a9c285 upgrade the STS version (#10289) 2020-05-07 07:12:07 -07:00
Aasim Khan
89a845d994 -Changed from AutoFit to DataFit for better visibility of cell content (#10278) 2020-05-06 20:41:50 -07:00
Benjin Dubishar
80901c9a7b Swapping vscode calls for ApiWrapper for testability (#10267)
* swapping vscode calls for apiwrapper for testability

* Adding mainController tests

* Adding unit tests for input validation

* Adding project controller tests, reorganizing error handling

* Removing commented-out code
2020-05-06 14:16:27 -07:00
Alan Ren
0ace033a6f table based explorer widget (#10279)
* bump sts

* extend widget container

* remove title

* wip

* refactoring

* Revert "extend widget container"

* showTitle option

* fix properties widget error

* icon column

* icon and button columns

* use textwithicon column

* icon

* refactor and filter

* context menu

* refactor

* tests

* fix hygiene

* tests

* comments
2020-05-06 13:52:20 -07:00
Anthony Dresser
df5df38a55 Query Runner Tests (#10252)
* rework some code and write an inital test

* fix strict

* add more to standard test

* add to existing workflow test

* fix tests

* simplify the code

* add more tests

* remove bad import

* fix compile

* fix timestampiong
2020-05-06 13:38:12 -07:00
rajeshka
4199cec393 Add Cell after current active cell (#10203)
* Add Cell after current active cell

* fixed error

* more fixes

* Addressed PR

* merged with master

* Fixed the tests and code

* removed try catch and modified the test to check the method does not throw.

Co-authored-by: Rajesh Kamath <rajkashop@hotmail.com>
2020-05-06 11:42:01 -07:00
Benjin Dubishar
9b296c9f42 Organizes context menus for database projects tree (#10266)
* reoranizing existing context menu commands

* adding build/deploy/import stubs

* Update error message

* adding for schemaCompare
2020-05-06 11:31:17 -07:00
Anthony Dresser
dbb40d820c Merge branch 'ads-master-vscode-2020-05-06T02-35-47' 2020-05-05 23:21:01 -07:00
Aasim Khan
2ca6e7ae64 Removed PG connections and the refresh button from the import wizard. (#10248)
* - Removed PG connections in the new connection wizard
- If a PG database is active sending an error
- Removed refresh button #6611

* Added back the refresh

* -Using getConnection only once.

* -Fixed the error message
-Fixed the bugs mentioned in the PR

Co-authored-by: Aasim Khan <aaskhan@microsoft.com>
2020-05-05 20:13:16 -07:00
Anthony Dresser
d7d13c7218 update sqlite in smoke tests 2020-05-05 19:41:28 -07:00
Anthony Dresser
485e185270 remove workflows 2020-05-05 19:38:07 -07:00
ADS Merger
8420d9f04e Merge from vscode bd0efff9e3f36d6b3e1045cee9887003af8034d7 2020-05-06 02:35:49 +00:00
Alex Ma
9a7810cbee removal of unnecessary spaces. (#10275) 2020-05-05 14:58:13 -07:00
Alan Ren
0fd3b25ccd remove table setActive (#10256)
* remove setActive method

* another place
2020-05-05 13:21:26 -07:00
Alex Ma
921e546fd7 Edit Connection Feature added, edit existing connection in connection tree. (#10214)
* Added Edit Connection Command

* Wip changes for new connection dialog

* Testing

* WIP commit

* added ID check to ensure connection

* wip commit

* model id check implemented

* addfooterbutton now accepts events

* wip commit

* message explaining check

* temporary change

* connectionManagementService restored

* Revert "connectionManagementService restored"

This reverts commit 9704a63184a06a33bee2648ef0a899229d117cc0.

* formatting test

* editConnection promise testing

* edit existing connection command added

* WIP Connection Edit

* disconnect added to editConnection promise

* WIP on editExistingConnection

* changed isEdit to true

* Amir/edit connection (#10112)

* Get edit connection working

* Delete unused code

* check for isEdit as well

* connection tree test added

* WIP connection management tests

* comment out test to find out what's wrong

* fix for one error

* added note about test skipped

* changed signature of saveprofile

* saveprofile fixed

* wrote working test

* added additional test

* changed message

* Fixes made

* fix for matcher

Co-authored-by: Amir Omidi <amomidi@microsoft.com>
2020-05-05 13:21:05 -07:00
Kim Santiago
5fe72d318b Fix toolbar overflow focus not always going to first element in overflow (#10246)
* fix overflow focus not always going to first element in overflow

* adding tests
2020-05-05 13:02:06 -07:00
Amir Omidi
5105694fb3 Render before adding to the splitView (#10261) 2020-05-05 12:05:12 -07:00
Jeff Trimmer
53cb97af9b Add Always Encrypted Parameterization Option (#10182) 2020-05-05 12:01:01 -07:00
Leila Lali
a7c4a259ad Fixed an issue with setting the default location in package management (#10263) 2020-05-04 17:00:51 -07:00
Benjin Dubishar
5498c1c3a9 Knobs for unit-testing extensions (#10178)
Converted `test-extensions-unit.bat` to javascript to add a small QoL improvement that I think people will find useful: specifying which extension(s) you want to run tests for instead of commenting out lines of a script.

In addition to existing behavior being mostly preserved (node test-extensions-unit.js defaults to running all unit tests), you can specify one or more:

```
node test-extensions-unit.js sql-database-projects
node test-extensions-unit.js sql-database-projects schema-compare
```
2020-05-04 13:07:52 -07:00
Anthony Dresser
3e5b7fd3e6 Fix errors in message panel (#10257)
* fix errors in message panel

* fix compile
2020-05-02 19:37:42 -07:00
Udeesha Gautam
e3873828e0 Feature/dotnet popup (#10231)
* Add a pop up for dotnet install if not present

* Second option of default location for mac/linux

* correcting the pop up message

* remove extra dependency

* updating as per PR comments
2020-05-02 16:29:36 -07:00
Karl Burtram
db57eb9581 Fix hygine error (#10255) 2020-05-02 12:04:08 -07:00
Karl Burtram
8d46aef40c Port/welcomeflag (#10254)
* Put new welcome page behind preview features flag (#10099)

* import correct welcome page (#10117)

Co-authored-by: Anthony Dresser <andresse@microsoft.com>
2020-05-02 10:27:47 -07:00
Alan Ren
cb2a02d82c add option to hide the widget title (#10222)
* extend widget container

* remove title

* Revert "extend widget container"

* showTitle option

* rename to showHeader

* comments and refactoring
2020-05-01 16:58:30 -07:00
Chris LaFreniere
52abcd68af Tweak cell margin (#10247) 2020-05-01 14:12:41 -07:00
Anthony Dresser
5cb9b36329 Add tests for statusmodeselect (#10235)
* add tests for statusmodeselect

* add another test
2020-05-01 13:52:20 -07:00
Charles Gagnon
13d2ce7d5d Update coveralls task (#10241)
* Update coveralls task

* Undo sql change
2020-05-01 11:37:37 -07:00
Aasim Khan
64c375a12d Import Flat File Wizard - hangs at table schema (#10216)
* Using azure sql compatible query

* Included changes requested in PR for azure specific DBs and then fixed issue #9361 for contained databases.

* Made some changes mentioned in the PR
- Converted let to const
- Logging the error
2020-05-01 10:53:02 -07:00
Kim Santiago
8449888db8 fix overflow menu disappearing (#10236) 2020-05-01 10:19:33 -07:00
Anthony Dresser
cebbd04d10 Merge from vscode 2a36b7d0d527bf408bae4f96b8386db9d9455113 (#10237) 2020-04-30 23:41:35 -07:00
Cory Rivera
d7a425239b Only reveal items in the books viewlet if the viewlet is visible. (#10234) 2020-04-30 20:10:27 -07:00
Anthony Dresser
b4deca3b4b update token for copycat (#10232) 2020-04-30 14:25:11 -07:00
Anthony Dresser
92e0b2e130 Rework message panel (#10177)
* wip

* fix compile

* fix look

* fix hygiene errors

* add back functionality

* fix some issues with accessibility

* proper dispose template

* handle state properly in message panel
2020-04-30 13:49:18 -07:00
dependabot[bot]
28230b67d4 Bump jquery from 3.4.0 to 3.5.0 (#10227)
* Bump jquery from 3.4.0 to 3.5.0

Bumps [jquery](https://github.com/jquery/jquery) from 3.4.0 to 3.5.0.
- [Release notes](https://github.com/jquery/jquery/releases)
- [Commits](https://github.com/jquery/jquery/compare/3.4.0...3.5.0)

Signed-off-by: dependabot[bot] <support@github.com>

* Update remote

* Update remote web

* Update jquery again

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: chgagnon <chgagnon@microsoft.com>
2020-04-30 13:17:52 -07:00
Leila Lali
af36d79c53 MLS - Setting the right url to install sqlmlutils from for MAC and Linux (#9962)
* Setting the right url to install sqlmlutils from for MAC and Linux
2020-04-30 12:09:05 -07:00
Alan Ren
0999060827 bump sts (#10225) 2020-04-30 10:51:46 -07:00
Charles Gagnon
0e6117f044 Only filter preview extensions when preview feat disabled (#10212)
* Filter database preview tabs

* Update comment

* Revert changes - just update comment
2020-04-30 10:46:16 -07:00
Karl Burtram
fdc21bfb9e Changelog for 1.17.1 (#10224) 2020-04-30 10:18:29 -07:00
615 changed files with 15096 additions and 6958 deletions

View File

@@ -57,7 +57,7 @@ jobs:
cat .build/coverage-single/lcov.info ./extensions/admin-tool-ext-win/coverage/lcov.info ./extensions/agent/coverage/lcov.info ./extensions/azurecore/coverage/lcov.info ./extensions/cms/coverage/lcov.info ./extensions/dacpac/coverage/lcov.info ./extensions/schema-compare/coverage/lcov.info ./extensions/notebook/coverage/lcov.info ./extensions/resource-deployment/coverage/lcov.info ./extensions/machine-learning/coverage/lcov.info > .build/coverage-combined/lcov.info cat .build/coverage-single/lcov.info ./extensions/admin-tool-ext-win/coverage/lcov.info ./extensions/agent/coverage/lcov.info ./extensions/azurecore/coverage/lcov.info ./extensions/cms/coverage/lcov.info ./extensions/dacpac/coverage/lcov.info ./extensions/schema-compare/coverage/lcov.info ./extensions/notebook/coverage/lcov.info ./extensions/resource-deployment/coverage/lcov.info ./extensions/machine-learning/coverage/lcov.info > .build/coverage-combined/lcov.info
name: Merge coverage reports name: Merge coverage reports
- name: Upload Code Coverage - name: Upload Code Coverage
uses: coverallsapp/github-action@v1.0.1 uses: coverallsapp/github-action@v1.1.1
with: with:
github-token: ${{ secrets.GITHUB_TOKEN }} github-token: ${{ secrets.GITHUB_TOKEN }}
path-to-lcov: '.build/coverage-combined/lcov.info' path-to-lcov: '.build/coverage-combined/lcov.info'

View File

@@ -19,5 +19,6 @@ jobs:
- name: Run CopyCat - name: Run CopyCat
uses: ./actions/build/actions/copycat uses: ./actions/build/actions/copycat
with: with:
token: ${{secrets.TRIAGE_PAT}}
owner: anthonydresser owner: anthonydresser
repo: testissues repo: testissues

View File

@@ -1,6 +0,0 @@
{
"useTabs": true,
"printWidth": 120,
"semi": true,
"singleQuote": true
}

19
.vscode/launch.json vendored
View File

@@ -145,6 +145,25 @@
"order": 2 "order": 2
} }
}, },
{
"type": "node",
"request": "launch",
"name": "Main Process",
"runtimeExecutable": "${workspaceFolder}/scripts/code.sh",
"windows": {
"runtimeExecutable": "${workspaceFolder}/scripts/code.bat",
},
"runtimeArgs": [
"--no-cached-data"
],
"outFiles": [
"${workspaceFolder}/out/**/*.js"
],
"presentation": {
"group": "1_vscode",
"order": 1
}
},
{ {
"type": "chrome", "type": "chrome",
"request": "launch", "request": "launch",

View File

@@ -2,7 +2,24 @@
# Flags: CaseSensitive WordMatch # Flags: CaseSensitive WordMatch
# ContextLines: 2 # ContextLines: 2
10 results - 4 files 16 results - 5 files
src/vs/base/browser/dom.ts:
81 };
82
83: /** @deprecated ES6 - use classList*/
84 export const hasClass: (node: HTMLElement | SVGElement, className: string) => boolean = _classList.hasClass.bind(_classList);
85: /** @deprecated ES6 - use classList*/
86 export const addClass: (node: HTMLElement | SVGElement, className: string) => void = _classList.addClass.bind(_classList);
87: /** @deprecated ES6 - use classList*/
88 export const addClasses: (node: HTMLElement | SVGElement, ...classNames: string[]) => void = _classList.addClasses.bind(_classList);
89: /** @deprecated ES6 - use classList*/
90 export const removeClass: (node: HTMLElement | SVGElement, className: string) => void = _classList.removeClass.bind(_classList);
91: /** @deprecated ES6 - use classList*/
92 export const removeClasses: (node: HTMLElement | SVGElement, ...classNames: string[]) => void = _classList.removeClasses.bind(_classList);
93: /** @deprecated ES6 - use classList*/
94 export const toggleClass: (node: HTMLElement | SVGElement, className: string, shouldHaveIt?: boolean) => void = _classList.toggleClass.bind(_classList);
95
src/vs/base/common/arrays.ts: src/vs/base/common/arrays.ts:
401 401

View File

@@ -1,3 +1,3 @@
disturl "https://atom.io/download/electron" disturl "https://atom.io/download/electron"
target "7.2.2" target "7.2.4"
runtime "electron" runtime "electron"

View File

@@ -1,5 +1,10 @@
# Change Log # Change Log
## Version 1.17.1
* Release date: April 29, 2020
* Release status: General Availability
* Hotfix for https://github.com/microsoft/azuredatastudio/milestone/54?closed=1
## Version 1.17.0 ## Version 1.17.0
* Release date: April 27, 2020 * Release date: April 27, 2020
* Release status: General Availability * Release status: General Availability

View File

@@ -49,6 +49,7 @@ expressly granted herein, whether by implication, estoppel or otherwise.
node-fetch: https://github.com/bitinn/node-fetch node-fetch: https://github.com/bitinn/node-fetch
node-pty: https://github.com/Tyriar/node-pty node-pty: https://github.com/Tyriar/node-pty
nsfw: https://github.com/Axosoft/nsfw nsfw: https://github.com/Axosoft/nsfw
optimist: https://github.com/substack/node-optimist
primeng: https://github.com/primefaces/primeng primeng: https://github.com/primefaces/primeng
process-nextick-args: https://github.com/calvinmetcalf/process-nextick-args process-nextick-args: https://github.com/calvinmetcalf/process-nextick-args
pty.js: https://github.com/chjj/pty.js pty.js: https://github.com/chjj/pty.js
@@ -72,6 +73,7 @@ expressly granted herein, whether by implication, estoppel or otherwise.
vscode-textmate: https://github.com/Microsoft/vscode-textmate vscode-textmate: https://github.com/Microsoft/vscode-textmate
winreg: https://github.com/fresc81/node-winreg winreg: https://github.com/fresc81/node-winreg
xterm: https://github.com/sourcelair/xterm.js xterm: https://github.com/sourcelair/xterm.js
yargs: https://github.com/yargs/yargs
yauzl: https://github.com/thejoshwolfe/yauzl yauzl: https://github.com/thejoshwolfe/yauzl
zone.js: https://www.npmjs.com/package/zone zone.js: https://www.npmjs.com/package/zone
@@ -1451,6 +1453,32 @@ SOFTWARE.
========================================= =========================================
END OF nsfw NOTICES AND INFORMATION END OF nsfw NOTICES AND INFORMATION
%% optimist NOTICES AND INFORMATION BEGIN HERE
=========================================
Copyright 2010 James Halliday (mail@substack.net)
This project is free software released under the MIT/X11 license:
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
=========================================
END OF optimist NOTICES AND INFORMATION
%% primeng NOTICES AND INFORMATION BEGIN HERE %% primeng NOTICES AND INFORMATION BEGIN HERE
========================================= =========================================
The MIT License (MIT) The MIT License (MIT)
@@ -2222,6 +2250,32 @@ THE SOFTWARE.
========================================= =========================================
END OF xterm NOTICES AND INFORMATION END OF xterm NOTICES AND INFORMATION
%% yargs NOTICES AND INFORMATION BEGIN HERE
=========================================
MIT License
Copyright 2010 James Halliday (mail@substack.net); Modified work Copyright 2014 Contributors (ben@npmjs.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
=========================================
END OF yargs NOTICES AND INFORMATION
%% yauzl NOTICES AND INFORMATION BEGIN HERE %% yauzl NOTICES AND INFORMATION BEGIN HERE
========================================= =========================================
The MIT License (MIT) The MIT License (MIT)

View File

@@ -2,54 +2,76 @@ steps:
- task: NodeTool@0 - task: NodeTool@0
inputs: inputs:
versionSpec: "12.13.0" versionSpec: "12.13.0"
- task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@3 # {{SQL CARBON EDIT}} update version - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@3 # {{SQL CARBON EDIT}} update version
inputs: inputs:
versionSpec: "1.x" versionSpec: "1.x"
- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 - task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1
inputs: inputs:
keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock' keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock'
targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules' targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules'
vstsFeed: 'npm-cache' # {{SQL CARBON EDIT}} update build cache vstsFeed: 'npm-cache' # {{SQL CARBON EDIT}} update build cache
- script: | - script: |
CHILD_CONCURRENCY=1 yarn --frozen-lockfile CHILD_CONCURRENCY=1 yarn --frozen-lockfile
displayName: Install Dependencies displayName: Install Dependencies
condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) condition: and(succeeded(), ne(variables['CacheRestored'], 'true'))
- task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1 - task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1
inputs: inputs:
keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock' keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock'
targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules' targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules'
vstsFeed: 'npm-cache' # {{SQL CARBON EDIT}} update build cache vstsFeed: 'npm-cache' # {{SQL CARBON EDIT}} update build cache
condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) condition: and(succeeded(), ne(variables['CacheRestored'], 'true'))
- script: | - script: |
yarn electron x64 yarn electron x64
displayName: Download Electron displayName: Download Electron
- script: | - script: |
yarn gulp hygiene yarn gulp hygiene
displayName: Run Hygiene Checks displayName: Run Hygiene Checks
- script: | # {{SQL CARBON EDIT}} add step - script: | # {{SQL CARBON EDIT}} add step
yarn strict-vscode yarn strict-vscode
displayName: Run Strict Null Check. displayName: Run Strict Null Check.
# - script: | {{SQL CARBON EDIT}} remove step # - script: | {{SQL CARBON EDIT}} remove step
# yarn monaco-compile-check # yarn monaco-compile-check
# displayName: Run Monaco Editor Checks # displayName: Run Monaco Editor Checks
- script: | - script: |
yarn valid-layers-check yarn valid-layers-check
displayName: Run Valid Layers Checks displayName: Run Valid Layers Checks
- script: | - script: |
yarn compile yarn compile
displayName: Compile Sources displayName: Compile Sources
# - script: | {{SQL CARBON EDIT}} remove step # - script: | {{SQL CARBON EDIT}} remove step
# yarn download-builtin-extensions # yarn download-builtin-extensions
# displayName: Download Built-in Extensions # displayName: Download Built-in Extensions
- script: | - script: |
./scripts/test.sh --tfs "Unit Tests" ./scripts/test.sh --tfs "Unit Tests"
displayName: Run Unit Tests (Electron) displayName: Run Unit Tests (Electron)
# - script: | {{SQL CARBON EDIT}} disable # - script: | {{SQL CARBON EDIT}} disable
# yarn test-browser --browser chromium --browser webkit --browser firefox # yarn test-browser --browser chromium --browser webkit --browser firefox
# displayName: Run Unit Tests (Browser) # displayName: Run Unit Tests (Browser)
# - script: | {{SQL CARBON EDIT}} disable # - script: | {{SQL CARBON EDIT}} disable
# ./scripts/test-integration.sh --tfs "Integration Tests" # ./scripts/test-integration.sh --tfs "Integration Tests"
# displayName: Run Integration Tests (Electron) # displayName: Run Integration Tests (Electron)
# - task: PublishPipelineArtifact@0
# inputs:
# artifactName: crash-dump-macos
# targetPath: .build/crashes
# displayName: 'Publish Crash Reports'
# condition: succeededOrFailed()
- task: PublishTestResults@2 - task: PublishTestResults@2
displayName: Publish Tests Results displayName: Publish Tests Results
inputs: inputs:

View File

@@ -2,13 +2,5 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
<true/>
</dict> </dict>
</plist> </plist>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
<true/>
</dict>
</plist>

View File

@@ -152,15 +152,29 @@ steps:
displayName: Run smoke tests (Browser) displayName: Run smoke tests (Browser)
condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false'))
- task: PublishPipelineArtifact@0
inputs:
artifactName: crash-dump-macos
targetPath: .build/crashes
displayName: 'Publish Crash Reports'
condition: succeededOrFailed()
- script: | - script: |
set -e set -e
APP_ROOT=$(agent.builddirectory)/VSCode-darwin
APP_NAME="`ls $APP_ROOT | head -n 1`"
HELPER_APP_NAME="`echo $APP_NAME | sed -e 's/^Visual Studio //;s/\.app$//'`"
APP_FRAMEWORK_PATH="$APP_ROOT/$APP_NAME/Contents/Frameworks"
security create-keychain -p pwd $(agent.tempdirectory)/buildagent.keychain security create-keychain -p pwd $(agent.tempdirectory)/buildagent.keychain
security default-keychain -s $(agent.tempdirectory)/buildagent.keychain security default-keychain -s $(agent.tempdirectory)/buildagent.keychain
security unlock-keychain -p pwd $(agent.tempdirectory)/buildagent.keychain security unlock-keychain -p pwd $(agent.tempdirectory)/buildagent.keychain
echo "$(macos-developer-certificate)" | base64 -D > $(agent.tempdirectory)/cert.p12 echo "$(macos-developer-certificate)" | base64 -D > $(agent.tempdirectory)/cert.p12
security import $(agent.tempdirectory)/cert.p12 -k $(agent.tempdirectory)/buildagent.keychain -P "$(macos-developer-certificate-key)" -T /usr/bin/codesign security import $(agent.tempdirectory)/cert.p12 -k $(agent.tempdirectory)/buildagent.keychain -P "$(macos-developer-certificate-key)" -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k pwd $(agent.tempdirectory)/buildagent.keychain security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k pwd $(agent.tempdirectory)/buildagent.keychain
codesign -s 99FM488X57 --deep --force --options runtime --entitlements build/azure-pipelines/darwin/entitlements.plist $(agent.builddirectory)/VSCode-darwin/*.app codesign -s 99FM488X57 --deep --force --options runtime --entitlements build/azure-pipelines/darwin/entitlements.plist "$APP_ROOT"/*.app
codesign -s 99FM488X57 --force --options runtime --entitlements build/azure-pipelines/darwin/helper-gpu-entitlements.plist "$APP_FRAMEWORK_PATH/$HELPER_APP_NAME Helper (GPU).app"
codesign -s 99FM488X57 --force --options runtime --entitlements build/azure-pipelines/darwin/helper-plugin-entitlements.plist "$APP_FRAMEWORK_PATH/$HELPER_APP_NAME Helper (Plugin).app"
codesign -s 99FM488X57 --force --options runtime --entitlements build/azure-pipelines/darwin/helper-renderer-entitlements.plist "$APP_FRAMEWORK_PATH/$HELPER_APP_NAME Helper (Renderer).app"
displayName: Set Hardened Entitlements displayName: Set Hardened Entitlements
- script: | - script: |

View File

@@ -114,14 +114,14 @@ steps:
APP_NAME="`ls $APP_ROOT | head -n 1`" APP_NAME="`ls $APP_ROOT | head -n 1`"
yarn smoketest --build "$APP_ROOT/$APP_NAME" --screenshots "$(build.artifactstagingdirectory)/smokeshots" yarn smoketest --build "$APP_ROOT/$APP_NAME" --screenshots "$(build.artifactstagingdirectory)/smokeshots"
displayName: Run smoke tests (Electron) displayName: Run smoke tests (Electron)
condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) condition: and(succeeded(), eq(variables['RUN_TESTS'], 'true'))
- script: | # - script: |
set -e # set -e
VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/azuredatastudio-reh-web-darwin" \ # VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/azuredatastudio-reh-web-darwin" \
yarn smoketest --web --headless --screenshots "$(build.artifactstagingdirectory)/smokeshots" # yarn smoketest --web --headless --screenshots "$(build.artifactstagingdirectory)/smokeshots"
displayName: Run smoke tests (Browser) # displayName: Run smoke tests (Browser)
condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) # condition: and(succeeded(), eq(variables['RUN_TESTS'], 'true'))
- script: | - script: |
set -e set -e

View File

@@ -7,57 +7,80 @@ steps:
sudo chmod +x /etc/init.d/xvfb sudo chmod +x /etc/init.d/xvfb
sudo update-rc.d xvfb defaults sudo update-rc.d xvfb defaults
sudo service xvfb start sudo service xvfb start
- task: NodeTool@0 - task: NodeTool@0
inputs: inputs:
versionSpec: "12.13.0" versionSpec: "12.13.0"
- task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@3 - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@3
inputs: inputs:
versionSpec: "1.x" versionSpec: "1.x"
- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 - task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1
inputs: inputs:
keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock' keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock'
targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules' targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules'
vstsFeed: 'npm-cache' # {{SQL CARBON EDIT}} update build cache vstsFeed: 'npm-cache' # {{SQL CARBON EDIT}} update build cache
- script: | - script: |
CHILD_CONCURRENCY=1 yarn --frozen-lockfile CHILD_CONCURRENCY=1 yarn --frozen-lockfile
displayName: Install Dependencies displayName: Install Dependencies
condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) condition: and(succeeded(), ne(variables['CacheRestored'], 'true'))
- task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1 - task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1
inputs: inputs:
keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock' keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock'
targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules' targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules'
vstsFeed: 'npm-cache' # {{SQL CARBON EDIT}} update build cache vstsFeed: 'npm-cache' # {{SQL CARBON EDIT}} update build cache
condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) condition: and(succeeded(), ne(variables['CacheRestored'], 'true'))
- script: | - script: |
yarn electron x64 yarn electron x64
displayName: Download Electron displayName: Download Electron
- script: | - script: |
yarn gulp hygiene yarn gulp hygiene
displayName: Run Hygiene Checks displayName: Run Hygiene Checks
- script: | # {{SQL CARBON EDIT}} add strict null check - script: | # {{SQL CARBON EDIT}} add strict null check
yarn strict-vscode yarn strict-vscode
displayName: Run Strict Null Check displayName: Run Strict Null Check
# - script: | {{SQL CARBON EDIT}} remove monaco editor checks # - script: | {{SQL CARBON EDIT}} remove monaco editor checks
# yarn monaco-compile-check # yarn monaco-compile-check
# displayName: Run Monaco Editor Checks # displayName: Run Monaco Editor Checks
- script: | - script: |
yarn valid-layers-check yarn valid-layers-check
displayName: Run Valid Layers Checks displayName: Run Valid Layers Checks
- script: | - script: |
yarn compile yarn compile
displayName: Compile Sources displayName: Compile Sources
# - script: | {{SQL CARBON EDIT}} remove step # - script: | {{SQL CARBON EDIT}} remove step
# yarn download-builtin-extensions # yarn download-builtin-extensions
# displayName: Download Built-in Extensions # displayName: Download Built-in Extensions
- script: | - script: |
DISPLAY=:10 ./scripts/test.sh --tfs "Unit Tests" DISPLAY=:10 ./scripts/test.sh --tfs "Unit Tests"
displayName: Run Unit Tests (Electron) displayName: Run Unit Tests (Electron)
# - script: | {{SQL CARBON EDIT}} disable # - script: | {{SQL CARBON EDIT}} disable
# DISPLAY=:10 yarn test-browser --browser chromium # DISPLAY=:10 yarn test-browser --browser chromium
# displayName: Run Unit Tests (Browser) # displayName: Run Unit Tests (Browser)
# - script: | {{SQL CARBON EDIT}} disable # - script: | {{SQL CARBON EDIT}} disable
# DISPLAY=:10 ./scripts/test-integration.sh --tfs "Integration Tests" # DISPLAY=:10 ./scripts/test-integration.sh --tfs "Integration Tests"
# displayName: Run Integration Tests (Electron) # displayName: Run Integration Tests (Electron)
# - task: PublishPipelineArtifact@0
# inputs:
# artifactName: crash-dump-linux
# targetPath: .build/crashes
# displayName: 'Publish Crash Reports'
# condition: succeededOrFailed()
- task: PublishTestResults@2 - task: PublishTestResults@2
displayName: Publish Tests Results displayName: Publish Tests Results
inputs: inputs:

View File

@@ -140,6 +140,13 @@ steps:
displayName: Run integration tests (Browser) displayName: Run integration tests (Browser)
condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false'))
- task: PublishPipelineArtifact@0
inputs:
artifactName: crash-dump-linux
targetPath: .build/crashes
displayName: 'Publish Crash Reports'
condition: succeededOrFailed()
- script: | - script: |
set -e set -e
yarn gulp "vscode-linux-x64-build-deb" yarn gulp "vscode-linux-x64-build-deb"

View File

@@ -16,12 +16,10 @@ steps:
- task: NodeTool@0 - task: NodeTool@0
inputs: inputs:
versionSpec: "10.15.1" versionSpec: "10.15.1"
condition: and(succeeded(), ne(variables['CacheRestored-Compilation'], 'true'))
- task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@3 - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@3
inputs: inputs:
versionSpec: "1.x" versionSpec: "1.x"
condition: and(succeeded(), ne(variables['CacheRestored-Compilation'], 'true'))
- task: AzureKeyVault@1 - task: AzureKeyVault@1
displayName: 'Azure Key Vault: Get Secrets' displayName: 'Azure Key Vault: Get Secrets'
@@ -56,26 +54,25 @@ steps:
keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock' keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock'
targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules' targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules'
vstsFeed: 'BuildCache' vstsFeed: 'BuildCache'
condition: and(succeeded(), ne(variables['CacheRestored-Compilation'], 'true'))
- script: | - script: |
set -e set -e
CHILD_CONCURRENCY=1 yarn --frozen-lockfile CHILD_CONCURRENCY=1 yarn --frozen-lockfile
displayName: Install dependencies displayName: Install dependencies
condition: and(succeeded(), ne(variables['CacheRestored-Compilation'], 'true'), ne(variables['CacheRestored'], 'true')) condition: and(succeeded(), ne(variables['CacheRestored'], 'true'))
- task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1 - task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1
inputs: inputs:
keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock' keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock'
targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules' targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules'
vstsFeed: 'BuildCache' vstsFeed: 'BuildCache'
condition: and(succeeded(), ne(variables['CacheRestored-Compilation'], 'true'), ne(variables['CacheRestored'], 'true')) condition: and(succeeded(), ne(variables['CacheRestored'], 'true'))
- script: | - script: |
set -e set -e
yarn postinstall yarn postinstall
displayName: Run postinstall scripts displayName: Run postinstall scripts
condition: and(succeeded(), ne(variables['CacheRestored-Compilation'], 'true'), eq(variables['CacheRestored'], 'true')) condition: and(succeeded(), eq(variables['CacheRestored'], 'true'))
# Mixin must run before optimize, because the CSS loader will # Mixin must run before optimize, because the CSS loader will
# inline small SVGs # inline small SVGs
@@ -113,7 +110,6 @@ steps:
node build/azure-pipelines/common/copyArtifacts.js node build/azure-pipelines/common/copyArtifacts.js
displayName: Write Version Information displayName: Write Version Information
condition: and(succeeded(), ne(variables['CacheRestored-Compilation'], 'true'))
- task: PublishBuildArtifacts@1 - task: PublishBuildArtifacts@1
displayName: 'Publish Artifact: drop' displayName: 'Publish Artifact: drop'

View File

@@ -2,59 +2,83 @@ steps:
- task: NodeTool@0 - task: NodeTool@0
inputs: inputs:
versionSpec: "12.13.0" versionSpec: "12.13.0"
- task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@3 # {{SQL CARBON EDIT}} update version - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@3 # {{SQL CARBON EDIT}} update version
inputs: inputs:
versionSpec: "1.x" versionSpec: "1.x"
- task: UsePythonVersion@0 - task: UsePythonVersion@0
inputs: inputs:
versionSpec: '2.x' versionSpec: '2.x'
addToPath: true addToPath: true
- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 - task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1
inputs: inputs:
keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock' keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock'
targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules' targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules'
vstsFeed: 'npm-cache' # {{SQL CARBON EDIT}} update build cache vstsFeed: 'npm-cache' # {{SQL CARBON EDIT}} update build cache
- powershell: | - powershell: |
yarn --frozen-lockfile yarn --frozen-lockfile
env: env:
CHILD_CONCURRENCY: "1" CHILD_CONCURRENCY: "1"
displayName: Install Dependencies displayName: Install Dependencies
condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) condition: and(succeeded(), ne(variables['CacheRestored'], 'true'))
- task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1 - task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1
inputs: inputs:
keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock' keyfile: 'build/.cachesalt, .yarnrc, remote/.yarnrc, **/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock, !samples/**/yarn.lock'
targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules' targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules'
vstsFeed: 'npm-cache' # {{SQL CARBON EDIT}} update build cache vstsFeed: 'npm-cache' # {{SQL CARBON EDIT}} update build cache
condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) condition: and(succeeded(), ne(variables['CacheRestored'], 'true'))
- powershell: | - powershell: |
yarn electron yarn electron
displayName: Download Electron
- script: | - script: |
yarn gulp hygiene yarn gulp hygiene
displayName: Run Hygiene Checks displayName: Run Hygiene Checks
- script: | # {{SQL CARBON EDIT}} add step - script: | # {{SQL CARBON EDIT}} add step
yarn strict-vscode yarn strict-vscode
displayName: Run Strict Null Check displayName: Run Strict Null Check
# - powershell: | {{SQL CARBON EDIT}} remove step # - powershell: | {{SQL CARBON EDIT}} remove step
# yarn monaco-compile-check # yarn monaco-compile-check
# displayName: Run Monaco Editor Checks # displayName: Run Monaco Editor Checks
- script: | - script: |
yarn valid-layers-check yarn valid-layers-check
displayName: Run Valid Layers Checks displayName: Run Valid Layers Checks
- powershell: | - powershell: |
yarn compile yarn compile
displayName: Compile Sources displayName: Compile Sources
# - powershell: | {{SQL CARBON EDIT}} remove step # - powershell: | {{SQL CARBON EDIT}} remove step
# yarn download-builtin-extensions # yarn download-builtin-extensions
# displayName: Download Built-in Extensions # displayName: Download Built-in Extensions
- powershell: | - powershell: |
.\scripts\test.bat --tfs "Unit Tests" .\scripts\test.bat --tfs "Unit Tests"
displayName: Run Unit Tests (Electron) displayName: Run Unit Tests (Electron)
# - powershell: | {{SQL CARBON EDIT}} disable # - powershell: | {{SQL CARBON EDIT}} disable
# yarn test-browser --browser chromium --browser firefox # yarn test-browser --browser chromium --browser firefox
# displayName: Run Unit Tests (Browser) # displayName: Run Unit Tests (Browser)
# - powershell: | {{SQL CARBON EDIT}} disable # - powershell: | {{SQL CARBON EDIT}} disable
# .\scripts\test-integration.bat --tfs "Integration Tests" # .\scripts\test-integration.bat --tfs "Integration Tests"
# displayName: Run Integration Tests (Electron) # displayName: Run Integration Tests (Electron)
# - task: PublishPipelineArtifact@0
# displayName: 'Publish Crash Reports'
# inputs:
# artifactName: crash-dump-windows
# targetPath: .build\crashes
# condition: succeededOrFailed()
- task: PublishTestResults@2 - task: PublishTestResults@2
displayName: Publish Tests Results displayName: Publish Tests Results
inputs: inputs:

View File

@@ -149,6 +149,13 @@ steps:
displayName: Run integration tests (Browser) displayName: Run integration tests (Browser)
condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false'))
- task: PublishPipelineArtifact@0
inputs:
artifactName: crash-dump-windows-$(VSCODE_ARCH)
targetPath: .build\crashes
displayName: 'Publish Crash Reports'
condition: succeededOrFailed()
- task: SFP.build-tasks.custom-build-task-1.EsrpCodeSigning@1 - task: SFP.build-tasks.custom-build-task-1.EsrpCodeSigning@1
inputs: inputs:
ConnectedServiceName: 'ESRP CodeSign' ConnectedServiceName: 'ESRP CodeSign'

View File

@@ -44,7 +44,7 @@ function prepareDebPackage(arch) {
.pipe(replace('@@NAME_SHORT@@', product.nameShort)) .pipe(replace('@@NAME_SHORT@@', product.nameShort))
.pipe(replace('@@NAME@@', product.applicationName)) .pipe(replace('@@NAME@@', product.applicationName))
.pipe(replace('@@EXEC@@', `/usr/share/${product.applicationName}/${product.applicationName}`)) .pipe(replace('@@EXEC@@', `/usr/share/${product.applicationName}/${product.applicationName}`))
.pipe(replace('@@ICON@@', `/usr/share/pixmaps/${product.linuxIconName}.png`)) .pipe(replace('@@ICON@@', product.linuxIconName))
.pipe(replace('@@URLPROTOCOL@@', product.urlProtocol)); .pipe(replace('@@URLPROTOCOL@@', product.urlProtocol));
const appdata = gulp.src('resources/linux/code.appdata.xml', { base: '.' }) const appdata = gulp.src('resources/linux/code.appdata.xml', { base: '.' })

View File

@@ -430,6 +430,7 @@ function markNodes(languageService, options) {
|| ts.isIndexSignatureDeclaration(member) || ts.isIndexSignatureDeclaration(member)
|| ts.isCallSignatureDeclaration(member) || ts.isCallSignatureDeclaration(member)
|| memberName === '[Symbol.iterator]' || memberName === '[Symbol.iterator]'
|| memberName === '[Symbol.toStringTag]'
|| memberName === 'toJSON' || memberName === 'toJSON'
|| memberName === 'toString' || memberName === 'toString'
|| memberName === 'dispose' // TODO: keeping all `dispose` methods || memberName === 'dispose' // TODO: keeping all `dispose` methods

View File

@@ -548,6 +548,7 @@ function markNodes(languageService: ts.LanguageService, options: ITreeShakingOpt
|| ts.isIndexSignatureDeclaration(member) || ts.isIndexSignatureDeclaration(member)
|| ts.isCallSignatureDeclaration(member) || ts.isCallSignatureDeclaration(member)
|| memberName === '[Symbol.iterator]' || memberName === '[Symbol.iterator]'
|| memberName === '[Symbol.toStringTag]'
|| memberName === 'toJSON' || memberName === 'toJSON'
|| memberName === 'toString' || memberName === 'toString'
|| memberName === 'dispose'// TODO: keeping all `dispose` methods || memberName === 'dispose'// TODO: keeping all `dispose` methods
@@ -795,8 +796,8 @@ function getRealNodeSymbol(checker: ts.TypeChecker, node: ts.Node): [ts.Symbol |
let symbol = ( let symbol = (
ts.isShorthandPropertyAssignment(node) ts.isShorthandPropertyAssignment(node)
? checker.getShorthandAssignmentValueSymbol(node) ? checker.getShorthandAssignmentValueSymbol(node)
: checker.getSymbolAtLocation(node) : checker.getSymbolAtLocation(node)
); );
let importNode: ts.Declaration | null = null; let importNode: ts.Declaration | null = null;

View File

@@ -48,7 +48,7 @@
"rollup-plugin-commonjs": "^10.1.0", "rollup-plugin-commonjs": "^10.1.0",
"rollup-plugin-node-resolve": "^5.2.0", "rollup-plugin-node-resolve": "^5.2.0",
"terser": "4.3.8", "terser": "4.3.8",
"typescript": "^3.9.0-dev.20200427", "typescript": "^3.9.1-rc",
"vsce": "1.48.0", "vsce": "1.48.0",
"vscode-telemetry-extractor": "^1.5.4", "vscode-telemetry-extractor": "^1.5.4",
"xml2js": "^0.4.17" "xml2js": "^0.4.17"

View File

@@ -3462,10 +3462,10 @@ typescript@^3.0.1:
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.5.3.tgz#c830f657f93f1ea846819e929092f5fe5983e977" resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.5.3.tgz#c830f657f93f1ea846819e929092f5fe5983e977"
integrity sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g== integrity sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g==
typescript@^3.9.0-dev.20200427: typescript@^3.9.1-rc:
version "3.9.0-dev.20200427" version "3.9.1-rc"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.0-dev.20200427.tgz#e42d606d938575dfb7b0b66f04a31b5f0eb0be57" resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.1-rc.tgz#81d5a5a0a597e224b6e2af8dffb46524b2eaf5f3"
integrity sha512-ja/GhL7BHT+VQZiLoYMGJt2CP1Pdr0EhYefv4LLw4tVooSuCDB8SDKT/i/HwsoPgQ4ZaYfg1vPl+1RhiO3bwJg== integrity sha512-+cPv8L2Vd4KidCotqi2wjegBZ5n47CDRUu/QiLVu2YbeXAz78hIfcai9ziBiNI6JTGTVwUqXRug2UZxDcxhvFw==
typical@^4.0.0: typical@^4.0.0:
version "4.0.0" version "4.0.0"

View File

@@ -60,12 +60,12 @@
"git": { "git": {
"name": "electron", "name": "electron",
"repositoryUrl": "https://github.com/electron/electron", "repositoryUrl": "https://github.com/electron/electron",
"commitHash": "959e80cc53cbebf8eb1d62eb2d14fa8fd86b0394" "commitHash": "0552e0d5de46ffa3b481d741f1db5c779e201565"
} }
}, },
"isOnlyProductionDependency": true, "isOnlyProductionDependency": true,
"license": "MIT", "license": "MIT",
"version": "7.2.2" "version": "7.2.4"
}, },
{ {
"component": { "component": {

View File

@@ -325,6 +325,7 @@ export abstract class AzureAuth implements vscode.Disposable {
return tenants; return tenants;
} catch (ex) { } catch (ex) {
console.log(ex); console.log(ex);
console.log(JSON.stringify(ex?.response?.data, undefined, 2));
throw new Error('Error retreiving tenant information'); throw new Error('Error retreiving tenant information');
} }
} }

View File

@@ -3,7 +3,6 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { WorkspaceConfiguration, ConfigurationTarget } from 'vscode';
import { Account } from 'azdata'; import { Account } from 'azdata';
import { azureResource } from '../azure-resource'; import { azureResource } from '../azure-resource';
@@ -53,25 +52,8 @@ export class AzureResourceSubscriptionFilterService implements IAzureResourceSub
for (const accountId in selectedSubscriptionsCache) { for (const accountId in selectedSubscriptionsCache) {
filters.push(...selectedSubscriptionsCache[accountId].map((subcription) => `${accountId}/${subcription.id}/${subcription.name}`)); filters.push(...selectedSubscriptionsCache[accountId].map((subcription) => `${accountId}/${subcription.id}/${subcription.name}`));
} }
const resourceFilterConfig = this._config.inspect<string[]>(AzureResourceSubscriptionFilterService.filterConfigName);
let configTarget = ConfigurationTarget.Global;
if (resourceFilterConfig) {
if (resourceFilterConfig.workspaceFolderValue) {
configTarget = ConfigurationTarget.WorkspaceFolder;
} else if (resourceFilterConfig.workspaceValue) {
configTarget = ConfigurationTarget.Workspace;
} else if (resourceFilterConfig.globalValue) {
configTarget = ConfigurationTarget.Global;
}
}
await this._config.update(AzureResourceSubscriptionFilterService.filterConfigName, filters, configTarget);
} }
private _config: WorkspaceConfiguration = undefined;
private _cacheService: IAzureResourceCacheService = undefined; private _cacheService: IAzureResourceCacheService = undefined;
private _cacheKey: string = undefined; private _cacheKey: string = undefined;
private static readonly filterConfigName = 'azure.resource.config.filter';
} }

View File

@@ -59,7 +59,6 @@ export class IconPathHelper {
export namespace cssStyles { export namespace cssStyles {
export const title = { 'font-size': '14px', 'font-weight': '600' }; export const title = { 'font-size': '14px', 'font-weight': '600' };
export const tableHeader = { 'text-align': 'left', 'font-weight': 'bold', 'text-transform': 'uppercase', 'font-size': '10px', 'user-select': 'text' }; export const tableHeader = { 'text-align': 'left', 'font-weight': 'bold', 'text-transform': 'uppercase', 'font-size': '10px', 'user-select': 'text' };
export const hyperlink = { 'user-select': 'text', 'color': '#0078d4', 'text-decoration': 'underline', 'cursor': 'pointer' };
export const text = { 'margin-block-start': '0px', 'margin-block-end': '0px' }; export const text = { 'margin-block-start': '0px', 'margin-block-end': '0px' };
export const overflowEllipsisText = { ...text, 'overflow': 'hidden', 'text-overflow': 'ellipsis' }; export const overflowEllipsisText = { ...text, 'overflow': 'hidden', 'text-overflow': 'ellipsis' };
export const nonSelectableText = { ...cssStyles.text, 'user-select': 'none' }; export const nonSelectableText = { ...cssStyles.text, 'user-select': 'none' };

View File

@@ -363,7 +363,7 @@ export class BdcDashboardOverviewPage extends BdcDashboardPage {
.withProperties<azdata.HyperlinkComponentProperties>({ .withProperties<azdata.HyperlinkComponentProperties>({
label: getServiceNameDisplayText(serviceStatus.serviceName), label: getServiceNameDisplayText(serviceStatus.serviceName),
url: '', url: '',
CSSStyles: { ...cssStyles.text, ...cssStyles.hyperlink } CSSStyles: { ...cssStyles.text }
}).component(); }).component();
nameCell.onDidClick(() => { nameCell.onDidClick(() => {
this.dashboard.switchToServiceTab(serviceStatus.serviceName); this.dashboard.switchToServiceTab(serviceStatus.serviceName);
@@ -458,7 +458,7 @@ function createEndpointComponent(modelBuilder: azdata.ModelBuilder, endpoint: En
.withProperties<azdata.HyperlinkComponentProperties>({ .withProperties<azdata.HyperlinkComponentProperties>({
label: endpoint.endpoint, label: endpoint.endpoint,
title: endpoint.endpoint, title: endpoint.endpoint,
url: endpoint.endpoint, CSSStyles: { ...cssStyles.hyperlink } url: endpoint.endpoint
}) })
.component(); .component();
} }
@@ -468,7 +468,7 @@ function createEndpointComponent(modelBuilder: azdata.ModelBuilder, endpoint: En
title: endpoint.endpoint, title: endpoint.endpoint,
label: endpoint.endpoint, label: endpoint.endpoint,
url: '', url: '',
CSSStyles: { ...cssStyles.text, ...cssStyles.hyperlink } CSSStyles: { ...cssStyles.text }
}).component(); }).component();
endpointCell.onDidClick(async () => { endpointCell.onDidClick(async () => {
const connProfile = bdcModel.getSqlServerMasterConnectionProfile(); const connProfile = bdcModel.getSqlServerMasterConnectionProfile();

View File

@@ -304,7 +304,7 @@ export class BdcDashboardResourceStatusPage extends BdcDashboardPage {
url: instanceStatus.dashboards.nodeMetricsUrl, url: instanceStatus.dashboards.nodeMetricsUrl,
title: instanceStatus.dashboards.nodeMetricsUrl, title: instanceStatus.dashboards.nodeMetricsUrl,
ariaLabel: loc.viewNodeMetrics(instanceStatus.dashboards.nodeMetricsUrl), ariaLabel: loc.viewNodeMetrics(instanceStatus.dashboards.nodeMetricsUrl),
CSSStyles: { ...cssStyles.text, ...cssStyles.hyperlink } CSSStyles: { ...cssStyles.text }
}).component()); }).component());
} }
@@ -319,7 +319,7 @@ export class BdcDashboardResourceStatusPage extends BdcDashboardPage {
url: instanceStatus.dashboards.sqlMetricsUrl, url: instanceStatus.dashboards.sqlMetricsUrl,
title: instanceStatus.dashboards.sqlMetricsUrl, title: instanceStatus.dashboards.sqlMetricsUrl,
ariaLabel: loc.viewSqlMetrics(instanceStatus.dashboards.sqlMetricsUrl), ariaLabel: loc.viewSqlMetrics(instanceStatus.dashboards.sqlMetricsUrl),
CSSStyles: { ...cssStyles.text, ...cssStyles.hyperlink } CSSStyles: { ...cssStyles.text }
}).component()); }).component());
} }
} }
@@ -332,7 +332,7 @@ export class BdcDashboardResourceStatusPage extends BdcDashboardPage {
url: instanceStatus.dashboards.logsUrl, url: instanceStatus.dashboards.logsUrl,
title: instanceStatus.dashboards.logsUrl, title: instanceStatus.dashboards.logsUrl,
ariaLabel: loc.viewLogs(instanceStatus.dashboards.logsUrl), ariaLabel: loc.viewLogs(instanceStatus.dashboards.logsUrl),
CSSStyles: { ...cssStyles.text, ...cssStyles.hyperlink } CSSStyles: { ...cssStyles.text }
}).component()); }).component());
} }
return row; return row;

View File

@@ -282,33 +282,42 @@
] ]
} }
}, },
"allOf": [ "oneOf": [
{ {
"oneOf": [ "allOf": [
{ {
"allOf": [ "oneOf": [
{ {
"oneOf": [ "allOf": [
{ {
"$ref": "#/definitions/dockerfileContainer" "oneOf": [
{
"$ref": "#/definitions/dockerfileContainer"
},
{
"$ref": "#/definitions/imageContainer"
}
]
}, },
{ {
"$ref": "#/definitions/imageContainer" "$ref": "#/definitions/nonComposeBase"
} }
] ]
}, },
{ {
"$ref": "#/definitions/nonComposeBase" "$ref": "#/definitions/composeContainer"
} }
] ]
}, },
{ {
"$ref": "#/definitions/composeContainer" "$ref": "#/definitions/devContainerCommon"
} }
] ]
}, },
{ {
"$ref": "#/definitions/devContainerCommon" "type": "object",
"$ref": "#/definitions/devContainerCommon",
"additionalProperties": false
} }
] ]
} }

View File

@@ -1,26 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
export default abstract class ControllerBase implements vscode.Disposable {
protected _context: vscode.ExtensionContext;
protected constructor(context: vscode.ExtensionContext) {
this._context = context;
}
public get extensionContext(): vscode.ExtensionContext {
return this._context;
}
abstract activate(): Promise<boolean>;
abstract deactivate(): void;
public dispose(): void {
this.deactivate();
}
}

View File

@@ -4,20 +4,17 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata'; import * as azdata from 'azdata';
import ControllerBase from './controllerBase';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import { DataTierApplicationWizard } from '../wizard/dataTierApplicationWizard'; import { DataTierApplicationWizard } from '../wizard/dataTierApplicationWizard';
/** /**
* The main controller class that initializes the extension * The main controller class that initializes the extension
*/ */
export default class MainController extends ControllerBase { export default class MainController implements vscode.Disposable {
public constructor(context: vscode.ExtensionContext) { public constructor(private context: vscode.ExtensionContext) {
super(context);
} }
/**
*/
public deactivate(): void { public deactivate(): void {
} }
@@ -29,4 +26,12 @@ export default class MainController extends ControllerBase {
private initializeDacFxWizard() { private initializeDacFxWizard() {
azdata.tasks.registerTask('dacFx.start', (profile: azdata.IConnectionProfile, ...args: any[]) => new DataTierApplicationWizard().start(profile, args)); azdata.tasks.registerTask('dacFx.start', (profile: azdata.IConnectionProfile, ...args: any[]) => new DataTierApplicationWizard().start(profile, args));
} }
public get extensionContext(): vscode.ExtensionContext {
return this.context;
}
public dispose(): void {
this.deactivate();
}
} }

View File

@@ -4,11 +4,9 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import ControllerBase from './controllers/controllerBase';
import MainController from './controllers/mainController'; import MainController from './controllers/mainController';
let controllers: ControllerBase[] = []; let controllers: MainController[] = [];
export function activate(context: vscode.ExtensionContext) { export function activate(context: vscode.ExtensionContext) {
let activations: Promise<boolean>[] = []; let activations: Promise<boolean>[] = [];

View File

@@ -0,0 +1,27 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as should from 'should';
import MainController from '../controllers/mainController';
import { TestContext, createContext } from './testContext';
let testContext: TestContext;
function createController(): MainController {
let controller = new MainController(testContext.context);
return controller;
}
describe('MainController', function (): void {
before(async function (): Promise<void> {
testContext = createContext();
});
it('Should create new instance successfully', async function (): Promise<void> {
let controller: MainController;
should.doesNotThrow(() => controller = createController());
should.notEqual(controller.extensionContext, undefined);
});
});

View File

@@ -0,0 +1,36 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import * as path from 'path';
export interface TestContext {
context: vscode.ExtensionContext;
}
export function createContext(): TestContext {
let extensionPath = path.join(__dirname, '..', '..');
return {
context: {
subscriptions: [],
workspaceState: {
get: () => { return Promise.resolve(); },
update: () => { return Promise.resolve(); }
},
globalState: {
get: () => { return Promise.resolve(); },
update: () => { return Promise.resolve(); }
},
extensionPath: extensionPath,
asAbsolutePath: () => { return ''; },
storagePath: '',
globalStoragePath: '',
logPath: '',
extensionUri: vscode.Uri.parse(''),
environmentVariableCollection: undefined as any
}
};
}

View File

@@ -7,10 +7,16 @@ import 'mocha';
import * as should from 'should'; import * as should from 'should';
import * as loc from '../localizedConstants'; import * as loc from '../localizedConstants';
import { DataTierApplicationWizard, Operation } from '../wizard/dataTierApplicationWizard'; import { DataTierApplicationWizard, Operation } from '../wizard/dataTierApplicationWizard';
import { DacFxDataModel } from '../wizard/api/models';
let wizard: DataTierApplicationWizard;
describe('Dacfx wizard', function (): void { describe('Dacfx wizard', function (): void {
beforeEach(async function (): Promise<void> {
wizard = new DataTierApplicationWizard();
wizard.model = <DacFxDataModel>{};
});
it('Should initialize wizard correctly', async () => { it('Should initialize wizard correctly', async () => {
let wizard = new DataTierApplicationWizard();
should.notEqual(wizard.wizard, undefined); should.notEqual(wizard.wizard, undefined);
should.equal(wizard.wizard.title, loc.wizardTitle); should.equal(wizard.wizard.title, loc.wizardTitle);
@@ -18,8 +24,50 @@ describe('Dacfx wizard', function (): void {
should.notEqual(wizard.pages, undefined); should.notEqual(wizard.pages, undefined);
should.equal(wizard.pages.size, 7); should.equal(wizard.pages.size, 7);
should.equal(wizard.wizard.pages.length, 4); should.equal(wizard.wizard.pages.length, 4);
});
it('Should determine summary page correctly', async () => {
// summary page should be 2 for deploy
wizard.selectedOperation = Operation.deploy;
wizard.model.upgradeExisting = false;
should.equal(wizard.isSummaryPage(2), true);
// summary page should be 3 for deploy - upgrade existing db
wizard.selectedOperation = Operation.deploy;
wizard.model.upgradeExisting = true;
should.equal(wizard.isSummaryPage(3), true);
// summary page should be 3 for generate deploy script
wizard.selectedOperation = Operation.generateDeployScript;
should.equal(wizard.isSummaryPage(3), true);
// summary page should be 2 for import
wizard.selectedOperation = Operation.import;
should.equal(wizard.isSummaryPage(2), true);
// summary page should be 2 for export
wizard.selectedOperation = Operation.export;
should.equal(wizard.isSummaryPage(2), true);
// summary page should be 2 for extract
wizard.selectedOperation = Operation.extract;
should.equal(wizard.isSummaryPage(2), true);
});
it('Should set Done button and operation correctly', async () => {
wizard.setDoneButton(Operation.deploy); wizard.setDoneButton(Operation.deploy);
should.equal(wizard.selectedOperation, Operation.deploy); should.equal(wizard.selectedOperation, Operation.deploy);
wizard.setDoneButton(Operation.generateDeployScript);
should.equal(wizard.selectedOperation, Operation.generateDeployScript);
wizard.setDoneButton(Operation.extract);
should.equal(wizard.selectedOperation, Operation.extract);
wizard.setDoneButton(Operation.import);
should.equal(wizard.selectedOperation, Operation.import);
wizard.setDoneButton(Operation.export);
should.equal(wizard.selectedOperation, Operation.export);
}); });
}); });

View File

@@ -79,7 +79,7 @@ export enum PageName {
export class DataTierApplicationWizard { export class DataTierApplicationWizard {
public wizard: azdata.window.Wizard; public wizard: azdata.window.Wizard;
private connection: azdata.connection.ConnectionProfile; private connection: azdata.connection.ConnectionProfile;
private model: DacFxDataModel; public model: DacFxDataModel;
public pages: Map<string, Page> = new Map<string, Page>(); public pages: Map<string, Page> = new Map<string, Page>();
public selectedOperation: Operation; public selectedOperation: Operation;
@@ -331,7 +331,7 @@ export class DataTierApplicationWizard {
return page; return page;
} }
private isSummaryPage(idx: number): boolean { public isSummaryPage(idx: number): boolean {
return this.selectedOperation === Operation.import && idx === ImportOperationPath.summary return this.selectedOperation === Operation.import && idx === ImportOperationPath.summary
|| this.selectedOperation === Operation.export && idx === ExportOperationPath.summary || this.selectedOperation === Operation.export && idx === ExportOperationPath.summary
|| this.selectedOperation === Operation.extract && idx === ExtractOperationPath.summary || this.selectedOperation === Operation.extract && idx === ExtractOperationPath.summary

View File

@@ -409,6 +409,11 @@
"command": "git.timeline.copyCommitMessage", "command": "git.timeline.copyCommitMessage",
"title": "%command.timelineCopyCommitMessage%", "title": "%command.timelineCopyCommitMessage%",
"category": "Git" "category": "Git"
},
{
"command": "git.rebaseAbort",
"title": "%command.rebaseAbort%",
"category": "Git"
} }
], ],
"keybindings": [ "keybindings": [

View File

@@ -63,6 +63,7 @@
"command.showOutput": "Show Git Output", "command.showOutput": "Show Git Output",
"command.ignore": "Add to .gitignore", "command.ignore": "Add to .gitignore",
"command.revealInExplorer": "Reveal in Side Bar", "command.revealInExplorer": "Reveal in Side Bar",
"command.rebaseAbort": "Abort Rebase",
"command.stashIncludeUntracked": "Stash (Include Untracked)", "command.stashIncludeUntracked": "Stash (Include Untracked)",
"command.stash": "Stash", "command.stash": "Stash",
"command.stashPop": "Pop Stash...", "command.stashPop": "Pop Stash...",

View File

@@ -2494,6 +2494,10 @@ export class CommandCenter {
env.clipboard.writeText(item.message); env.clipboard.writeText(item.message);
} }
@command('git.rebaseAbort', { repository: true })
async rebaseAbort(repository: Repository): Promise<void> {
await repository.rebaseAbort();
}
private createCommand(id: string, key: string, method: Function, options: CommandOptions): (...args: any[]) => any { private createCommand(id: string, key: string, method: Function, options: CommandOptions): (...args: any[]) => any {
const result = (...args: any[]) => { const result = (...args: any[]) => {

View File

@@ -1333,6 +1333,10 @@ export class Repository {
} }
} }
async rebaseAbort(): Promise<void> {
await this.run(['rebase', '--abort']);
}
async rebaseContinue(): Promise<void> { async rebaseContinue(): Promise<void> {
const args = ['rebase', '--continue']; const args = ['rebase', '--continue'];

View File

@@ -6,7 +6,7 @@
import * as nls from 'vscode-nls'; import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle(); const localize = nls.loadMessageBundle();
import { ExtensionContext, workspace, window, Disposable, commands, OutputChannel } from 'vscode'; // {{SQL CARBON EDIT}} - remove unused imports import { ExtensionContext, workspace, window, Disposable, commands, OutputChannel, Uri } from 'vscode';
import { findGit, Git, IGit } from './git'; import { findGit, Git, IGit } from './git';
import { Model } from './model'; import { Model } from './model';
import { CommandCenter } from './commands'; import { CommandCenter } from './commands';
@@ -78,7 +78,7 @@ async function createModel(context: ExtensionContext, outputChannel: OutputChann
new GitTimelineProvider(model) new GitTimelineProvider(model)
); );
await checkGitVersion(info); // await checkGitVersion(info); {{SQL CARBON EDIT}} Don't check git version
return model; return model;
} }
@@ -180,13 +180,8 @@ export async function activate(context: ExtensionContext): Promise<GitExtension>
return result; return result;
} }
async function checkGitVersion(_info: IGit): Promise<void> { // {{SQL CARBON EDIT}} - Rename info to _info to prevent error due to unused variable // @ts-expect-error
return; /* {{SQL CARBON EDIT}} return immediately async function checkGitVersion(info: IGit): Promise<void> {
/*const config = workspace.getConfiguration('git');
const shouldIgnore = config.get<boolean>('ignoreLegacyWarning') === true;
const config = workspace.getConfiguration('git'); const config = workspace.getConfiguration('git');
const shouldIgnore = config.get<boolean>('ignoreLegacyWarning') === true; const shouldIgnore = config.get<boolean>('ignoreLegacyWarning') === true;
@@ -211,5 +206,5 @@ async function checkGitVersion(_info: IGit): Promise<void> { // {{SQL CARBON EDI
commands.executeCommand('vscode.open', Uri.parse('https://git-scm.com/')); commands.executeCommand('vscode.open', Uri.parse('https://git-scm.com/'));
} else if (choice === neverShowAgain) { } else if (choice === neverShowAgain) {
await config.update('ignoreLegacyWarning', true, true); await config.update('ignoreLegacyWarning', true, true);
}*/ }
} }

View File

@@ -303,6 +303,7 @@ export const enum Operation {
CheckIgnore = 'CheckIgnore', CheckIgnore = 'CheckIgnore',
GetObjectDetails = 'GetObjectDetails', GetObjectDetails = 'GetObjectDetails',
SubmoduleUpdate = 'SubmoduleUpdate', SubmoduleUpdate = 'SubmoduleUpdate',
RebaseAbort = 'RebaseAbort',
RebaseContinue = 'RebaseContinue', RebaseContinue = 'RebaseContinue',
FindTrackingBranches = 'GetTracking', FindTrackingBranches = 'GetTracking',
Apply = 'Apply', Apply = 'Apply',
@@ -1331,6 +1332,10 @@ export class Repository implements Disposable {
}); });
} }
async rebaseAbort(): Promise<void> {
await this.run(Operation.RebaseAbort, async () => await this.repository.rebaseAbort());
}
checkIgnore(filePaths: string[]): Promise<Set<string>> { checkIgnore(filePaths: string[]): Promise<Set<string>> {
return this.run(Operation.CheckIgnore, () => { return this.run(Operation.CheckIgnore, () => {
return new Promise<Set<string>>((resolve, reject) => { return new Promise<Set<string>>((resolve, reject) => {

View File

@@ -38,14 +38,25 @@ export class FlatFileWizard {
let pages: Map<number, ImportPage> = new Map<number, ImportPage>(); let pages: Map<number, ImportPage> = new Map<number, ImportPage>();
let connectionId = (await azdata.connection.getCurrentConnection())?.connectionId ?? let currentConnection = await azdata.connection.getCurrentConnection();
(await azdata.connection.openConnectionDialog())?.connectionId;
if (!connectionId) { let connectionId: string;
vscode.window.showErrorMessage(localize('import.needConnection', "Please connect to a server before using this wizard."));
return; if (!currentConnection) {
connectionId = (await azdata.connection.openConnectionDialog(['MSSQL'])).connectionId;
if (!connectionId) {
vscode.window.showErrorMessage(localize('import.needConnection', "Please connect to a server before using this wizard."));
return;
}
} else {
if (currentConnection.providerId !== 'MSSQL') {
vscode.window.showErrorMessage(localize('import.needSQLConnection', "SQL Server Import extension does not support this type of connection"));
return;
}
connectionId = currentConnection.connectionId;
} }
model.serverId = connectionId; model.serverId = connectionId;
this.wizard = azdata.window.createWizard(localize('flatFileImport.wizardName', "Import flat file wizard")); this.wizard = azdata.window.createWizard(localize('flatFileImport.wizardName', "Import flat file wizard"));

View File

@@ -124,6 +124,9 @@ export class FileConfigPage extends ImportPage {
this.databaseDropdown.onValueChanged(async (db) => { this.databaseDropdown.onValueChanged(async (db) => {
this.model.database = (<azdata.CategoryValue>this.databaseDropdown.value).name; this.model.database = (<azdata.CategoryValue>this.databaseDropdown.value).name;
//this.populateTableNames(); //this.populateTableNames();
let connectionProvider = azdata.dataprotocol.getProvider<azdata.ConnectionProvider>(this.model.server.providerName, azdata.DataProviderType.ConnectionProvider);
let connectionUri = await azdata.connection.getUriForConnection(this.model.server.connectionId);
connectionProvider.changeDatabase(connectionUri, this.model.database);
this.populateSchemaDropdown(); this.populateSchemaDropdown();
}); });
@@ -146,13 +149,25 @@ export class FileConfigPage extends ImportPage {
return false; return false;
} }
let values = await this.getDatabaseValues(); let defaultServerDatabase = this.model.server.options.database;
this.model.database = values[0].name; let values: any[];
try {
values = await this.getDatabaseValues();
} catch (error) {
// This code is used in case of contained databases when the query will return an error.
console.log(error);
values = [{ displayName: defaultServerDatabase, name: defaultServerDatabase }];
this.databaseDropdown.editable = false;
}
this.model.database = defaultServerDatabase;
this.databaseDropdown.updateProperties({ this.databaseDropdown.updateProperties({
values: values values: values
}); });
this.databaseDropdown.value = { displayName: this.model.database, name: this.model.database };
this.databaseLoader.loading = false; this.databaseLoader.loading = false;
return true; return true;
@@ -266,11 +281,11 @@ export class FileConfigPage extends ImportPage {
private async populateSchemaDropdown(): Promise<boolean> { private async populateSchemaDropdown(): Promise<boolean> {
this.schemaLoader.loading = true; this.schemaLoader.loading = true;
let connectionUri = await azdata.connection.getUriForConnection(this.model.server.connectionId); let connectionUri = await azdata.connection.getUriForConnection(this.model.server.connectionId);
let queryProvider = azdata.dataprotocol.getProvider<azdata.QueryProvider>(this.model.server.providerName, azdata.DataProviderType.QueryProvider); let queryProvider = azdata.dataprotocol.getProvider<azdata.QueryProvider>(this.model.server.providerName, azdata.DataProviderType.QueryProvider);
const escapedQuotedDb = this.databaseDropdown.value ? `[${(<azdata.CategoryValue>this.databaseDropdown.value).name.replace(/]/g, ']]')}].` : ''; const query = `SELECT name FROM sys.schemas`;
const query = `SELECT name FROM ${escapedQuotedDb}sys.schemas`;
let results = await queryProvider.runQueryAndReturn(connectionUri, query); let results = await queryProvider.runQueryAndReturn(connectionUri, query);

View File

@@ -32,7 +32,7 @@ export class ProsePreviewPage extends ImportPage {
this.table = this.view.modelBuilder.table().withProperties<azdata.TableComponentProperties>({ this.table = this.view.modelBuilder.table().withProperties<azdata.TableComponentProperties>({
data: undefined, data: undefined,
columns: undefined, columns: undefined,
forceFitColumns: azdata.ColumnSizingMode.AutoFit forceFitColumns: azdata.ColumnSizingMode.DataFit
}).component(); }).component();
this.refresh = this.view.modelBuilder.button().withProperties({ this.refresh = this.view.modelBuilder.button().withProperties({
label: localize('flatFileImport.refresh', "Refresh"), label: localize('flatFileImport.refresh', "Refresh"),

View File

@@ -28,13 +28,11 @@
"devDependencies": { "devDependencies": {
"@types/chai": "3.4.34", "@types/chai": "3.4.34",
"@types/node": "^10.14.8", "@types/node": "^10.14.8",
"azure-keyvault": "^3.0.4",
"chai": "3.5.0", "chai": "3.5.0",
"mocha-junit-reporter": "^1.17.0", "mocha-junit-reporter": "^1.17.0",
"mocha-multi-reporters": "^1.1.7", "mocha-multi-reporters": "^1.1.7",
"ms-rest-azure": "^2.6.0",
"vscode": "1.1.5" "vscode": "1.1.5"
},
"dependencies": {
"azure-keyvault": "^3.0.4",
"ms-rest-azure": "^2.6.0"
} }
} }

View File

@@ -77,6 +77,12 @@ interface JSONSchemaSettings {
schema?: any; schema?: any;
} }
namespace SettingIds {
export const enableFormatter = 'json.format.enable';
export const enableSchemaDownload = 'json.schemaDownload.enable';
export const maxItemsComputed = 'json.maxItemsComputed';
}
let telemetryReporter: TelemetryReporter | undefined; let telemetryReporter: TelemetryReporter | undefined;
export function activate(context: ExtensionContext) { export function activate(context: ExtensionContext) {
@@ -107,10 +113,8 @@ export function activate(context: ExtensionContext) {
id: 'status.json.resolveError', id: 'status.json.resolveError',
name: localize('json.resolveError', "JSON: Schema Resolution Error"), name: localize('json.resolveError', "JSON: Schema Resolution Error"),
alignment: StatusBarAlignment.Right, alignment: StatusBarAlignment.Right,
priority: 0 priority: 0,
}); });
schemaResolutionErrorStatusBarItem.command = '_json.retryResolveSchema';
schemaResolutionErrorStatusBarItem.tooltip = localize('json.schemaResolutionErrorMessage', 'Unable to resolve schema.') + ' ' + localize('json.clickToRetry', 'Click to retry.');
schemaResolutionErrorStatusBarItem.text = '$(alert)'; schemaResolutionErrorStatusBarItem.text = '$(alert)';
toDispose.push(schemaResolutionErrorStatusBarItem); toDispose.push(schemaResolutionErrorStatusBarItem);
@@ -200,6 +204,7 @@ export function activate(context: ExtensionContext) {
toDispose.push(disposable); toDispose.push(disposable);
client.onReady().then(() => { client.onReady().then(() => {
const schemaDocuments: { [uri: string]: boolean } = {}; const schemaDocuments: { [uri: string]: boolean } = {};
let schemaDownloadEnabled = true;
// handle content request // handle content request
client.onRequest(VSCodeContentRequest.type, (uriPath: string) => { client.onRequest(VSCodeContentRequest.type, (uriPath: string) => {
@@ -208,12 +213,16 @@ export function activate(context: ExtensionContext) {
return Promise.reject(new Error(localize('untitled.schema', 'Unable to load {0}', uri.toString()))); return Promise.reject(new Error(localize('untitled.schema', 'Unable to load {0}', uri.toString())));
} }
if (uri.scheme !== 'http' && uri.scheme !== 'https') { if (uri.scheme !== 'http' && uri.scheme !== 'https') {
return workspace.openTextDocument(uri).then(doc => { if (schemaDownloadEnabled) {
schemaDocuments[uri.toString()] = true; return workspace.openTextDocument(uri).then(doc => {
return doc.getText(); schemaDocuments[uri.toString()] = true;
}, error => { return doc.getText();
return Promise.reject(error); }, error => {
}); return Promise.reject(error);
});
} else {
return Promise.reject(localize('schemaDownloadDisabled', 'Downloading schemas is disabled through setting \'{0}\'', SettingIds.enableSchemaDownload));
}
} else { } else {
if (telemetryReporter && uri.authority === 'schema.management.azure.com') { if (telemetryReporter && uri.authority === 'schema.management.azure.com') {
/* __GDPR__ /* __GDPR__
@@ -294,16 +303,61 @@ export function activate(context: ExtensionContext) {
client.sendNotification(SchemaAssociationNotification.type, getSchemaAssociations(context)); client.sendNotification(SchemaAssociationNotification.type, getSchemaAssociations(context));
}); });
// manually register / deregister format provider based on the `html.format.enable` setting avoiding issues with late registration. See #71652. // manually register / deregister format provider based on the `json.format.enable` setting avoiding issues with late registration. See #71652.
updateFormatterRegistration(); updateFormatterRegistration();
toDispose.push({ dispose: () => rangeFormatting && rangeFormatting.dispose() }); toDispose.push({ dispose: () => rangeFormatting && rangeFormatting.dispose() });
toDispose.push(workspace.onDidChangeConfiguration(e => e.affectsConfiguration('html.format.enable') && updateFormatterRegistration()));
updateSchemaDownloadSetting();
toDispose.push(workspace.onDidChangeConfiguration(e => {
if (e.affectsConfiguration(SettingIds.enableFormatter)) {
updateFormatterRegistration();
} else if (e.affectsConfiguration(SettingIds.enableSchemaDownload)) {
updateSchemaDownloadSetting();
}
}));
client.onNotification(ResultLimitReachedNotification.type, message => { client.onNotification(ResultLimitReachedNotification.type, message => {
window.showInformationMessage(`${message}\nUse setting 'json.maxItemsComputed' to configure the limit.`); window.showInformationMessage(`${message}\n${localize('configureLimit', 'Use setting \'{0}\' to configure the limit.', SettingIds.maxItemsComputed)}`);
}); });
function updateFormatterRegistration() {
const formatEnabled = workspace.getConfiguration().get(SettingIds.enableFormatter);
if (!formatEnabled && rangeFormatting) {
rangeFormatting.dispose();
rangeFormatting = undefined;
} else if (formatEnabled && !rangeFormatting) {
rangeFormatting = languages.registerDocumentRangeFormattingEditProvider(documentSelector, {
provideDocumentRangeFormattingEdits(document: TextDocument, range: Range, options: FormattingOptions, token: CancellationToken): ProviderResult<TextEdit[]> {
const params: DocumentRangeFormattingParams = {
textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(document),
range: client.code2ProtocolConverter.asRange(range),
options: client.code2ProtocolConverter.asFormattingOptions(options)
};
return client.sendRequest(DocumentRangeFormattingRequest.type, params, token).then(
client.protocol2CodeConverter.asTextEdits,
(error) => {
client.logFailedRequest(DocumentRangeFormattingRequest.type, error);
return Promise.resolve([]);
}
);
}
});
}
}
function updateSchemaDownloadSetting() {
schemaDownloadEnabled = workspace.getConfiguration().get(SettingIds.enableSchemaDownload) !== false;
if (schemaDownloadEnabled) {
schemaResolutionErrorStatusBarItem.tooltip = localize('json.schemaResolutionErrorMessage', 'Unable to resolve schema. Click to retry.');
schemaResolutionErrorStatusBarItem.command = '_json.retryResolveSchema';
handleRetryResolveSchemaCommand();
} else {
schemaResolutionErrorStatusBarItem.tooltip = localize('json.schemaResolutionDisabledMessage', 'Downloading schemas is disabled. Click to configure.');
schemaResolutionErrorStatusBarItem.command = { command: 'workbench.action.openSettings', arguments: [SettingIds.enableSchemaDownload], title: '' };
}
}
}); });
const languageConfiguration: LanguageConfiguration = { const languageConfiguration: LanguageConfiguration = {
@@ -316,30 +370,6 @@ export function activate(context: ExtensionContext) {
languages.setLanguageConfiguration('json', languageConfiguration); languages.setLanguageConfiguration('json', languageConfiguration);
languages.setLanguageConfiguration('jsonc', languageConfiguration); languages.setLanguageConfiguration('jsonc', languageConfiguration);
function updateFormatterRegistration() {
const formatEnabled = workspace.getConfiguration().get('json.format.enable');
if (!formatEnabled && rangeFormatting) {
rangeFormatting.dispose();
rangeFormatting = undefined;
} else if (formatEnabled && !rangeFormatting) {
rangeFormatting = languages.registerDocumentRangeFormattingEditProvider(documentSelector, {
provideDocumentRangeFormattingEdits(document: TextDocument, range: Range, options: FormattingOptions, token: CancellationToken): ProviderResult<TextEdit[]> {
const params: DocumentRangeFormattingParams = {
textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(document),
range: client.code2ProtocolConverter.asRange(range),
options: client.code2ProtocolConverter.asFormattingOptions(options)
};
return client.sendRequest(DocumentRangeFormattingRequest.type, params, token).then(
client.protocol2CodeConverter.asTextEdits,
(error) => {
client.logFailedRequest(DocumentRangeFormattingRequest.type, error);
return Promise.resolve([]);
}
);
}
});
}
}
} }
@@ -386,7 +416,7 @@ function getSchemaAssociations(_context: ExtensionContext): ISchemaAssociation[]
function getSettings(): Settings { function getSettings(): Settings {
const httpSettings = workspace.getConfiguration('http'); const httpSettings = workspace.getConfiguration('http');
const resultLimit: number = Math.trunc(Math.max(0, Number(workspace.getConfiguration().get('json.maxItemsComputed')))) || 5000; const resultLimit: number = Math.trunc(Math.max(0, Number(workspace.getConfiguration().get(SettingIds.maxItemsComputed)))) || 5000;
const settings: Settings = { const settings: Settings = {
http: { http: {

View File

@@ -95,7 +95,13 @@
"type": "number", "type": "number",
"default": 5000, "default": 5000,
"description": "%json.maxItemsComputed.desc%" "description": "%json.maxItemsComputed.desc%"
} },
"json.schemaDownload.enable": {
"type": "boolean",
"default": true,
"description": "%json.enableSchemaDownload.desc%",
"tags": ["usesOnlineServices"]
}
} }
}, },
"configurationDefaults": { "configurationDefaults": {

View File

@@ -12,5 +12,6 @@
"json.colorDecorators.enable.deprecationMessage": "The setting `json.colorDecorators.enable` has been deprecated in favor of `editor.colorDecorators`.", "json.colorDecorators.enable.deprecationMessage": "The setting `json.colorDecorators.enable` has been deprecated in favor of `editor.colorDecorators`.",
"json.schemaResolutionErrorMessage": "Unable to resolve schema.", "json.schemaResolutionErrorMessage": "Unable to resolve schema.",
"json.clickToRetry": "Click to retry.", "json.clickToRetry": "Click to retry.",
"json.maxItemsComputed.desc": "The maximum number of outline symbols and folding regions computed (limited for performance reasons)." "json.maxItemsComputed.desc": "The maximum number of outline symbols and folding regions computed (limited for performance reasons).",
"json.enableSchemaDownload.desc": "When enabled, JSON schemas can be fetched from http and https locations."
} }

View File

@@ -9,12 +9,25 @@
"requiredRPackages": [ "requiredRPackages": [
{ {
"name": "RODBCext", "name": "RODBCext",
"repository": "https://cran.microsoft.com" "repository": "https://mran.microsoft.com/snapshot/2019-02-01/"
}, },
{ {
"name": "sqlmlutils", "name": "sqlmlutils",
"fileName": "sqlmlutils_0.7.1.zip", "fileName": "sqlmlutils_0.7.1.zip",
"downloadUrl": "https://github.com/microsoft/sqlmlutils/blob/master/R/dist/sqlmlutils_0.7.1.zip?raw=true" "downloadUrl": "https://github.com/microsoft/sqlmlutils/blob/master/R/dist/sqlmlutils_0.7.1.zip?raw=true",
"platform" : "win32"
},
{
"name": "sqlmlutils",
"fileName": "sqlmlutils_0.7.1.tar.gz",
"downloadUrl": "https://github.com/microsoft/sqlmlutils/blob/master/R/dist/sqlmlutils_0.7.1.tar.gz?raw=true",
"platform" : "darwin"
},
{
"name": "sqlmlutils",
"fileName": "sqlmlutils_0.7.1.tar.gz",
"downloadUrl": "https://github.com/microsoft/sqlmlutils/blob/master/R/dist/sqlmlutils_0.7.1.tar.gz?raw=true",
"platform" : "linux"
} }
], ],
"rPackagesRepository": "https://cran.r-project.org" "rPackagesRepository": "https://cran.r-project.org"

View File

@@ -32,4 +32,9 @@ export interface PackageConfigModel {
* Package file name if package has download url * Package file name if package has download url
*/ */
fileName?: string; fileName?: string;
/**
* Package platform (Windows, Mac, Linux)
*/
platform?: string;
} }

View File

@@ -67,7 +67,6 @@ export class PackageManager {
*/ */
public async managePackages(): Promise<void> { public async managePackages(): Promise<void> {
try { try {
await this.enableExternalScript();
// Only execute the command if there's a valid connection with ml configuration enabled // Only execute the command if there's a valid connection with ml configuration enabled
// //
@@ -129,7 +128,8 @@ export class PackageManager {
} }
await utils.createFolder(utils.getRPackagesFolderPath(this._rootFolder)); await utils.createFolder(utils.getRPackagesFolderPath(this._rootFolder));
await Promise.all(this._config.requiredSqlRPackages.map(x => this.installRPackage(x))); const packages = this._config.requiredSqlRPackages.filter(p => !p.platform || p.platform === process.platform);
await Promise.all(packages.map(x => this.installRPackage(x)));
} }
/** /**

View File

@@ -75,7 +75,7 @@ export class SqlRPackageManageProvider extends SqlPackageManageProviderBase impl
let scripts: string[] = [ let scripts: string[] = [
'formals(quit)$save <- formals(q)$save <- "no"', 'formals(quit)$save <- formals(q)$save <- "no"',
'library(sqlmlutils)', 'library(sqlmlutils)',
`connection <- connectionInfo(${connectionParts})`, `connection <- connectionInfo(driver= "ODBC Driver 17 for SQL Server", ${connectionParts})`,
`r = getOption("repos")`, `r = getOption("repos")`,
`r["CRAN"] = "${this._config.rPackagesRepository}"`, `r["CRAN"] = "${this._config.rPackagesRepository}"`,
`options(repos = r)`, `options(repos = r)`,

View File

@@ -90,7 +90,8 @@ function createContext(): TestContext {
storagePath: '', storagePath: '',
globalStoragePath: '', globalStoragePath: '',
logPath: '', logPath: '',
extensionUri: vscode.Uri.parse('') extensionUri: vscode.Uri.parse(''),
environmentVariableCollection: { } as any
}, },
outputChannel: { outputChannel: {
name: '', name: '',

View File

@@ -51,7 +51,8 @@ export function createViewContext(): ViewTestContext {
removeItem: () => true, removeItem: () => true,
insertItem: () => { }, insertItem: () => { },
items: [], items: [],
setLayout: () => { } setLayout: () => { },
setItemLayout: () => { }
}; };
let form: azdata.FormContainer = Object.assign({}, componentBase, container, { let form: azdata.FormContainer = Object.assign({}, componentBase, container, {
}); });

View File

@@ -133,7 +133,6 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview
webview: vscode.WebviewPanel, webview: vscode.WebviewPanel,
state: any state: any
): Promise<void> { ): Promise<void> {
console.log(state);
const resource = vscode.Uri.parse(state.resource); const resource = vscode.Uri.parse(state.resource);
const locked = state.locked; const locked = state.locked;
const line = state.line; const line = state.line;

View File

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

View File

@@ -366,6 +366,28 @@
"displayName": "%onprem.serverProperties.osVersion%", "displayName": "%onprem.serverProperties.osVersion%",
"value": "osVersion" "value": "osVersion"
} }
],
"databasesListProperties": [
{
"displayName": "%databasesListProperties.name%",
"value": "name",
"widthWeight": 60
},
{
"displayName": "%databasesListProperties.status%",
"value": "state",
"widthWeight": 10
},
{
"displayName": "%databasesListProperties.size%",
"value": "sizeInMB",
"widthWeight": 10
},
{
"displayName": "%databasesListProperties.lastBackup%",
"value": "lastBackup",
"widthWeight": 20
}
] ]
}, },
{ {
@@ -404,6 +426,23 @@
"displayName": "%cloud.serverProperties.serverEdition%", "displayName": "%cloud.serverProperties.serverEdition%",
"value": "serverEdition" "value": "serverEdition"
} }
],
"databasesListProperties": [
{
"displayName": "%databasesListProperties.name%",
"value": "name",
"widthWeight": 60
},
{
"displayName": "%databasesListProperties.status%",
"value": "state",
"widthWeight": 20
},
{
"displayName": "%databasesListProperties.size%",
"value": "sizeInMB",
"widthWeight": 20
}
] ]
}, },
{ {
@@ -434,6 +473,13 @@
"displayName": "%cloud.serverProperties.serverEdition%", "displayName": "%cloud.serverProperties.serverEdition%",
"value": "serverEdition" "value": "serverEdition"
} }
],
"databasesListProperties": [
{
"displayName": "%databasesListProperties.name%",
"value": "name",
"widthWeight": 100
}
] ]
} }
] ]
@@ -1043,7 +1089,7 @@
"figures": "^2.0.0", "figures": "^2.0.0",
"find-remove": "1.2.1", "find-remove": "1.2.1",
"request": "^2.88.0", "request": "^2.88.0",
"request-promise": "^4.2.2", "request-light": "^0.3.0",
"service-downloader": "0.2.1", "service-downloader": "0.2.1",
"stream-meter": "^1.0.4", "stream-meter": "^1.0.4",
"through2": "^3.0.1", "through2": "^3.0.1",
@@ -1057,7 +1103,6 @@
"@types/chai": "^4.2.11", "@types/chai": "^4.2.11",
"@types/mocha": "^7.0.2", "@types/mocha": "^7.0.2",
"@types/request": "^2.48.2", "@types/request": "^2.48.2",
"@types/request-promise": "^4.1.44",
"@types/stream-meter": "^0.0.22", "@types/stream-meter": "^0.0.22",
"@types/through2": "^2.0.34", "@types/through2": "^2.0.34",
"chai": "^4.2.0", "chai": "^4.2.0",

View File

@@ -140,5 +140,10 @@
"mssql.connectionOptions.packetSize.displayName": "Packet size", "mssql.connectionOptions.packetSize.displayName": "Packet size",
"mssql.connectionOptions.packetSize.description": "Size in bytes of the network packets used to communicate with an instance of SQL Server", "mssql.connectionOptions.packetSize.description": "Size in bytes of the network packets used to communicate with an instance of SQL Server",
"mssql.connectionOptions.typeSystemVersion.displayName": "Type system version", "mssql.connectionOptions.typeSystemVersion.displayName": "Type system version",
"mssql.connectionOptions.typeSystemVersion.description": "Indicates which server type system the provider will expose through the DataReader" "mssql.connectionOptions.typeSystemVersion.description": "Indicates which server type system the provider will expose through the DataReader",
"databasesListProperties.name": "Name",
"databasesListProperties.status": "Status",
"databasesListProperties.size": "Size (MB)",
"databasesListProperties.lastBackup": "Last backup",
"objectsListProperties.name": "Name"
} }

View File

@@ -39,6 +39,7 @@ export const SchemaCompareService = 'schemaCompareService';
export const LanguageExtensionService = 'languageExtensionService'; export const LanguageExtensionService = 'languageExtensionService';
export const objectExplorerPrefix: string = 'objectexplorer://'; export const objectExplorerPrefix: string = 'objectexplorer://';
export const ViewType = 'view'; export const ViewType = 'view';
export const SqlAssessmentService = 'sqlAssessmentService';
export enum BuiltInCommands { export enum BuiltInCommands {
SetContext = 'setContext' SetContext = 'setContext'

View File

@@ -674,6 +674,35 @@ export namespace SchemaCompareCancellationRequest {
// ------------------------------- <Schema Compare> ----------------------------- // ------------------------------- <Schema Compare> -----------------------------
// ------------------------------- <Sql Assessment> -----------------------------
export interface SqlAssessmentParams {
ownerUri: string;
targetType: mssql.SqlAssessmentTargetType
}
export interface GenerateSqlAssessmentScriptParams {
items: mssql.SqlAssessmentResultItem[];
taskExecutionMode: azdata.TaskExecutionMode;
targetServerName: string;
targetDatabaseName: string;
}
export namespace SqlAssessmentInvokeRequest {
export const type = new RequestType<SqlAssessmentParams, mssql.SqlAssessmentResult, void, void>('assessment/invoke');
}
export namespace GetSqlAssessmentItemsRequest {
export const type = new RequestType<SqlAssessmentParams, mssql.SqlAssessmentResult, void, void>('assessment/getAssessmentItems');
}
export namespace GenerateSqlAssessmentScriptRequest {
export const type = new RequestType<GenerateSqlAssessmentScriptParams, azdata.ResultStatus, void, void>('assessment/generateScript');
}
// ------------------------------- <Sql Assessment> -----------------------------
// ------------------------------- <Serialization> ----------------------------- // ------------------------------- <Serialization> -----------------------------
export namespace SerializeDataStartRequest { export namespace SerializeDataStartRequest {
export const type = new RequestType<azdata.SerializeDataStartRequestParams, azdata.SerializeDataResult, void, void>('serialize/start'); export const type = new RequestType<azdata.SerializeDataStartRequestParams, azdata.SerializeDataResult, void, void>('serialize/start');

View File

@@ -41,6 +41,8 @@ export interface IExtension {
readonly languageExtension: ILanguageExtensionService; readonly languageExtension: ILanguageExtensionService;
readonly dacFx: IDacFxService; readonly dacFx: IDacFxService;
readonly sqlAssessment: ISqlAssessmentService;
} }
/** /**
@@ -463,3 +465,47 @@ export interface ListRegisteredServersResult {
registeredServerGroups: Array<RegisteredServerGroup>; registeredServerGroups: Array<RegisteredServerGroup>;
} }
//#endregion //#endregion
/**
* Sql Assessment
*/
// SqlAssessment interfaces -----------------------------------------------------------------------
export const enum SqlAssessmentTargetType {
Server = 1,
Database = 2
}
export const enum SqlAssessmentResultItemKind {
RealResult = 0,
Warning = 1,
Error = 2
}
export interface SqlAssessmentResultItem {
rulesetVersion: string;
rulesetName: string;
targetType: SqlAssessmentTargetType;
targetName: string;
checkId: string;
tags: string[];
displayName: string;
description: string;
message: string;
helpLink: string;
level: string;
timestamp: string;
kind: SqlAssessmentResultItemKind;
}
export interface SqlAssessmentResult extends azdata.ResultStatus {
items: SqlAssessmentResultItem[];
apiVersion: string;
}
export interface ISqlAssessmentService {
assessmentInvoke(ownerUri: string, targetType: SqlAssessmentTargetType): Promise<SqlAssessmentResult>;
getAssessmentItems(ownerUri: string, targetType: SqlAssessmentTargetType): Promise<SqlAssessmentResult>;
generateAssessmentScript(items: SqlAssessmentResultItem[], targetServerName: string, targetDatabaseName: string, taskExecutionMode: azdata.TaskExecutionMode): Promise<azdata.ResultStatus>;
}

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { AppContext } from './appContext'; import { AppContext } from './appContext';
import { IExtension, ICmsService, IDacFxService, ISchemaCompareService, MssqlObjectExplorerBrowser, ILanguageExtensionService } from './mssql'; import { IExtension, ICmsService, IDacFxService, ISchemaCompareService, MssqlObjectExplorerBrowser, ILanguageExtensionService, ISqlAssessmentService } from './mssql';
import * as constants from './constants'; import * as constants from './constants';
import { MssqlObjectExplorerNodeProvider } from './objectExplorerNodeProvider/objectExplorerNodeProvider'; import { MssqlObjectExplorerNodeProvider } from './objectExplorerNodeProvider/objectExplorerNodeProvider';
import * as azdata from 'azdata'; import * as azdata from 'azdata';
@@ -30,6 +30,9 @@ export function createMssqlApi(context: AppContext): IExtension {
return <any>oeProvider.findSqlClusterNodeByContext(explorerContext); return <any>oeProvider.findSqlClusterNodeByContext(explorerContext);
} }
}; };
},
get sqlAssessment() {
return context.getService<ISqlAssessmentService>(constants.SqlAssessmentService);
} }
}; };
} }

View File

@@ -37,15 +37,14 @@ export class SparkJobSubmissionModel {
constructor( constructor(
private readonly _sqlClusterConnection: SqlClusterConnection, private readonly _sqlClusterConnection: SqlClusterConnection,
private readonly _dialog: azdata.window.Dialog, private readonly _dialog: azdata.window.Dialog,
private readonly _appContext: AppContext, private readonly _appContext: AppContext) {
requestService?: typeof import('request-promise')) {
if (!this._sqlClusterConnection || !this._dialog || !this._appContext) { if (!this._sqlClusterConnection || !this._dialog || !this._appContext) {
throw new Error(localize('sparkJobSubmission.SparkJobSubmissionModelInitializeError', throw new Error(localize('sparkJobSubmission.SparkJobSubmissionModelInitializeError',
"Parameters for SparkJobSubmissionModel is illegal")); "Parameters for SparkJobSubmissionModel is illegal"));
} }
this._dialogService = new SparkJobSubmissionService(requestService); this._dialogService = new SparkJobSubmissionService();
this._guidForClusterFolder = utils.generateGuid(); this._guidForClusterFolder = utils.generateGuid();
} }

View File

@@ -10,21 +10,9 @@ import * as constants from '../../../constants';
import { SqlClusterConnection } from '../../../objectExplorerNodeProvider/connection'; import { SqlClusterConnection } from '../../../objectExplorerNodeProvider/connection';
import * as utils from '../../../utils'; import * as utils from '../../../utils';
import * as auth from '../../../util/auth'; import * as auth from '../../../util/auth';
import { Options } from 'request-promise'; import * as request from 'request-light';
export class SparkJobSubmissionService { export class SparkJobSubmissionService {
private _requestPromise: typeof import('request-promise');
constructor(
requestService?: typeof import('request-promise')) {
if (requestService) {
// this is to fake the request service for test.
this._requestPromise = requestService;
} else {
this._requestPromise = require('request-promise');
}
}
public async submitBatchJob(submissionArgs: SparkJobSubmissionInput): Promise<string> { public async submitBatchJob(submissionArgs: SparkJobSubmissionInput): Promise<string> {
try { try {
let livyUrl: string = `https://${submissionArgs.host}:${submissionArgs.port}${submissionArgs.livyPath}/`; let livyUrl: string = `https://${submissionArgs.host}:${submissionArgs.port}${submissionArgs.livyPath}/`;
@@ -32,12 +20,11 @@ export class SparkJobSubmissionService {
// Get correct authentication headers // Get correct authentication headers
let headers = await this.getAuthenticationHeaders(submissionArgs); let headers = await this.getAuthenticationHeaders(submissionArgs);
let options: Options = { let options: request.XHROptions = {
uri: livyUrl, url: livyUrl,
method: 'POST', type: 'POST',
json: true, strictSSL: !auth.getIgnoreSslVerificationConfigSetting(),
rejectUnauthorized: !auth.getIgnoreSslVerificationConfigSetting(), data: {
body: {
file: submissionArgs.sparkFile, file: submissionArgs.sparkFile,
proxyUser: submissionArgs.user, proxyUser: submissionArgs.user,
className: submissionArgs.mainClass, className: submissionArgs.mainClass,
@@ -51,7 +38,7 @@ export class SparkJobSubmissionService {
if (submissionArgs.jobArguments && submissionArgs.jobArguments.trim()) { if (submissionArgs.jobArguments && submissionArgs.jobArguments.trim()) {
let argsList = submissionArgs.jobArguments.split(' '); let argsList = submissionArgs.jobArguments.split(' ');
if (argsList.length > 0) { if (argsList.length > 0) {
options.body['args'] = argsList; options.data['args'] = argsList;
} }
} }
@@ -59,7 +46,7 @@ export class SparkJobSubmissionService {
if (submissionArgs.jarFileList && submissionArgs.jarFileList.trim()) { if (submissionArgs.jarFileList && submissionArgs.jarFileList.trim()) {
let jarList = submissionArgs.jarFileList.split(';'); let jarList = submissionArgs.jarFileList.split(';');
if (jarList.length > 0) { if (jarList.length > 0) {
options.body['jars'] = jarList; options.data['jars'] = jarList;
} }
} }
@@ -67,7 +54,7 @@ export class SparkJobSubmissionService {
if (submissionArgs.pyFileList && submissionArgs.pyFileList.trim()) { if (submissionArgs.pyFileList && submissionArgs.pyFileList.trim()) {
let pyList = submissionArgs.pyFileList.split(';'); let pyList = submissionArgs.pyFileList.split(';');
if (pyList.length > 0) { if (pyList.length > 0) {
options.body['pyFiles'] = pyList; options.data['pyFiles'] = pyList;
} }
} }
@@ -75,11 +62,17 @@ export class SparkJobSubmissionService {
if (submissionArgs.otherFileList && submissionArgs.otherFileList.trim()) { if (submissionArgs.otherFileList && submissionArgs.otherFileList.trim()) {
let otherList = submissionArgs.otherFileList.split(';'); let otherList = submissionArgs.otherFileList.split(';');
if (otherList.length > 0) { if (otherList.length > 0) {
options.body['files'] = otherList; options.data['files'] = otherList;
} }
} }
const response = await this._requestPromise(options); options.data = JSON.stringify(options.data);
// Note this is currently required to be called each time since request-light is overwriting
// the setting passed in through the options. If/when that gets fixed this can be removed
request.configure(null, !auth.getIgnoreSslVerificationConfigSetting());
const response = JSON.parse((await request.xhr(options)).responseText);
if (response && utils.isValidNumber(response.id)) { if (response && utils.isValidNumber(response.id)) {
return response.id; return response.id;
} }
@@ -108,16 +101,19 @@ export class SparkJobSubmissionService {
let livyUrl = `https://${submissionArgs.host}:${submissionArgs.port}${submissionArgs.livyPath}/${livyBatchId}/log`; let livyUrl = `https://${submissionArgs.host}:${submissionArgs.port}${submissionArgs.livyPath}/${livyBatchId}/log`;
let headers = await this.getAuthenticationHeaders(submissionArgs); let headers = await this.getAuthenticationHeaders(submissionArgs);
let options = { let options: request.XHROptions = {
uri: livyUrl, url: livyUrl,
method: 'GET', type: 'GET',
json: true, strictSSL: !auth.getIgnoreSslVerificationConfigSetting(),
rejectUnauthorized: !auth.getIgnoreSslVerificationConfigSetting(),
// authentication headers // authentication headers
headers: headers headers: headers
}; };
const response = await this._requestPromise(options); // Note this is currently required to be called each time since request-light is overwriting
// the setting passed in through the options. If/when that gets fixed this can be removed
request.configure(null, !auth.getIgnoreSslVerificationConfigSetting());
const response = JSON.parse((await request.xhr(options)).responseText);
if (response && response.log) { if (response && response.log) {
return this.extractYarnAppIdFromLog(response.log); return this.extractYarnAppIdFromLog(response.log);
} }

View File

@@ -0,0 +1,67 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as mssql from '../mssql';
import { AppContext } from '../appContext';
import { SqlOpsDataClient, ISqlOpsFeature } from 'dataprotocol-client';
import { ClientCapabilities } from 'vscode-languageclient';
import * as constants from '../constants';
import * as azdata from 'azdata';
import * as contracts from '../contracts';
export class SqlAssessmentService implements mssql.ISqlAssessmentService {
public static asFeature(context: AppContext): ISqlOpsFeature {
return class extends SqlAssessmentService {
constructor(client: SqlOpsDataClient) {
super(context, client);
}
fillClientCapabilities(capabilities: ClientCapabilities): void {
}
initialize(): void {
}
};
}
private constructor(context: AppContext, protected readonly client: SqlOpsDataClient) {
context.registerService(constants.SqlAssessmentService, this);
}
async assessmentInvoke(ownerUri: string, targetType: mssql.SqlAssessmentTargetType): Promise<mssql.SqlAssessmentResult | undefined> {
let params: contracts.SqlAssessmentParams = { ownerUri: ownerUri, targetType: targetType };
try {
return this.client.sendRequest(contracts.SqlAssessmentInvokeRequest.type, params);
}
catch (e) {
this.client.logFailedRequest(contracts.SqlAssessmentInvokeRequest.type, e);
}
return undefined;
}
async getAssessmentItems(ownerUri: string, targetType: mssql.SqlAssessmentTargetType): Promise<mssql.SqlAssessmentResult | undefined> {
let params: contracts.SqlAssessmentParams = { ownerUri: ownerUri, targetType: targetType };
try {
return this.client.sendRequest(contracts.GetSqlAssessmentItemsRequest.type, params);
}
catch (e) {
this.client.logFailedRequest(contracts.GetSqlAssessmentItemsRequest.type, e);
}
return undefined;
}
async generateAssessmentScript(items: mssql.SqlAssessmentResultItem[], targetServerName: string, targetDatabaseName: string, taskExecutionMode: azdata.TaskExecutionMode): Promise<azdata.ResultStatus | undefined> {
let params: contracts.GenerateSqlAssessmentScriptParams = { items: items, targetServerName: targetServerName, targetDatabaseName: targetDatabaseName, taskExecutionMode: taskExecutionMode };
try {
return this.client.sendRequest(contracts.GenerateSqlAssessmentScriptRequest.type, params);
}
catch (e) {
this.client.logFailedRequest(contracts.GenerateSqlAssessmentScriptRequest.type, e);
}
return undefined;
}
}

View File

@@ -22,6 +22,7 @@ import { CompletionExtensionParams, CompletionExtLoadRequest } from './contracts
import { promises as fs } from 'fs'; import { promises as fs } from 'fs';
import * as nls from 'vscode-nls'; import * as nls from 'vscode-nls';
import { LanguageExtensionService } from './languageExtension/languageExtensionService'; import { LanguageExtensionService } from './languageExtension/languageExtensionService';
import { SqlAssessmentService } from './sqlAssessment/sqlAssessmentService';
const localize = nls.loadMessageBundle(); const localize = nls.loadMessageBundle();
const outputChannel = vscode.window.createOutputChannel(Constants.serviceName); const outputChannel = vscode.window.createOutputChannel(Constants.serviceName);
@@ -157,7 +158,8 @@ function getClientOptions(context: AppContext): ClientOptions {
SchemaCompareService.asFeature(context), SchemaCompareService.asFeature(context),
LanguageExtensionService.asFeature(context), LanguageExtensionService.asFeature(context),
DacFxService.asFeature(context), DacFxService.asFeature(context),
CmsService.asFeature(context) CmsService.asFeature(context),
SqlAssessmentService.asFeature(context)
], ],
outputChannel: new CustomOutputChannel() outputChannel: new CustomOutputChannel()
}; };

View File

@@ -2,11 +2,6 @@
# yarn lockfile v1 # yarn lockfile v1
"@types/bluebird@*":
version "3.5.30"
resolved "https://registry.yarnpkg.com/@types/bluebird/-/bluebird-3.5.30.tgz#ee034a0eeea8b84ed868b1aa60d690b08a6cfbc5"
integrity sha512-8LhzvcjIoqoi1TghEkRMkbbmM+jhHnBokPGkJWjclMK+Ks0MxEBow3/p2/iFTZ+OIbJHQDSfpgdZEb+af3gfVw==
"@types/bytes@^3.0.0": "@types/bytes@^3.0.0":
version "3.1.0" version "3.1.0"
resolved "https://registry.yarnpkg.com/@types/bytes/-/bytes-3.1.0.tgz#835a3e4aea3b4d7604aca216a78de372bff3ecc3" resolved "https://registry.yarnpkg.com/@types/bytes/-/bytes-3.1.0.tgz#835a3e4aea3b4d7604aca216a78de372bff3ecc3"
@@ -32,15 +27,7 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-13.11.1.tgz#49a2a83df9d26daacead30d0ccc8762b128d53c7" resolved "https://registry.yarnpkg.com/@types/node/-/node-13.11.1.tgz#49a2a83df9d26daacead30d0ccc8762b128d53c7"
integrity sha512-eWQGP3qtxwL8FGneRrC5DwrJLGN4/dH1clNTuLfN81HCrxVtxRjygDTUoZJ5ASlDEeo0ppYFQjQIlXhtXpOn6g== integrity sha512-eWQGP3qtxwL8FGneRrC5DwrJLGN4/dH1clNTuLfN81HCrxVtxRjygDTUoZJ5ASlDEeo0ppYFQjQIlXhtXpOn6g==
"@types/request-promise@^4.1.44": "@types/request@^2.48.2":
version "4.1.46"
resolved "https://registry.yarnpkg.com/@types/request-promise/-/request-promise-4.1.46.tgz#37df6efae984316dfbfbbe8fcda37f3ba52822f2"
integrity sha512-3Thpj2Va5m0ji3spaCk8YKrjkZyZc6RqUVOphA0n/Xet66AW/AiOAs5vfXhQIL5NmkaO7Jnun7Nl9NEjJ2zBaw==
dependencies:
"@types/bluebird" "*"
"@types/request" "*"
"@types/request@*", "@types/request@^2.48.2":
version "2.48.4" version "2.48.4"
resolved "https://registry.yarnpkg.com/@types/request/-/request-2.48.4.tgz#df3d43d7b9ed3550feaa1286c6eabf0738e6cf7e" resolved "https://registry.yarnpkg.com/@types/request/-/request-2.48.4.tgz#df3d43d7b9ed3550feaa1286c6eabf0738e6cf7e"
integrity sha512-W1t1MTKYR8PxICH+A4HgEIPuAC3sbljoEVfyZbeFJJDbr30guDspJri2XOaM2E+Un7ZjrihaDi7cf6fPa2tbgw== integrity sha512-W1t1MTKYR8PxICH+A4HgEIPuAC3sbljoEVfyZbeFJJDbr30guDspJri2XOaM2E+Un7ZjrihaDi7cf6fPa2tbgw==
@@ -257,11 +244,6 @@ binary-extensions@^2.0.0:
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.0.0.tgz#23c0df14f6a88077f5f986c0d167ec03c3d5537c" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.0.0.tgz#23c0df14f6a88077f5f986c0d167ec03c3d5537c"
integrity sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow== integrity sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==
bluebird@^3.5.0:
version "3.7.2"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==
brace-expansion@^1.1.7: brace-expansion@^1.1.7:
version "1.1.11" version "1.1.11"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
@@ -893,7 +875,7 @@ http-signature@~1.2.0:
jsprim "^1.2.2" jsprim "^1.2.2"
sshpk "^1.7.0" sshpk "^1.7.0"
https-proxy-agent@^2.2.3: https-proxy-agent@^2.2.3, https-proxy-agent@^2.2.4:
version "2.2.4" version "2.2.4"
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz#4ee7a737abd92678a293d9b34a1af4d0d08c787b" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz#4ee7a737abd92678a293d9b34a1af4d0d08c787b"
integrity sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg== integrity sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==
@@ -1493,22 +1475,14 @@ remap-istanbul@^0.11.1:
source-map "^0.6.1" source-map "^0.6.1"
through2 "2.0.1" through2 "2.0.1"
request-promise-core@1.1.3: request-light@^0.3.0:
version "1.1.3" version "0.3.0"
resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.3.tgz#e9a3c081b51380dfea677336061fea879a829ee9" resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.3.0.tgz#04daa783e7f0a70392328dda4b546f3e27845f2d"
integrity sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ== integrity sha512-xlVlZVT0ZvCT+c3zm3SjeFCzchoQxsUUmx5fkal0I6RIDJK+lmb1UYyKJ7WM4dTfnzHP4ElWwAf8Dli8c0/tVA==
dependencies: dependencies:
lodash "^4.17.15" http-proxy-agent "^2.1.0"
https-proxy-agent "^2.2.4"
request-promise@^4.2.2: vscode-nls "^4.1.1"
version "4.2.5"
resolved "https://registry.yarnpkg.com/request-promise/-/request-promise-4.2.5.tgz#186222c59ae512f3497dfe4d75a9c8461bd0053c"
integrity sha512-ZgnepCykFdmpq86fKGwqntyTiUrHycALuGggpyCZwMvGaZWgxW6yagT0FHkgo5LzYvOaCNvxYwWYIjevSH1EDg==
dependencies:
bluebird "^3.5.0"
request-promise-core "1.1.3"
stealthy-require "^1.1.1"
tough-cookie "^2.3.3"
request@^2.88.0: request@^2.88.0:
version "2.88.2" version "2.88.2"
@@ -1642,11 +1616,6 @@ sshpk@^1.7.0:
safer-buffer "^2.0.2" safer-buffer "^2.0.2"
tweetnacl "~0.14.0" tweetnacl "~0.14.0"
stealthy-require@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b"
integrity sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=
stream-meter@^1.0.4: stream-meter@^1.0.4:
version "1.0.4" version "1.0.4"
resolved "https://registry.yarnpkg.com/stream-meter/-/stream-meter-1.0.4.tgz#52af95aa5ea760a2491716704dbff90f73afdd1d" resolved "https://registry.yarnpkg.com/stream-meter/-/stream-meter-1.0.4.tgz#52af95aa5ea760a2491716704dbff90f73afdd1d"
@@ -1812,14 +1781,6 @@ to-regex-range@^5.0.1:
dependencies: dependencies:
is-number "^7.0.0" is-number "^7.0.0"
tough-cookie@^2.3.3, tough-cookie@~2.5.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2"
integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==
dependencies:
psl "^1.1.28"
punycode "^2.1.1"
tough-cookie@^3.0.1: tough-cookie@^3.0.1:
version "3.0.1" version "3.0.1"
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-3.0.1.tgz#9df4f57e739c26930a018184887f4adb7dca73b2" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-3.0.1.tgz#9df4f57e739c26930a018184887f4adb7dca73b2"
@@ -1829,6 +1790,14 @@ tough-cookie@^3.0.1:
psl "^1.1.28" psl "^1.1.28"
punycode "^2.1.1" punycode "^2.1.1"
tough-cookie@~2.5.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2"
integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==
dependencies:
psl "^1.1.28"
punycode "^2.1.1"
tunnel-agent@^0.6.0: tunnel-agent@^0.6.0:
version "0.6.0" version "0.6.0"
resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd"
@@ -1928,7 +1897,7 @@ vscode-languageserver-types@3.14.0:
resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.14.0.tgz#d3b5952246d30e5241592b6dde8280e03942e743" resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.14.0.tgz#d3b5952246d30e5241592b6dde8280e03942e743"
integrity sha512-lTmS6AlAlMHOvPQemVwo3CezxBp0sNB95KNPkqp3Nxd5VFEnuG1ByM0zlRWos0zjO3ZWtkvhal0COgiV1xIA4A== integrity sha512-lTmS6AlAlMHOvPQemVwo3CezxBp0sNB95KNPkqp3Nxd5VFEnuG1ByM0zlRWos0zjO3ZWtkvhal0COgiV1xIA4A==
vscode-nls@^4.0.0: vscode-nls@^4.0.0, vscode-nls@^4.1.1:
version "4.1.2" version "4.1.2"
resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.1.2.tgz#ca8bf8bb82a0987b32801f9fddfdd2fb9fd3c167" resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.1.2.tgz#ca8bf8bb82a0987b32801f9fddfdd2fb9fd3c167"
integrity sha512-7bOHxPsfyuCqmP+hZXscLhiHwe7CSuFE4hyhbs22xPIhQ4jv99FcR4eBzfYYVLP356HNFpdvz63FFb/xw6T4Iw== integrity sha512-7bOHxPsfyuCqmP+hZXscLhiHwe7CSuFE4hyhbs22xPIhQ4jv99FcR4eBzfYYVLP356HNFpdvz63FFb/xw6T4Iw==

View File

@@ -110,12 +110,11 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
this.currentBook = existingBook; this.currentBook = existingBook;
} else { } else {
await this.createAndAddBookModel(bookPath, !!isNotebook); await this.createAndAddBookModel(bookPath, !!isNotebook);
let bookViewer = vscode.window.createTreeView(this.viewId, { showCollapseAll: true, treeDataProvider: this });
this.currentBook = this.books.find(book => book.bookPath === bookPath); this.currentBook = this.books.find(book => book.bookPath === bookPath);
bookViewer.reveal(this.currentBook.bookItems[0], { expand: vscode.TreeItemCollapsibleState.Expanded, focus: true, select: true });
} }
if (showPreview) { if (showPreview) {
this._bookViewer.reveal(this.currentBook.bookItems[0], { expand: vscode.TreeItemCollapsibleState.Expanded, focus: true, select: true });
await this.showPreviewFile(urlToOpen); await this.showPreviewFile(urlToOpen);
} }
@@ -223,9 +222,7 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
openDocumentListenerUnsubscriber.dispose(); openDocumentListenerUnsubscriber.dispose();
}); });
} }
azdata.nb.showNotebookDocument(vscode.Uri.file(resource));
let doc = await vscode.workspace.openTextDocument(resource);
vscode.window.showTextDocument(doc);
} }
} catch (e) { } catch (e) {
vscode.window.showErrorMessage(loc.openNotebookError(resource, e instanceof Error ? e.message : e)); vscode.window.showErrorMessage(loc.openNotebookError(resource, e instanceof Error ? e.message : e));

View File

@@ -34,6 +34,13 @@ export const localhostName = 'localhost';
export const localhostTitle = localize('managePackages.localhost', "localhost"); export const localhostTitle = localize('managePackages.localhost', "localhost");
export const PackageNotFoundError = localize('managePackages.packageNotFound', "Could not find the specified package"); export const PackageNotFoundError = localize('managePackages.packageNotFound', "Could not find the specified package");
export const python3DisplayName = 'Python 3';
export const pysparkDisplayName = 'PySpark';
export const sparkScalaDisplayName = 'Spark | Scala';
export const sparkRDisplayName = 'Spark | R';
export const powershellDisplayName = 'PowerShell';
export const allKernelsName = 'All Kernels';
export const visitedNotebooksMementoKey = 'notebooks.visited'; export const visitedNotebooksMementoKey = 'notebooks.visited';
export enum BuiltInCommands { export enum BuiltInCommands {

View File

@@ -294,3 +294,7 @@ function decorate(decorator: (fn: Function, key: string) => Function): Function
descriptor[fnKey] = decorator(fn, key); descriptor[fnKey] = decorator(fn, key);
}; };
} }
export function getDropdownValue(dropdown: azdata.DropDownComponent): string {
return (typeof dropdown.value === 'string') ? dropdown.value : dropdown.value.name;
}

View File

@@ -0,0 +1,33 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import { ConfigurePythonModel, ConfigurePythonWizard } from './configurePythonWizard';
import { ApiWrapper } from '../../common/apiWrapper';
export abstract class BasePage {
constructor(protected readonly apiWrapper: ApiWrapper,
protected readonly instance: ConfigurePythonWizard,
protected readonly wizardPage: azdata.window.WizardPage,
protected readonly model: ConfigurePythonModel,
protected readonly view: azdata.ModelView) {
}
/**
* This method constructs all the elements of the page.
*/
public async abstract initialize(): Promise<boolean>;
/**
* This method is called when the user is entering the page.
*/
public async abstract onPageEnter(): Promise<void>;
/**
* This method is called when the user is leaving the page.
*/
public async abstract onPageLeave(): Promise<boolean>;
}

View File

@@ -0,0 +1,190 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import * as azdata from 'azdata';
import { BasePage } from './basePage';
import * as nls from 'vscode-nls';
import { JupyterServerInstallation } from '../../jupyter/jupyterServerInstallation';
import { PythonPathInfo } from '../pythonPathLookup';
import * as utils from '../../common/utils';
const localize = nls.loadMessageBundle();
export class ConfigurePathPage extends BasePage {
private readonly BrowseButtonText = localize('configurePython.browseButtonText', "Browse");
private readonly LocationTextBoxTitle = localize('configurePython.locationTextBoxText', "Python Install Location");
private readonly SelectFileLabel = localize('configurePython.selectFileLabel', "Select");
private pythonLocationDropdown: azdata.DropDownComponent;
private pythonDropdownLoader: azdata.LoadingComponent;
private browseButton: azdata.ButtonComponent;
private newInstallButton: azdata.RadioButtonComponent;
private existingInstallButton: azdata.RadioButtonComponent;
private usingCustomPath: boolean = false;
public async initialize(): Promise<boolean> {
this.pythonLocationDropdown = this.view.modelBuilder.dropDown()
.withProperties<azdata.DropDownProperties>({
value: undefined,
values: [],
width: '100%'
}).component();
this.pythonDropdownLoader = this.view.modelBuilder.loadingComponent()
.withItem(this.pythonLocationDropdown)
.withProperties<azdata.LoadingComponentProperties>({
loading: false
})
.component();
this.browseButton = this.view.modelBuilder.button()
.withProperties<azdata.ButtonProperties>({
label: this.BrowseButtonText,
width: '70px'
}).component();
this.browseButton.onDidClick(() => this.handleBrowse());
this.createInstallRadioButtons(this.view.modelBuilder, this.model.useExistingPython);
let formModel = this.view.modelBuilder.formContainer()
.withFormItems([{
component: this.newInstallButton,
title: localize('configurePython.installationType', "Installation Type")
}, {
component: this.existingInstallButton,
title: ''
}, {
component: this.pythonDropdownLoader,
title: this.LocationTextBoxTitle
}, {
component: this.browseButton,
title: ''
}]).component();
await this.view.initializeModel(formModel);
await this.updatePythonPathsDropdown(this.model.useExistingPython);
return true;
}
public async onPageEnter(): Promise<void> {
}
public async onPageLeave(): Promise<boolean> {
let pythonLocation = utils.getDropdownValue(this.pythonLocationDropdown);
if (!pythonLocation || pythonLocation.length === 0) {
this.instance.showErrorMessage(this.instance.InvalidLocationMsg);
return false;
}
this.model.pythonLocation = pythonLocation;
this.model.useExistingPython = !!this.existingInstallButton.checked;
return true;
}
private async updatePythonPathsDropdown(useExistingPython: boolean): Promise<void> {
this.pythonDropdownLoader.loading = true;
try {
let pythonPaths: PythonPathInfo[];
let dropdownValues: azdata.CategoryValue[];
if (useExistingPython) {
pythonPaths = await this.model.pythonPathsPromise;
if (pythonPaths && pythonPaths.length > 0) {
dropdownValues = pythonPaths.map(path => {
return {
displayName: localize('configurePythyon.dropdownPathLabel', "{0} (Python {1})", path.installDir, path.version),
name: path.installDir
};
});
} else {
dropdownValues = [{
displayName: localize('configurePythyon.noVersionsFound', "No supported Python versions found."),
name: ''
}];
}
} else {
let defaultPath = JupyterServerInstallation.DefaultPythonLocation;
dropdownValues = [{
displayName: localize('configurePythyon.defaultPathLabel', "{0} (Default)", defaultPath),
name: defaultPath
}];
}
this.usingCustomPath = false;
await this.pythonLocationDropdown.updateProperties({
value: dropdownValues[0],
values: dropdownValues
});
} finally {
this.pythonDropdownLoader.loading = false;
}
}
private createInstallRadioButtons(modelBuilder: azdata.ModelBuilder, useExistingPython: boolean): void {
let buttonGroup = 'installationType';
this.newInstallButton = modelBuilder.radioButton()
.withProperties<azdata.RadioButtonProperties>({
name: buttonGroup,
label: localize('configurePython.newInstall', "New Python installation"),
checked: !useExistingPython
}).component();
this.newInstallButton.onDidClick(() => {
this.existingInstallButton.checked = false;
this.updatePythonPathsDropdown(false)
.catch(err => {
this.instance.showErrorMessage(utils.getErrorMessage(err));
});
});
this.existingInstallButton = modelBuilder.radioButton()
.withProperties<azdata.RadioButtonProperties>({
name: buttonGroup,
label: localize('configurePython.existingInstall', "Use existing Python installation"),
checked: useExistingPython
}).component();
this.existingInstallButton.onDidClick(() => {
this.newInstallButton.checked = false;
this.updatePythonPathsDropdown(true)
.catch(err => {
this.instance.showErrorMessage(utils.getErrorMessage(err));
});
});
}
private async handleBrowse(): Promise<void> {
let options: vscode.OpenDialogOptions = {
defaultUri: vscode.Uri.file(utils.getUserHome()),
canSelectFiles: false,
canSelectFolders: true,
canSelectMany: false,
openLabel: this.SelectFileLabel
};
let fileUris: vscode.Uri[] = await this.apiWrapper.showOpenDialog(options);
if (fileUris?.length > 0 && fileUris[0]) {
let existingValues = <azdata.CategoryValue[]>this.pythonLocationDropdown.values;
let filePath = fileUris[0].fsPath;
let newValue = {
displayName: localize('configurePythyon.customPathLabel', "{0} (Custom)", filePath),
name: filePath
};
if (this.usingCustomPath) {
existingValues[0] = newValue;
} else {
existingValues.unshift(newValue);
this.usingCustomPath = true;
}
await this.pythonLocationDropdown.updateProperties({
value: existingValues[0],
values: existingValues
});
}
}
}

View File

@@ -7,12 +7,12 @@ import * as vscode from 'vscode';
import * as nls from 'vscode-nls'; import * as nls from 'vscode-nls';
import * as azdata from 'azdata'; import * as azdata from 'azdata';
import { promises as fs } from 'fs'; import { promises as fs } from 'fs';
import * as utils from '../common/utils'; import * as utils from '../../common/utils';
import { JupyterServerInstallation } from '../jupyter/jupyterServerInstallation'; import { JupyterServerInstallation } from '../../jupyter/jupyterServerInstallation';
import { ApiWrapper } from '../common/apiWrapper'; import { ApiWrapper } from '../../common/apiWrapper';
import { Deferred } from '../common/promise'; import { Deferred } from '../../common/promise';
import { PythonPathLookup, PythonPathInfo } from './pythonPathLookup'; import { PythonPathLookup, PythonPathInfo } from '../pythonPathLookup';
const localize = nls.loadMessageBundle(); const localize = nls.loadMessageBundle();

View File

@@ -0,0 +1,199 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vscode-nls';
import * as azdata from 'azdata';
import { BasePage } from './basePage';
import { ConfigurePathPage } from './configurePathPage';
import { PickPackagesPage } from './pickPackagesPage';
import { JupyterServerInstallation, PythonPkgDetails, PythonInstallSettings } from '../../jupyter/jupyterServerInstallation';
import * as utils from '../../common/utils';
import { promises as fs } from 'fs';
import { Deferred } from '../../common/promise';
import { PythonPathInfo, PythonPathLookup } from '../pythonPathLookup';
import { ApiWrapper } from '../../common/apiWrapper';
const localize = nls.loadMessageBundle();
export interface ConfigurePythonModel {
kernelName: string;
pythonLocation: string;
useExistingPython: boolean;
pythonPathsPromise: Promise<PythonPathInfo[]>;
packagesToInstall: PythonPkgDetails[];
installation: JupyterServerInstallation;
}
export class ConfigurePythonWizard {
private readonly InstallButtonText = localize('configurePython.okButtonText', "Install");
public readonly InvalidLocationMsg = localize('configurePython.invalidLocationMsg', "The specified install location is invalid.");
private readonly PythonNotFoundMsg = localize('configurePython.pythonNotFoundMsg', "No Python installation was found at the specified location.");
private _wizard: azdata.window.Wizard;
private model: ConfigurePythonModel;
private _setupComplete: Deferred<void>;
private pythonPathsPromise: Promise<PythonPathInfo[]>;
constructor(private apiWrapper: ApiWrapper, private jupyterInstallation: JupyterServerInstallation) {
this._setupComplete = new Deferred<void>();
this.pythonPathsPromise = (new PythonPathLookup()).getSuggestions();
}
public get wizard(): azdata.window.Wizard {
return this._wizard;
}
public get setupComplete(): Promise<void> {
return this._setupComplete.promise;
}
public async start(kernelName?: string, rejectOnCancel?: boolean, ...args: any[]): Promise<void> {
this.model = <ConfigurePythonModel>{
kernelName: kernelName,
pythonPathsPromise: this.pythonPathsPromise,
installation: this.jupyterInstallation,
useExistingPython: JupyterServerInstallation.getExistingPythonSetting(this.apiWrapper)
};
let pages: Map<number, BasePage> = new Map<number, BasePage>();
let wizardTitle: string;
if (kernelName) {
wizardTitle = localize('configurePython.wizardNameWithKernel', 'Configure Python to run {0} kernel', kernelName);
} else {
wizardTitle = localize('configurePython.wizardNameWithoutKernel', 'Configure Python to run kernels');
}
this._wizard = azdata.window.createWizard(wizardTitle);
let page0 = azdata.window.createWizardPage(localize('configurePython.page0Name', 'Configure Python Runtime'));
let page1 = azdata.window.createWizardPage(localize('configurePython.page1Name', 'Install Dependencies'));
page0.registerContent(async (view) => {
let configurePathPage = new ConfigurePathPage(this.apiWrapper, this, page0, this.model, view);
pages.set(0, configurePathPage);
await configurePathPage.initialize();
await configurePathPage.onPageEnter();
});
page1.registerContent(async (view) => {
let pickPackagesPage = new PickPackagesPage(this.apiWrapper, this, page1, this.model, view);
pages.set(1, pickPackagesPage);
await pickPackagesPage.initialize();
});
this._wizard.doneButton.label = this.InstallButtonText;
this._wizard.cancelButton.onClick(() => {
if (rejectOnCancel) {
this._setupComplete.reject(localize('configurePython.pythonInstallDeclined', "Python installation was declined."));
} else {
this._setupComplete.resolve();
}
});
this._wizard.onPageChanged(async info => {
let newPage = pages.get(info.newPage);
if (newPage) {
await newPage.onPageEnter();
}
});
this._wizard.registerNavigationValidator(async (info) => {
let lastPage = pages.get(info.lastPage);
let newPage = pages.get(info.newPage);
// Hit "next" on last page, so handle submit
let nextOnLastPage = !newPage && lastPage instanceof PickPackagesPage;
if (nextOnLastPage) {
return await this.handlePackageInstall();
}
if (lastPage) {
let pageValid = await lastPage.onPageLeave();
if (!pageValid) {
return false;
}
}
this.clearStatusMessage();
return true;
});
this._wizard.generateScriptButton.hidden = true;
this._wizard.pages = [page0, page1];
this._wizard.open();
}
public async close(): Promise<void> {
await this._wizard.close();
}
public showErrorMessage(errorMsg: string) {
this._wizard.message = <azdata.window.DialogMessage>{
text: errorMsg,
level: azdata.window.MessageLevel.Error
};
}
public clearStatusMessage() {
this._wizard.message = undefined;
}
private async handlePackageInstall(): Promise<boolean> {
let pythonLocation = this.model.pythonLocation;
let useExistingPython = this.model.useExistingPython;
try {
let isValid = await this.isFileValid(pythonLocation);
if (!isValid) {
return false;
}
if (useExistingPython) {
let exePath = JupyterServerInstallation.getPythonExePath(pythonLocation, true);
let pythonExists = await utils.exists(exePath);
if (!pythonExists) {
this.showErrorMessage(this.PythonNotFoundMsg);
return false;
}
}
} catch (err) {
this.showErrorMessage(utils.getErrorMessage(err));
return false;
}
// Don't wait on installation, since there's currently no Cancel functionality
let installSettings: PythonInstallSettings = {
installPath: pythonLocation,
existingPython: useExistingPython,
specificPackages: this.model.packagesToInstall
};
this.jupyterInstallation.startInstallProcess(false, installSettings)
.then(() => {
this._setupComplete.resolve();
})
.catch(err => {
this._setupComplete.reject(utils.getErrorMessage(err));
});
return true;
}
private async isFileValid(pythonLocation: string): Promise<boolean> {
try {
const stats = await fs.stat(pythonLocation);
if (stats.isFile()) {
this.showErrorMessage(this.InvalidLocationMsg);
return false;
}
} catch (err) {
// Ignore error if folder doesn't exist, since it will be
// created during installation
if (err.code !== 'ENOENT') {
this.showErrorMessage(err.message);
return false;
}
}
return true;
}
}

View File

@@ -0,0 +1,134 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import * as nls from 'vscode-nls';
import { BasePage } from './basePage';
import { JupyterServerInstallation, PythonPkgDetails } from '../../jupyter/jupyterServerInstallation';
import { python3DisplayName, pysparkDisplayName, sparkScalaDisplayName, sparkRDisplayName, powershellDisplayName, allKernelsName } from '../../common/constants';
import { getDropdownValue } from '../../common/utils';
const localize = nls.loadMessageBundle();
export class PickPackagesPage extends BasePage {
private kernelLabel: azdata.TextComponent | undefined;
private kernelDropdown: azdata.DropDownComponent | undefined;
private requiredPackagesTable: azdata.DeclarativeTableComponent;
private packageTableSpinner: azdata.LoadingComponent;
private installedPackagesPromise: Promise<PythonPkgDetails[]>;
private installedPackages: PythonPkgDetails[];
public async initialize(): Promise<boolean> {
if (this.model.kernelName) {
// Wizard was started for a specific kernel, so don't populate any other options
this.kernelLabel = this.view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
value: this.model.kernelName
}).component();
} else {
let dropdownValues = [python3DisplayName, pysparkDisplayName, sparkScalaDisplayName, sparkRDisplayName, powershellDisplayName, allKernelsName];
this.kernelDropdown = this.view.modelBuilder.dropDown().withProperties<azdata.DropDownProperties>({
value: dropdownValues[0],
values: dropdownValues,
width: '300px'
}).component();
this.kernelDropdown.onValueChanged(async value => {
await this.updateRequiredPackages(value.selected);
});
}
this.requiredPackagesTable = this.view.modelBuilder.declarativeTable().withProperties<azdata.DeclarativeTableProperties>({
columns: [{
displayName: localize('configurePython.pkgNameColumn', "Name"),
valueType: azdata.DeclarativeDataType.string,
isReadOnly: true,
width: '200px'
}, {
displayName: localize('configurePython.existingVersionColumn', "Existing Version"),
valueType: azdata.DeclarativeDataType.string,
isReadOnly: true,
width: '200px'
}, {
displayName: localize('configurePython.requiredVersionColumn', "Required Version"),
valueType: azdata.DeclarativeDataType.string,
isReadOnly: true,
width: '200px'
}],
data: [[]]
}).component();
this.packageTableSpinner = this.view.modelBuilder.loadingComponent().withItem(this.requiredPackagesTable).component();
let formModel = this.view.modelBuilder.formContainer()
.withFormItems([{
component: this.kernelDropdown ?? this.kernelLabel,
title: localize('configurePython.kernelLabel', "Kernel")
}, {
component: this.packageTableSpinner,
title: localize('configurePython.requiredDependencies', "Install required kernel dependencies")
}]).component();
await this.view.initializeModel(formModel);
return true;
}
public async onPageEnter(): Promise<void> {
let pythonExe = JupyterServerInstallation.getPythonExePath(this.model.pythonLocation, this.model.useExistingPython);
this.installedPackagesPromise = this.model.installation.getInstalledPipPackages(pythonExe);
this.installedPackages = undefined;
if (this.kernelDropdown) {
if (this.model.kernelName) {
this.kernelDropdown.value = this.model.kernelName;
} else {
this.model.kernelName = getDropdownValue(this.kernelDropdown);
}
}
await this.updateRequiredPackages(this.model.kernelName);
}
public async onPageLeave(): Promise<boolean> {
return true;
}
private async updateRequiredPackages(kernelName: string): Promise<void> {
this.packageTableSpinner.loading = true;
try {
let pkgVersionMap = new Map<string, { currentVersion: string, newVersion: string }>();
// Fetch list of required packages for the specified kernel
let requiredPackages = JupyterServerInstallation.getRequiredPackagesForKernel(kernelName);
requiredPackages.forEach(pkg => {
pkgVersionMap.set(pkg.name, { currentVersion: undefined, newVersion: pkg.version });
});
// For each required package, check if there is another version of that package already installed
if (!this.installedPackages) {
this.installedPackages = await this.installedPackagesPromise;
}
this.installedPackages.forEach(pkg => {
let info = pkgVersionMap.get(pkg.name);
if (info) {
info.currentVersion = pkg.version;
pkgVersionMap.set(pkg.name, info);
}
});
if (pkgVersionMap.size > 0) {
let packageData = [];
for (let [key, value] of pkgVersionMap.entries()) {
packageData.push([key, value.currentVersion ?? '-', value.newVersion]);
}
this.requiredPackagesTable.data = packageData;
this.model.packagesToInstall = requiredPackages;
} else {
this.instance.showErrorMessage(localize('msgUnsupportedKernel', "Could not retrieve packages for unsupported kernel {0}", kernelName));
this.requiredPackagesTable.data = [['-', '-', '-']];
this.model.packagesToInstall = undefined;
}
} finally {
this.packageTableSpinner.loading = false;
}
}
}

View File

@@ -133,7 +133,7 @@ export class ManagePackagesDialogModel {
* Returns the default location * Returns the default location
*/ */
public get defaultLocation(): string | undefined { public get defaultLocation(): string | undefined {
return this.options.defaultLocation || this.targetLocationTypes.length > 0 ? this.targetLocationTypes[0] : undefined; return this.options.defaultLocation || (this.targetLocationTypes.length > 0 ? this.targetLocationTypes[0] : undefined);
} }
/** /**

View File

@@ -22,7 +22,7 @@ import { ApiWrapper } from '../common/apiWrapper';
import { LocalJupyterServerManager, ServerInstanceFactory } from './jupyterServerManager'; import { LocalJupyterServerManager, ServerInstanceFactory } from './jupyterServerManager';
import { NotebookCompletionItemProvider } from '../intellisense/completionItemProvider'; import { NotebookCompletionItemProvider } from '../intellisense/completionItemProvider';
import { JupyterNotebookProvider } from './jupyterNotebookProvider'; import { JupyterNotebookProvider } from './jupyterNotebookProvider';
import { ConfigurePythonDialog } from '../dialog/configurePythonDialog'; import { ConfigurePythonWizard } from '../dialog/configurePython/configurePythonWizard';
import CodeAdapter from '../prompts/adapter'; import CodeAdapter from '../prompts/adapter';
import { ManagePackagesDialog } from '../dialog/managePackages/managePackagesDialog'; import { ManagePackagesDialog } from '../dialog/managePackages/managePackagesDialog';
import { IPackageManageProvider } from '../types'; import { IPackageManageProvider } from '../types';
@@ -30,6 +30,7 @@ import { LocalPipPackageManageProvider } from './localPipPackageManageProvider';
import { LocalCondaPackageManageProvider } from './localCondaPackageManageProvider'; import { LocalCondaPackageManageProvider } from './localCondaPackageManageProvider';
import { ManagePackagesDialogModel, ManagePackageDialogOptions } from '../dialog/managePackages/managePackagesDialogModel'; import { ManagePackagesDialogModel, ManagePackageDialogOptions } from '../dialog/managePackages/managePackagesDialogModel';
import { PiPyClient } from './pipyClient'; import { PiPyClient } from './pipyClient';
import { ConfigurePythonDialog } from '../dialog/configurePython/configurePythonDialog';
let untitledCounter = 0; let untitledCounter = 0;
@@ -250,10 +251,21 @@ export class JupyterController implements vscode.Disposable {
} }
public doConfigurePython(jupyterInstaller: JupyterServerInstallation): void { public doConfigurePython(jupyterInstaller: JupyterServerInstallation): void {
let pythonDialog = new ConfigurePythonDialog(this.apiWrapper, jupyterInstaller); let enablePreviewFeatures = this.apiWrapper.getConfiguration('workbench').get('enablePreviewFeatures');
pythonDialog.showDialog().catch((err: any) => { if (enablePreviewFeatures) {
this.apiWrapper.showErrorMessage(utils.getErrorMessage(err)); let pythonWizard = new ConfigurePythonWizard(this.apiWrapper, jupyterInstaller);
}); pythonWizard.start().catch((err: any) => {
this.apiWrapper.showErrorMessage(utils.getErrorMessage(err));
});
pythonWizard.setupComplete.catch((err: any) => {
this.apiWrapper.showErrorMessage(utils.getErrorMessage(err));
});
} else {
let pythonDialog = new ConfigurePythonDialog(this.apiWrapper, jupyterInstaller);
pythonDialog.showDialog().catch((err: any) => {
this.apiWrapper.showErrorMessage(utils.getErrorMessage(err));
});
}
} }
public get jupyterInstallation() { public get jupyterInstallation() {

View File

@@ -20,8 +20,8 @@ export class JupyterNotebookManager implements nb.NotebookManager, vscode.Dispos
this._sessionManager = sessionManager || new JupyterSessionManager(pythonEnvVarPath); this._sessionManager = sessionManager || new JupyterSessionManager(pythonEnvVarPath);
this._serverManager.onServerStarted(() => { this._serverManager.onServerStarted(() => {
this.setServerSettings(this._serverManager.serverSettings); this.setServerSettings(this._serverManager.serverSettings);
this._sessionManager.installation = this._serverManager.instanceOptions.install;
}); });
} }
public get contentManager(): nb.ContentManager { public get contentManager(): nb.ContentManager {
return undefined; return undefined;

View File

@@ -17,9 +17,10 @@ import * as constants from '../common/constants';
import * as utils from '../common/utils'; import * as utils from '../common/utils';
import { OutputChannel, ConfigurationTarget, window } from 'vscode'; import { OutputChannel, ConfigurationTarget, window } from 'vscode';
import { Deferred } from '../common/promise'; import { Deferred } from '../common/promise';
import { ConfigurePythonDialog } from '../dialog/configurePythonDialog'; import { ConfigurePythonWizard } from '../dialog/configurePython/configurePythonWizard';
import { IPrompter, IQuestion, QuestionTypes } from '../prompts/question'; import { IPrompter, IQuestion, QuestionTypes } from '../prompts/question';
import CodeAdapter from '../prompts/adapter'; import CodeAdapter from '../prompts/adapter';
import { ConfigurePythonDialog } from '../dialog/configurePython/configurePythonDialog';
const localize = nls.loadMessageBundle(); const localize = nls.loadMessageBundle();
const msgInstallPkgProgress = localize('msgInstallPkgProgress', "Notebook dependencies installation is in progress"); const msgInstallPkgProgress = localize('msgInstallPkgProgress', "Notebook dependencies installation is in progress");
@@ -39,10 +40,15 @@ function msgDependenciesInstallationFailed(errorMessage: string): string { retur
function msgDownloadPython(platform: string, pythonDownloadUrl: string): string { return localize('msgDownloadPython', "Downloading local python for platform: {0} to {1}", platform, pythonDownloadUrl); } function msgDownloadPython(platform: string, pythonDownloadUrl: string): string { return localize('msgDownloadPython', "Downloading local python for platform: {0} to {1}", platform, pythonDownloadUrl); }
function msgPackageRetrievalFailed(errorMessage: string): string { return localize('msgPackageRetrievalFailed', "Encountered an error when trying to retrieve list of installed packages: {0}", errorMessage); } function msgPackageRetrievalFailed(errorMessage: string): string { return localize('msgPackageRetrievalFailed', "Encountered an error when trying to retrieve list of installed packages: {0}", errorMessage); }
export interface PythonInstallSettings {
installPath: string;
existingPython: boolean;
specificPackages?: PythonPkgDetails[];
}
export interface IJupyterServerInstallation { export interface IJupyterServerInstallation {
installCondaPackages(packages: PythonPkgDetails[], useMinVersion: boolean): Promise<void>; installCondaPackages(packages: PythonPkgDetails[], useMinVersion: boolean): Promise<void>;
configurePackagePaths(): Promise<void>; configurePackagePaths(): Promise<void>;
startInstallProcess(forceInstall: boolean, installSettings?: { installPath: string, existingPython: boolean }): Promise<void>; startInstallProcess(forceInstall: boolean, installSettings?: PythonInstallSettings): Promise<void>;
getInstalledPipPackages(): Promise<PythonPkgDetails[]>; getInstalledPipPackages(): Promise<PythonPkgDetails[]>;
getInstalledCondaPackages(): Promise<PythonPkgDetails[]>; getInstalledCondaPackages(): Promise<PythonPkgDetails[]>;
uninstallCondaPackages(packages: PythonPkgDetails[]): Promise<void>; uninstallCondaPackages(packages: PythonPkgDetails[]): Promise<void>;
@@ -66,7 +72,6 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
private _pythonInstallationPath: string; private _pythonInstallationPath: string;
private _pythonExecutable: string; private _pythonExecutable: string;
private _pythonPackageDir: string;
private _usingExistingPython: boolean; private _usingExistingPython: boolean;
private _usingConda: boolean; private _usingConda: boolean;
@@ -104,6 +109,8 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
private readonly _expectedCondaPipPackages = this._commonPipPackages; private readonly _expectedCondaPipPackages = this._commonPipPackages;
private readonly _expectedCondaPackages: PythonPkgDetails[]; private readonly _expectedCondaPackages: PythonPkgDetails[];
private _kernelSetupCache: Map<string, boolean>;
constructor(extensionPath: string, outputChannel: OutputChannel, apiWrapper: ApiWrapper, pythonInstallationPath?: string) { constructor(extensionPath: string, outputChannel: OutputChannel, apiWrapper: ApiWrapper, pythonInstallationPath?: string) {
this.extensionPath = extensionPath; this.extensionPath = extensionPath;
this.outputChannel = outputChannel; this.outputChannel = outputChannel;
@@ -120,9 +127,11 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
} else { } else {
this._expectedCondaPackages = this._commonPackages; this._expectedCondaPackages = this._commonPackages;
} }
this._kernelSetupCache = new Map<string, boolean>();
} }
private async installDependencies(backgroundOperation: azdata.BackgroundOperation, forceInstall: boolean): Promise<void> { private async installDependencies(backgroundOperation: azdata.BackgroundOperation, forceInstall: boolean, specificPackages?: PythonPkgDetails[]): Promise<void> {
if (!(await utils.exists(this._pythonExecutable)) || forceInstall || this._usingExistingPython) { if (!(await utils.exists(this._pythonExecutable)) || forceInstall || this._usingExistingPython) {
window.showInformationMessage(msgInstallPkgStart); window.showInformationMessage(msgInstallPkgStart);
@@ -132,12 +141,7 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
try { try {
await this.installPythonPackage(backgroundOperation, this._usingExistingPython, this._pythonInstallationPath, this.outputChannel); await this.installPythonPackage(backgroundOperation, this._usingExistingPython, this._pythonInstallationPath, this.outputChannel);
await this.upgradePythonPackages(false, forceInstall, specificPackages);
if (this._usingExistingPython) {
await this.upgradePythonPackages(false, forceInstall);
} else {
await this.installOfflinePipDependencies();
}
} catch (err) { } catch (err) {
this.outputChannel.appendLine(msgDependenciesInstallationFailed(utils.getErrorMessage(err))); this.outputChannel.appendLine(msgDependenciesInstallationFailed(utils.getErrorMessage(err)));
throw err; throw err;
@@ -282,19 +286,13 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
? this._pythonInstallationPath ? this._pythonInstallationPath
: path.join(this._pythonInstallationPath, constants.pythonBundleVersion); : path.join(this._pythonInstallationPath, constants.pythonBundleVersion);
if (this._usingExistingPython) {
this._pythonPackageDir = undefined;
} else {
this._pythonPackageDir = path.join(pythonSourcePath, 'offlinePackages');
}
// Update python paths and properties to reference user's local python. // Update python paths and properties to reference user's local python.
let pythonBinPathSuffix = process.platform === constants.winPlatform ? '' : 'bin'; let pythonBinPathSuffix = process.platform === constants.winPlatform ? '' : 'bin';
this._pythonExecutable = JupyterServerInstallation.getPythonExePath(this._pythonInstallationPath, this._usingExistingPython); this._pythonExecutable = JupyterServerInstallation.getPythonExePath(this._pythonInstallationPath, this._usingExistingPython);
this.pythonBinPath = path.join(pythonSourcePath, pythonBinPathSuffix); this.pythonBinPath = path.join(pythonSourcePath, pythonBinPathSuffix);
this._usingConda = this.checkCondaExists(); this._usingConda = this.isCondaInstalled();
// Store paths to python libraries required to run jupyter. // Store paths to python libraries required to run jupyter.
this.pythonEnvVarPath = process.env['PATH']; this.pythonEnvVarPath = process.env['PATH'];
@@ -333,11 +331,6 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
delete env['Path']; // Delete extra 'Path' variable for Windows, just in case. delete env['Path']; // Delete extra 'Path' variable for Windows, just in case.
env['PATH'] = this.pythonEnvVarPath; env['PATH'] = this.pythonEnvVarPath;
// We don't want Jupyter to know about DOTNET_ROOT, as it's been modified by the liveshare experience
// Without this, won't be able to find the ASP.NET bits
if (process.env['DOTNET_ROOT']) {
delete env['DOTNET_ROOT'];
}
this.execOptions = { this.execOptions = {
env: env env: env
}; };
@@ -365,7 +358,7 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
* @param installSettings Optional parameter that specifies where to install python, and whether the install targets an existing python install. * @param installSettings Optional parameter that specifies where to install python, and whether the install targets an existing python install.
* The previous python path (or the default) is used if a new path is not specified. * The previous python path (or the default) is used if a new path is not specified.
*/ */
public async startInstallProcess(forceInstall: boolean, installSettings?: { installPath: string, existingPython: boolean }): Promise<void> { public async startInstallProcess(forceInstall: boolean, installSettings?: PythonInstallSettings): Promise<void> {
let isPythonRunning: boolean; let isPythonRunning: boolean;
if (installSettings) { if (installSettings) {
isPythonRunning = await this.isPythonRunning(installSettings.installPath, installSettings.existingPython); isPythonRunning = await this.isPythonRunning(installSettings.installPath, installSettings.existingPython);
@@ -404,7 +397,7 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
description: msgTaskName, description: msgTaskName,
isCancelable: false, isCancelable: false,
operation: op => { operation: op => {
this.installDependencies(op, forceInstall) this.installDependencies(op, forceInstall, installSettings?.specificPackages)
.then(async () => { .then(async () => {
await updateConfig(); await updateConfig();
this._installCompletion.resolve(); this._installCompletion.resolve();
@@ -432,28 +425,45 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
/** /**
* Opens a dialog for configuring the installation path for the Notebook Python dependencies. * Opens a dialog for configuring the installation path for the Notebook Python dependencies.
*/ */
public async promptForPythonInstall(): Promise<void> { public async promptForPythonInstall(kernelDisplayName: string): Promise<void> {
if (!JupyterServerInstallation.isPythonInstalled(this.apiWrapper)) { if (!JupyterServerInstallation.isPythonInstalled(this.apiWrapper)) {
let pythonDialog = new ConfigurePythonDialog(this.apiWrapper, this); let enablePreviewFeatures = this.apiWrapper.getConfiguration('workbench').get('enablePreviewFeatures');
return pythonDialog.showDialog(true); if (enablePreviewFeatures) {
let pythonWizard = new ConfigurePythonWizard(this.apiWrapper, this);
await pythonWizard.start(kernelDisplayName, true);
return pythonWizard.setupComplete;
} else {
let pythonDialog = new ConfigurePythonDialog(this.apiWrapper, this);
return pythonDialog.showDialog(true);
}
} }
} }
/** /**
* Prompts user to upgrade certain python packages if they're below the minimum expected version. * Prompts user to upgrade certain python packages if they're below the minimum expected version.
*/ */
public async promptForPackageUpgrade(): Promise<void> { public async promptForPackageUpgrade(kernelName: string): Promise<void> {
if (this._installInProgress) { if (this._installInProgress) {
this.apiWrapper.showInfoMessage(msgWaitingForInstall); this.apiWrapper.showInfoMessage(msgWaitingForInstall);
return this._installCompletion.promise; return this._installCompletion.promise;
} }
let requiredPackages: PythonPkgDetails[];
let enablePreviewFeatures = this.apiWrapper.getConfiguration('workbench').get('enablePreviewFeatures');
if (enablePreviewFeatures) {
if (this._kernelSetupCache.get(kernelName)) {
return;
}
requiredPackages = JupyterServerInstallation.getRequiredPackagesForKernel(kernelName);
}
this._installInProgress = true; this._installInProgress = true;
this._installCompletion = new Deferred<void>(); this._installCompletion = new Deferred<void>();
this.upgradePythonPackages(true, false) this.upgradePythonPackages(true, false, requiredPackages)
.then(() => { .then(() => {
this._installCompletion.resolve(); this._installCompletion.resolve();
this._installInProgress = false; this._installInProgress = false;
this._kernelSetupCache.set(kernelName, true);
}) })
.catch(err => { .catch(err => {
let errorMsg = msgDependenciesInstallationFailed(utils.getErrorMessage(err)); let errorMsg = msgDependenciesInstallationFailed(utils.getErrorMessage(err));
@@ -463,10 +473,14 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
return this._installCompletion.promise; return this._installCompletion.promise;
} }
private async upgradePythonPackages(promptForUpgrade: boolean, forceInstall: boolean): Promise<void> { private async upgradePythonPackages(promptForUpgrade: boolean, forceInstall: boolean, specificPackages?: PythonPkgDetails[]): Promise<void> {
let expectedCondaPackages: PythonPkgDetails[]; let expectedCondaPackages: PythonPkgDetails[];
let expectedPipPackages: PythonPkgDetails[]; let expectedPipPackages: PythonPkgDetails[];
if (this._usingConda) { if (specificPackages) {
// Always install generic packages with pip, since conda may not have them.
expectedCondaPackages = [];
expectedPipPackages = specificPackages;
} else if (this._usingConda) {
expectedCondaPackages = this._expectedCondaPackages; expectedCondaPackages = this._expectedCondaPackages;
expectedPipPackages = this._expectedCondaPipPackages; expectedPipPackages = this._expectedCondaPipPackages;
} else { } else {
@@ -515,9 +529,12 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
if (promptForUpgrade) { if (promptForUpgrade) {
doUpgrade = await this._prompter.promptSingle<boolean>(<IQuestion>{ doUpgrade = await this._prompter.promptSingle<boolean>(<IQuestion>{
type: QuestionTypes.confirm, type: QuestionTypes.confirm,
message: localize('confirmPackageUpgrade', "Some installed python packages need to be upgraded. Would you like to upgrade them now?"), message: localize('confirmPackageUpgrade', "Some required python packages need to be installed. Would you like to install them now?"),
default: true default: true
}); });
if (!doUpgrade) {
throw new Error(localize('configurePython.packageInstallDeclined', "Package installation was declined."));
}
} else { } else {
doUpgrade = true; doUpgrade = true;
} }
@@ -568,9 +585,17 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
} }
} }
public async getInstalledPipPackages(): Promise<PythonPkgDetails[]> { public async getInstalledPipPackages(pythonExePath?: string): Promise<PythonPkgDetails[]> {
try { try {
let cmd = `"${this.pythonExecutable}" -m pip list --format=json`; if (pythonExePath) {
if (!fs.existsSync(pythonExePath)) {
return [];
}
} else if (!JupyterServerInstallation.isPythonInstalled(this.apiWrapper)) {
return [];
}
let cmd = `"${pythonExePath ?? this.pythonExecutable}" -m pip list --format=json`;
let packagesInfo = await this.executeBufferedCommand(cmd); let packagesInfo = await this.executeBufferedCommand(cmd);
let packagesResult: PythonPkgDetails[] = []; let packagesResult: PythonPkgDetails[] = [];
if (packagesInfo) { if (packagesInfo) {
@@ -603,6 +628,10 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
public async getInstalledCondaPackages(): Promise<PythonPkgDetails[]> { public async getInstalledCondaPackages(): Promise<PythonPkgDetails[]> {
try { try {
if (!this.isCondaInstalled()) {
return [];
}
let condaExe = this.getCondaExePath(); let condaExe = this.getCondaExePath();
let cmd = `"${condaExe}" list --json`; let cmd = `"${condaExe}" list --json`;
let packagesInfo = await this.executeBufferedCommand(cmd); let packagesInfo = await this.executeBufferedCommand(cmd);
@@ -642,32 +671,6 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
return this.executeStreamedCommand(cmd); return this.executeStreamedCommand(cmd);
} }
private async installOfflinePipDependencies(): Promise<void> {
// Skip this step if using existing python, since this is for our provided package
if (!this._usingExistingPython && process.platform === constants.winPlatform) {
this.outputChannel.show(true);
this.outputChannel.appendLine(localize('msgInstallStart', "Installing required packages to run Notebooks..."));
let requirements = path.join(this._pythonPackageDir, 'requirements.txt');
let installJupyterCommand = `"${this._pythonExecutable}" -m pip install --no-index -r "${requirements}" --find-links "${this._pythonPackageDir}" --no-warn-script-location`;
await this.executeStreamedCommand(installJupyterCommand);
// Force reinstall pip to update shebangs in pip*.exe files
installJupyterCommand = `"${this._pythonExecutable}" -m pip install --force-reinstall --no-index pip --find-links "${this._pythonPackageDir}" --no-warn-script-location`;
await this.executeStreamedCommand(installJupyterCommand);
fs.remove(this._pythonPackageDir, (err: Error) => {
if (err) {
this.outputChannel.appendLine(err.message);
}
});
this.outputChannel.appendLine(localize('msgJupyterInstallDone', "... Jupyter installation complete."));
} else {
return Promise.resolve();
}
}
public async executeStreamedCommand(command: string): Promise<void> { public async executeStreamedCommand(command: string): Promise<void> {
await utils.executeStreamedCommand(command, { env: this.execOptions.env }, this.outputChannel); await utils.executeStreamedCommand(command, { env: this.execOptions.env }, this.outputChannel);
} }
@@ -696,7 +699,7 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
return this._usingConda; return this._usingConda;
} }
private checkCondaExists(): boolean { private isCondaInstalled(): boolean {
if (!this._usingExistingPython) { if (!this._usingExistingPython) {
return false; return false;
} }
@@ -805,6 +808,55 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
return undefined; return undefined;
} }
public static getRequiredPackagesForKernel(kernelName: string): PythonPkgDetails[] {
let packages = [{
name: 'jupyter',
version: '1.0.0'
}];
switch (kernelName) {
case constants.python3DisplayName:
break;
case constants.pysparkDisplayName:
case constants.sparkScalaDisplayName:
case constants.sparkRDisplayName:
packages.push({
name: 'sparkmagic',
version: '0.12.9'
}, {
name: 'pandas',
version: '0.24.2'
}, {
name: 'prose-codeaccelerator',
version: '1.3.0'
});
break;
case constants.powershellDisplayName:
packages.push({
name: 'powershell-kernel',
version: '0.1.3'
});
break;
case constants.allKernelsName:
packages.push({
name: 'sparkmagic',
version: '0.12.9'
}, {
name: 'pandas',
version: '0.24.2'
}, {
name: 'prose-codeaccelerator',
version: '1.3.0'
}, {
name: 'powershell-kernel',
version: '0.1.3'
});
break;
default:
return undefined;
}
return packages;
}
} }
export interface PythonPkgDetails { export interface PythonPkgDetails {

View File

@@ -56,16 +56,15 @@ export class LocalJupyterServerManager implements nb.ServerManager, vscode.Dispo
return this.options && this.options.jupyterInstallation; return this.options && this.options.jupyterInstallation;
} }
public async startServer(): Promise<void> { public async startServer(kernelSpec: nb.IKernelSpec): Promise<void> {
try { try {
if (!this._jupyterServer) { if (!this._jupyterServer) {
this._jupyterServer = await this.doStartServer(); this._jupyterServer = await this.doStartServer(kernelSpec);
this.options.extensionContext.subscriptions.push(this); this.options.extensionContext.subscriptions.push(this);
let partialSettings = LocalJupyterServerManager.getLocalConnectionSettings(this._jupyterServer.uri); let partialSettings = LocalJupyterServerManager.getLocalConnectionSettings(this._jupyterServer.uri);
this._serverSettings = partialSettings; this._serverSettings = partialSettings;
this._onServerStarted.fire(); this._onServerStarted.fire();
} }
} catch (error) { } catch (error) {
// this is caught and notified up the stack, no longer showing a message here // this is caught and notified up the stack, no longer showing a message here
throw error; throw error;
@@ -107,10 +106,10 @@ export class LocalJupyterServerManager implements nb.ServerManager, vscode.Dispo
return this.options.documentPath; return this.options.documentPath;
} }
private async doStartServer(): Promise<IServerInstance> { // We can't find or create servers until the installation is complete private async doStartServer(kernelSpec: nb.IKernelSpec): Promise<IServerInstance> { // We can't find or create servers until the installation is complete
let installation = this.options.jupyterInstallation; let installation = this.options.jupyterInstallation;
await installation.promptForPythonInstall(); await installation.promptForPythonInstall(kernelSpec.display_name);
await installation.promptForPackageUpgrade(); await installation.promptForPackageUpgrade(kernelSpec.display_name);
this._apiWrapper.setCommandContext(CommandContext.NotebookPythonInstalled, true); this._apiWrapper.setCommandContext(CommandContext.NotebookPythonInstalled, true);
// Calculate the path to use as the notebook-dir for Jupyter based on the path of the uri of the // Calculate the path to use as the notebook-dir for Jupyter based on the path of the uri of the

View File

@@ -15,6 +15,7 @@ const localize = nls.loadMessageBundle();
import { JupyterKernel } from './jupyterKernel'; import { JupyterKernel } from './jupyterKernel';
import { Deferred } from '../common/promise'; import { Deferred } from '../common/promise';
import { JupyterServerInstallation } from './jupyterServerInstallation';
const configBase = { const configBase = {
'kernel_python_credentials': { 'kernel_python_credentials': {
@@ -66,6 +67,7 @@ export class JupyterSessionManager implements nb.SessionManager {
private _isReady: boolean; private _isReady: boolean;
private _sessionManager: Session.IManager; private _sessionManager: Session.IManager;
private static _sessions: JupyterSession[] = []; private static _sessions: JupyterSession[] = [];
private _installation: JupyterServerInstallation;
constructor(private _pythonEnvVarPath?: string) { constructor(private _pythonEnvVarPath?: string) {
this._isReady = false; this._isReady = false;
@@ -84,6 +86,12 @@ export class JupyterSessionManager implements nb.SessionManager {
}); });
} }
public set installation(installation: JupyterServerInstallation) {
this._installation = installation;
JupyterSessionManager._sessions.forEach(session => {
session.installation = installation;
});
}
public get isReady(): boolean { public get isReady(): boolean {
return this._isReady; return this._isReady;
} }
@@ -126,7 +134,7 @@ export class JupyterSessionManager implements nb.SessionManager {
return Promise.reject(new Error(localize('errorStartBeforeReady', "Cannot start a session, the manager is not yet initialized"))); return Promise.reject(new Error(localize('errorStartBeforeReady', "Cannot start a session, the manager is not yet initialized")));
} }
let sessionImpl = await this._sessionManager.startNew(options); let sessionImpl = await this._sessionManager.startNew(options);
let jupyterSession = new JupyterSession(sessionImpl, skipSettingEnvironmentVars, this._pythonEnvVarPath); let jupyterSession = new JupyterSession(sessionImpl, this._installation, skipSettingEnvironmentVars, this._pythonEnvVarPath);
await jupyterSession.messagesComplete; await jupyterSession.messagesComplete;
let index = JupyterSessionManager._sessions.findIndex(session => session.path === options.path); let index = JupyterSessionManager._sessions.findIndex(session => session.path === options.path);
if (index > -1) { if (index > -1) {
@@ -173,7 +181,7 @@ export class JupyterSession implements nb.ISession {
private _kernel: nb.IKernel; private _kernel: nb.IKernel;
private _messagesComplete: Deferred<void> = new Deferred<void>(); private _messagesComplete: Deferred<void> = new Deferred<void>();
constructor(private sessionImpl: Session.ISession, skipSettingEnvironmentVars?: boolean, private _pythonEnvVarPath?: string) { constructor(private sessionImpl: Session.ISession, private _installation: JupyterServerInstallation, skipSettingEnvironmentVars?: boolean, private _pythonEnvVarPath?: string) {
this.setEnvironmentVars(skipSettingEnvironmentVars).catch(error => { this.setEnvironmentVars(skipSettingEnvironmentVars).catch(error => {
console.error(`Unexpected exception setting Jupyter Session variables : ${error}`); console.error(`Unexpected exception setting Jupyter Session variables : ${error}`);
// We don't want callers to hang forever waiting - it's better to continue on even if we weren't // We don't want callers to hang forever waiting - it's better to continue on even if we weren't
@@ -221,7 +229,20 @@ export class JupyterSession implements nb.ISession {
return this._messagesComplete.promise; return this._messagesComplete.promise;
} }
public set installation(installation: JupyterServerInstallation) {
this._installation = installation;
}
public async changeKernel(kernelInfo: nb.IKernelSpec): Promise<nb.IKernel> { public async changeKernel(kernelInfo: nb.IKernelSpec): Promise<nb.IKernel> {
if (this._installation) {
try {
await this._installation.promptForPackageUpgrade(kernelInfo.display_name);
} catch (err) {
// Have to swallow the error here to prevent hangs when changing back to the old kernel.
console.error(err.toString());
return this._kernel;
}
}
// For now, Jupyter implementation handles disposal etc. so we can just // For now, Jupyter implementation handles disposal etc. so we can just
// null out our kernel and let the changeKernel call handle this // null out our kernel and let the changeKernel call handle this
this._kernel = undefined; this._kernel = undefined;
@@ -348,11 +369,6 @@ export class JupyterSession implements nb.ISession {
} }
for (let i = 0; i < Object.keys(process.env).length; i++) { for (let i = 0; i < Object.keys(process.env).length; i++) {
let key = Object.keys(process.env)[i]; let key = Object.keys(process.env)[i];
// DOTNET_ROOT gets set as part of the liveshare experience, but confuses the dotnet interactive kernel
// Not setting this environment variable for notebooks removes this issue
if (key.toLowerCase() === 'dotnet_root') {
continue;
}
if (key.toLowerCase() === 'path' && this._pythonEnvVarPath) { if (key.toLowerCase() === 'path' && this._pythonEnvVarPath) {
allCode += `%set_env ${key}=${this._pythonEnvVarPath}${EOL}`; allCode += `%set_env ${key}=${this._pythonEnvVarPath}${EOL}`;
} else { } else {

View File

@@ -3,6 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import { IServerInstance } from '../jupyter/common'; import { IServerInstance } from '../jupyter/common';
@@ -255,3 +256,160 @@ export class FutureStub implements Kernel.IFuture {
} }
} }
//#endregion //#endregion
//#region test modelView components
class TestComponentBase implements azdata.Component {
id: string = '';
updateProperties(properties: { [key: string]: any; }): Thenable<void> {
Object.assign(this, properties);
return Promise.resolve();
}
updateProperty(key: string, value: any): Thenable<void> {
throw new Error('Method not implemented');
}
updateCssStyles(cssStyles: { [key: string]: string; }): Thenable<void> {
throw new Error('Method not implemented');
}
onValidityChanged: vscode.Event<boolean> = undefined;
valid: boolean = true;
validate(): Thenable<boolean> {
return Promise.resolve(true);
}
focus(): Thenable<void> {
return Promise.resolve();
}
}
class TestDropdownComponent extends TestComponentBase implements azdata.DropDownComponent {
constructor(private onClick: vscode.EventEmitter<any>) {
super();
}
onValueChanged: vscode.Event<any> = this.onClick.event;
}
class TestDeclarativeTableComponent extends TestComponentBase implements azdata.DeclarativeTableComponent {
constructor(private onClick: vscode.EventEmitter<any>) {
super();
}
onDataChanged: vscode.Event<any> = this.onClick.event;
data: any[][];
columns: azdata.DeclarativeTableColumn[];
}
class TestButtonComponent extends TestComponentBase implements azdata.ButtonComponent {
constructor(private onClick: vscode.EventEmitter<any>) {
super();
}
onDidClick: vscode.Event<any> = this.onClick.event;
}
class TestRadioButtonComponent extends TestComponentBase implements azdata.RadioButtonComponent {
constructor(private onClick: vscode.EventEmitter<any>) {
super();
}
onDidClick: vscode.Event<any> = this.onClick.event;
}
class TestTextComponent extends TestComponentBase implements azdata.TextComponent {
}
class TestLoadingComponent extends TestComponentBase implements azdata.LoadingComponent {
loading: boolean;
component: azdata.Component;
}
class TestFormContainer extends TestComponentBase implements azdata.FormContainer {
items: azdata.Component[] = [];
clearItems(): void {
}
addItems(itemConfigs: azdata.Component[], itemLayout?: azdata.FormItemLayout): void {
}
addItem(component: azdata.Component, itemLayout?: azdata.FormItemLayout): void {
}
insertItem(component: azdata.Component, index: number, itemLayout?: azdata.FormItemLayout): void {
}
removeItem(component: azdata.Component): boolean {
return true;
}
setLayout(layout: azdata.FormLayout): void {
}
setItemLayout(component: azdata.Component, layout: azdata.FormItemLayout): void {
}
}
class TestComponentBuilder<T extends azdata.Component> implements azdata.ComponentBuilder<T> {
constructor(private _component: T) {
}
component(): T {
return this._component;
}
withProperties<U>(properties: U): azdata.ComponentBuilder<T> {
this._component.updateProperties(properties);
return this;
}
withValidation(validation: (component: T) => boolean): azdata.ComponentBuilder<T> {
return this;
}
}
class TestLoadingBuilder extends TestComponentBuilder<azdata.LoadingComponent> implements azdata.LoadingComponentBuilder {
withItem(component: azdata.Component): azdata.LoadingComponentBuilder {
this.component().component = component;
return this;
}
}
export function createViewContext(): TestContext {
let onClick: vscode.EventEmitter<any> = new vscode.EventEmitter<any>();
let form: azdata.FormContainer = new TestFormContainer();
let textBuilder: azdata.ComponentBuilder<azdata.TextComponent> = new TestComponentBuilder(new TestTextComponent());
let buttonBuilder: azdata.ComponentBuilder<azdata.ButtonComponent> = new TestComponentBuilder(new TestButtonComponent(onClick));
let radioButtonBuilder: azdata.ComponentBuilder<azdata.ButtonComponent> = new TestComponentBuilder(new TestRadioButtonComponent(onClick));
let declarativeTableBuilder: azdata.ComponentBuilder<azdata.DeclarativeTableComponent> = new TestComponentBuilder(new TestDeclarativeTableComponent(onClick));
let loadingBuilder: azdata.LoadingComponentBuilder = new TestLoadingBuilder(new TestLoadingComponent());
let dropdownBuilder: azdata.ComponentBuilder<azdata.DropDownComponent> = new TestComponentBuilder(new TestDropdownComponent(onClick));
let formBuilder: azdata.FormBuilder = Object.assign({}, {
component: () => form,
addFormItem: () => { },
insertFormItem: () => { },
removeFormItem: () => true,
addFormItems: () => { },
withFormItems: () => formBuilder,
withProperties: () => formBuilder,
withValidation: () => formBuilder,
withItems: () => formBuilder,
withLayout: () => formBuilder
});
let view: azdata.ModelView = {
onClosed: undefined!,
connection: undefined!,
serverInfo: undefined!,
valid: true,
onValidityChanged: undefined!,
validate: undefined!,
initializeModel: () => { return Promise.resolve(); },
modelBuilder: <azdata.ModelBuilder>{
radioButton: () => radioButtonBuilder,
text: () => textBuilder,
button: () => buttonBuilder,
dropDown: () => dropdownBuilder,
declarativeTable: () => declarativeTableBuilder,
formContainer: () => formBuilder,
loadingComponent: () => loadingBuilder
}
};
return {
view: view,
onClick: onClick,
};
}
export interface TestContext {
view: azdata.ModelView;
onClick: vscode.EventEmitter<any>;
}
//#endregion

View File

@@ -22,6 +22,7 @@ export class MockExtensionContext implements vscode.ExtensionContext {
constructor() { constructor() {
this.subscriptions = []; this.subscriptions = [];
} }
environmentVariableCollection: vscode.EnvironmentVariableCollection;
} }
export class MockOutputChannel implements vscode.OutputChannel { export class MockOutputChannel implements vscode.OutputChannel {

View File

@@ -0,0 +1,133 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import * as TypeMoq from 'typemoq';
import { ApiWrapper } from '../common/apiWrapper';
import { ConfigurePythonWizard, ConfigurePythonModel } from '../dialog/configurePython/configurePythonWizard';
import { JupyterServerInstallation } from '../jupyter/jupyterServerInstallation';
import { ConfigurePathPage } from '../dialog/configurePython/configurePathPage';
import * as should from 'should';
import { PickPackagesPage } from '../dialog/configurePython/pickPackagesPage';
import { python3DisplayName, allKernelsName } from '../common/constants';
import { TestContext, createViewContext } from './common';
describe('Configure Python Wizard', function () {
let apiWrapper: ApiWrapper = new ApiWrapper();
let testWizard: ConfigurePythonWizard;
let viewContext: TestContext;
let testInstallation: JupyterServerInstallation;
beforeEach(() => {
let mockInstall = TypeMoq.Mock.ofType(JupyterServerInstallation);
mockInstall.setup(i => i.getInstalledPipPackages(TypeMoq.It.isAnyString())).returns(() => Promise.resolve([]));
testInstallation = mockInstall.object;
let mockWizard = TypeMoq.Mock.ofType(ConfigurePythonWizard);
mockWizard.setup(w => w.showErrorMessage(TypeMoq.It.isAnyString()));
testWizard = mockWizard.object;
viewContext = createViewContext();
});
// These wizard tests are disabled due to errors with disposable objects
//
// it('Start wizard test', async () => {
// let wizard = new ConfigurePythonWizard(apiWrapper, testInstallation);
// await wizard.start();
// await wizard.close();
// await should(wizard.setupComplete).be.resolved();
// });
// it('Reject setup on cancel test', async () => {
// let wizard = new ConfigurePythonWizard(apiWrapper, testInstallation);
// await wizard.start(undefined, true);
// await wizard.close();
// await should(wizard.setupComplete).be.rejected();
// });
// it('Error message test', async () => {
// let wizard = new ConfigurePythonWizard(apiWrapper, testInstallation);
// await wizard.start();
// should(wizard.wizard.message).be.undefined();
// let testMsg = 'Test message';
// wizard.showErrorMessage(testMsg);
// should(wizard.wizard.message.text).be.equal(testMsg);
// should(wizard.wizard.message.level).be.equal(azdata.window.MessageLevel.Error);
// wizard.clearStatusMessage();
// should(wizard.wizard.message).be.undefined();
// await wizard.close();
// });
it('Configure Path Page test', async () => {
let testPythonLocation = '/not/a/real/path';
let model = <ConfigurePythonModel>{
useExistingPython: true,
pythonPathsPromise: Promise.resolve([{
installDir: testPythonLocation,
version: '4000'
}])
};
let page = azdata.window.createWizardPage('Page 1');
let configurePathPage = new ConfigurePathPage(apiWrapper, testWizard, page, model, viewContext.view);
should(await configurePathPage.initialize()).be.true();
// First page, so onPageEnter should do nothing
await should(configurePathPage.onPageEnter()).be.resolved();
should(await configurePathPage.onPageLeave()).be.true();
should(model.useExistingPython).be.true();
should(model.pythonLocation).be.equal(testPythonLocation);
});
it('Pick Packages Page test', async () => {
let model = <ConfigurePythonModel>{
kernelName: allKernelsName,
installation: testInstallation,
pythonLocation: '/not/a/real/path',
useExistingPython: true
};
let page = azdata.window.createWizardPage('Page 2');
let pickPackagesPage = new PickPackagesPage(apiWrapper, testWizard, page, model, viewContext.view);
should(await pickPackagesPage.initialize()).be.true();
should((<any>pickPackagesPage).kernelLabel).not.be.undefined();
should((<any>pickPackagesPage).kernelDropdown).be.undefined();
// Last page, so onPageLeave should do nothing
should(await pickPackagesPage.onPageLeave()).be.true();
await should(pickPackagesPage.onPageEnter()).be.resolved();
should(model.packagesToInstall).be.deepEqual(JupyterServerInstallation.getRequiredPackagesForKernel(allKernelsName));
});
it('Undefined kernel test', async () => {
let model = <ConfigurePythonModel>{
kernelName: undefined,
installation: testInstallation,
pythonLocation: '/not/a/real/path',
useExistingPython: true
};
let page = azdata.window.createWizardPage('Page 2');
let pickPackagesPage = new PickPackagesPage(apiWrapper, testWizard, page, model, viewContext.view);
should(await pickPackagesPage.initialize()).be.true();
should((<any>pickPackagesPage).kernelLabel).be.undefined();
should((<any>pickPackagesPage).kernelDropdown).not.be.undefined();
await should(pickPackagesPage.onPageEnter()).be.resolved();
should(model.packagesToInstall).be.deepEqual(JupyterServerInstallation.getRequiredPackagesForKernel(python3DisplayName));
});
});

View File

@@ -8,7 +8,7 @@ import * as azdata from 'azdata';
import * as should from 'should'; import * as should from 'should';
import 'mocha'; import 'mocha';
import * as TypeMoq from 'typemoq'; import * as TypeMoq from 'typemoq';
import { JupyterServerInstallation, PythonPkgDetails, IJupyterServerInstallation } from '../../jupyter/jupyterServerInstallation'; import { JupyterServerInstallation, PythonPkgDetails, IJupyterServerInstallation, PythonInstallSettings } from '../../jupyter/jupyterServerInstallation';
import { LocalCondaPackageManageProvider } from '../../jupyter/localCondaPackageManageProvider'; import { LocalCondaPackageManageProvider } from '../../jupyter/localCondaPackageManageProvider';
import * as constants from '../../common/constants'; import * as constants from '../../common/constants';
import { LocalPipPackageManageProvider } from '../../jupyter/localPipPackageManageProvider'; import { LocalPipPackageManageProvider } from '../../jupyter/localPipPackageManageProvider';
@@ -188,7 +188,7 @@ describe('Manage Package Providers', () => {
serverInstallation: { serverInstallation: {
installCondaPackages: (packages: PythonPkgDetails[], useMinVersion: boolean) => { return Promise.resolve(); }, installCondaPackages: (packages: PythonPkgDetails[], useMinVersion: boolean) => { return Promise.resolve(); },
configurePackagePaths: () => { return Promise.resolve(); }, configurePackagePaths: () => { return Promise.resolve(); },
startInstallProcess: (forceInstall: boolean, installSettings?: { installPath: string, existingPython: boolean }) => { return Promise.resolve(); }, startInstallProcess: (forceInstall: boolean, installSettings?: PythonInstallSettings) => { return Promise.resolve(); },
getInstalledPipPackages: () => { return Promise.resolve([]); }, getInstalledPipPackages: () => { return Promise.resolve([]); },
installPipPackages: (packages: PythonPkgDetails[], useMinVersion: boolean) => { return Promise.resolve(); }, installPipPackages: (packages: PythonPkgDetails[], useMinVersion: boolean) => { return Promise.resolve(); },
uninstallPipPackages: (packages: PythonPkgDetails[]) => { return Promise.resolve(); }, uninstallPipPackages: (packages: PythonPkgDetails[]) => { return Promise.resolve(); },

View File

@@ -139,7 +139,8 @@ describe('Manage Package Dialog', () => {
removeItem: () => true, removeItem: () => true,
insertItem: () => { }, insertItem: () => { },
items: components, items: components,
setLayout: () => { } setLayout: () => { },
setItemLayout: () => { }
}; };
let form: azdata.FormContainer = Object.assign({}, componentBase, container, { let form: azdata.FormContainer = Object.assign({}, componentBase, container, {
}); });

View File

@@ -199,6 +199,36 @@ describe('Manage Packages', () => {
should.deepEqual(model.getPackageTypes('location1'), [{providerId: 'providerId1', packageType: 'package-type1'}]); should.deepEqual(model.getPackageTypes('location1'), [{providerId: 'providerId1', packageType: 'package-type1'}]);
}); });
it('Should set default location to one set in given options', async function (): Promise<void> {
let testContext1 = createContext();
testContext1.provider.providerId = 'providerId1';
testContext1.provider.packageTarget = {
location: 'location1',
packageType: 'package-type1'
};
let testContext2 = createContext();
testContext2.provider.providerId = 'providerId2';
testContext2.provider.packageTarget = {
location: 'location1',
packageType: 'package-type2'
};
testContext2.provider.canUseProvider = () => { return Promise.resolve(false); };
let providers = new Map<string, IPackageManageProvider>();
providers.set(testContext1.provider.providerId, createProvider(testContext1));
providers.set(testContext2.provider.providerId, createProvider(testContext2));
let model = new ManagePackagesDialogModel(jupyterServerInstallation, providers, {
defaultLocation: testContext2.provider.packageTarget.location,
defaultProviderId: testContext2.provider.providerId
});
await model.init();
should.equal(model.defaultLocation, testContext2.provider.packageTarget.location);
should.deepEqual(model.getPackageTypes('location1'), [{providerId: 'providerId1', packageType: 'package-type1'}]);
});
it('changeProvider should change current provider successfully', async function (): Promise<void> { it('changeProvider should change current provider successfully', async function (): Promise<void> {
let testContext1 = createContext(); let testContext1 = createContext();
testContext1.provider.providerId = 'providerId1'; testContext1.provider.providerId = 'providerId1';

View File

@@ -6,6 +6,7 @@
import * as should from 'should'; import * as should from 'should';
import * as TypeMoq from 'typemoq'; import * as TypeMoq from 'typemoq';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import * as azdata from 'azdata';
import 'mocha'; import 'mocha';
import { JupyterServerInstanceStub } from '../common'; import { JupyterServerInstanceStub } from '../common';
@@ -18,6 +19,10 @@ import { IServerInstance } from '../../jupyter/common';
import { MockExtensionContext } from '../common/stubs'; import { MockExtensionContext } from '../common/stubs';
describe('Local Jupyter Server Manager', function (): void { describe('Local Jupyter Server Manager', function (): void {
const pythonKernelSpec: azdata.nb.IKernelSpec = {
name: 'python3',
display_name: 'Python 3'
};
let expectedPath = 'my/notebook.ipynb'; let expectedPath = 'my/notebook.ipynb';
let serverManager: LocalJupyterServerManager; let serverManager: LocalJupyterServerManager;
let deferredInstall: Deferred<void>; let deferredInstall: Deferred<void>;
@@ -33,7 +38,7 @@ describe('Local Jupyter Server Manager', function (): void {
deferredInstall = new Deferred<void>(); deferredInstall = new Deferred<void>();
let mockInstall = TypeMoq.Mock.ofType(JupyterServerInstallation, undefined, undefined, '/root'); let mockInstall = TypeMoq.Mock.ofType(JupyterServerInstallation, undefined, undefined, '/root');
mockInstall.setup(j => j.promptForPythonInstall()).returns(() => deferredInstall.promise); mockInstall.setup(j => j.promptForPythonInstall(TypeMoq.It.isAny())).returns(() => deferredInstall.promise);
mockInstall.object.execOptions = { env: Object.assign({}, process.env) }; mockInstall.object.execOptions = { env: Object.assign({}, process.env) };
serverManager = new LocalJupyterServerManager({ serverManager = new LocalJupyterServerManager({
@@ -53,7 +58,7 @@ describe('Local Jupyter Server Manager', function (): void {
it('Should show error message on install failure', async function (): Promise<void> { it('Should show error message on install failure', async function (): Promise<void> {
let error = 'Error!!'; let error = 'Error!!';
deferredInstall.reject(error); deferredInstall.reject(error);
await testUtils.assertThrowsAsync(() => serverManager.startServer(), undefined); await testUtils.assertThrowsAsync(() => serverManager.startServer(pythonKernelSpec), undefined);
}); });
it('Should configure and start install', async function (): Promise<void> { it('Should configure and start install', async function (): Promise<void> {
@@ -65,7 +70,7 @@ describe('Local Jupyter Server Manager', function (): void {
// When I start the server // When I start the server
let notified = false; let notified = false;
serverManager.onServerStarted(() => notified = true); serverManager.onServerStarted(() => notified = true);
await serverManager.startServer(); await serverManager.startServer(pythonKernelSpec);
// Then I expect the port to be included in settings // Then I expect the port to be included in settings
should(serverManager.serverSettings.baseUrl.indexOf('1234') > -1).be.true(); should(serverManager.serverSettings.baseUrl.indexOf('1234') > -1).be.true();
@@ -89,7 +94,7 @@ describe('Local Jupyter Server Manager', function (): void {
deferredInstall.resolve(); deferredInstall.resolve();
// When I start and then the server // When I start and then the server
await serverManager.startServer(); await serverManager.startServer(pythonKernelSpec);
await serverManager.stopServer(); await serverManager.stopServer();
// Then I expect stop to have been called on the server instance // Then I expect stop to have been called on the server instance
@@ -104,7 +109,7 @@ describe('Local Jupyter Server Manager', function (): void {
deferredInstall.resolve(); deferredInstall.resolve();
// When I start and then dispose the extension // When I start and then dispose the extension
await serverManager.startServer(); await serverManager.startServer(pythonKernelSpec);
should(mockExtensionContext.subscriptions).have.length(1); should(mockExtensionContext.subscriptions).have.length(1);
mockExtensionContext.subscriptions[0].dispose(); mockExtensionContext.subscriptions[0].dispose();

View File

@@ -108,7 +108,7 @@ describe('Jupyter Session', function (): void {
beforeEach(() => { beforeEach(() => {
mockJupyterSession = TypeMoq.Mock.ofType(SessionStub); mockJupyterSession = TypeMoq.Mock.ofType(SessionStub);
session = new JupyterSession(mockJupyterSession.object); session = new JupyterSession(mockJupyterSession.object, undefined);
}); });
it('should always be able to change kernels', function (): void { it('should always be able to change kernels', function (): void {

View File

@@ -3,7 +3,7 @@
"version": "0.0.1", "version": "0.0.1",
"description": "Dependencies shared by all extensions", "description": "Dependencies shared by all extensions",
"dependencies": { "dependencies": {
"typescript": "3.8.3" "typescript": "3.9.1-rc"
}, },
"scripts": { "scripts": {
"postinstall": "node ./postinstall" "postinstall": "node ./postinstall"

View File

@@ -26,6 +26,17 @@
"Microsoft.mssql" "Microsoft.mssql"
], ],
"contributes": { "contributes": {
"configuration": [
{
"title": "%sqlDatabaseProjects.Settings%",
"properties": {
"sqlDatabaseProjects.netCoreSDKLocation": {
"type": "string",
"description": "%sqlDatabaseProjects.netCoreInstallLocation%"
}
}
}
],
"commands": [ "commands": [
{ {
"command": "sqlDatabaseProjects.new", "command": "sqlDatabaseProjects.new",
@@ -71,6 +82,31 @@
"command": "sqlDatabaseProjects.newFolder", "command": "sqlDatabaseProjects.newFolder",
"title": "%sqlDatabaseProjects.newFolder%", "title": "%sqlDatabaseProjects.newFolder%",
"category": "%sqlDatabaseProjects.displayName%" "category": "%sqlDatabaseProjects.displayName%"
},
{
"command": "sqlDatabaseProjects.build",
"title": "%sqlDatabaseProjects.build%",
"category": "%sqlDatabaseProjects.displayName%"
},
{
"command": "sqlDatabaseProjects.deploy",
"title": "%sqlDatabaseProjects.deploy%",
"category": "%sqlDatabaseProjects.displayName%"
},
{
"command": "sqlDatabaseProjects.import",
"title": "%sqlDatabaseProjects.import%",
"category": "%sqlDatabaseProjects.displayName%"
},
{
"command": "sqlDatabaseProjects.properties",
"title": "%sqlDatabaseProjects.properties%",
"category": "%sqlDatabaseProjects.displayName%"
},
{
"command": "sqlDatabaseProjects.schemaCompare",
"title": "%sqlDatabaseProjects.schemaCompare%",
"category": "%sqlDatabaseProjects.displayName%"
} }
], ],
"menus": { "menus": {
@@ -108,36 +144,87 @@
{ {
"command": "sqlDatabaseProjects.newFolder", "command": "sqlDatabaseProjects.newFolder",
"when": "false" "when": "false"
},
{
"command": "sqlDatabaseProjects.build",
"when": "false"
},
{
"command": "sqlDatabaseProjects.deploy",
"when": "false"
},
{
"command": "sqlDatabaseProjects.import"
},
{
"command": "sqlDatabaseProjects.properties",
"when": "false"
},
{
"command": "sqlDatabaseProjects.schemaCompare",
"when": "false"
} }
], ],
"view/item/context": [ "view/item/context": [
{ {
"command": "sqlDatabaseProjects.close", "command": "sqlDatabaseProjects.build",
"when": "view == sqlDatabaseProjectsView" "when": "view == sqlDatabaseProjectsView",
"group": "1_dbProjectsFirst@1"
}, },
{ {
"command": "sqlDatabaseProjects.newScript", "command": "sqlDatabaseProjects.deploy",
"when": "view == sqlDatabaseProjectsView" "when": "view == sqlDatabaseProjectsView",
"group": "1_dbProjectsFirst@2"
}, },
{ {
"command": "sqlDatabaseProjects.newTable", "command": "sqlDatabaseProjects.schemaCompare",
"when": "view == sqlDatabaseProjectsView" "when": "view == sqlDatabaseProjectsView",
"group": "1_dbProjectsFirst@3"
}, },
{ {
"command": "sqlDatabaseProjects.newView", "command": "sqlDatabaseProjects.import",
"when": "view == sqlDatabaseProjectsView" "when": "view == sqlDatabaseProjectsView",
}, "group": "1_dbProjectsFirst@4"
{
"command": "sqlDatabaseProjects.newStoredProcedure",
"when": "view == sqlDatabaseProjectsView"
}, },
{ {
"command": "sqlDatabaseProjects.newItem", "command": "sqlDatabaseProjects.newItem",
"when": "view == sqlDatabaseProjectsView" "when": "view == sqlDatabaseProjectsView",
"group": "2_dbProjects_newMain@1"
}, },
{ {
"command": "sqlDatabaseProjects.newFolder", "command": "sqlDatabaseProjects.newFolder",
"when": "view == sqlDatabaseProjectsView" "when": "view == sqlDatabaseProjectsView",
"group": "2_dbProjects_newMain@2"
},
{
"command": "sqlDatabaseProjects.newTable",
"when": "view == sqlDatabaseProjectsView",
"group": "3_dbProjects_newItem@1"
},
{
"command": "sqlDatabaseProjects.newView",
"when": "view == sqlDatabaseProjectsView",
"group": "3_dbProjects_newItem@2"
},
{
"command": "sqlDatabaseProjects.newStoredProcedure",
"when": "view == sqlDatabaseProjectsView",
"group": "3_dbProjects_newItem@3"
},
{
"command": "sqlDatabaseProjects.newScript",
"when": "view == sqlDatabaseProjectsView",
"group": "3_dbProjects_newItem@9"
},
{
"command": "sqlDatabaseProjects.close",
"when": "view == sqlDatabaseProjectsView",
"group": "9_dbProjectsLast"
},
{
"command": "sqlDatabaseProjects.properties",
"when": "view == sqlDatabaseProjectsView",
"group": "9_dbProjectsLast"
} }
] ]
}, },

View File

@@ -6,11 +6,20 @@
"sqlDatabaseProjects.new": "New Database Project", "sqlDatabaseProjects.new": "New Database Project",
"sqlDatabaseProjects.open": "Open Database Project", "sqlDatabaseProjects.open": "Open Database Project",
"sqlDatabaseProjects.close": "Close Database Project", "sqlDatabaseProjects.close": "Close Database Project",
"sqlDatabaseProjects.build": "Build",
"sqlDatabaseProjects.deploy": "Deploy",
"sqlDatabaseProjects.import": "Import",
"sqlDatabaseProjects.properties": "Properties",
"sqlDatabaseProjects.schemaCompare": "Schema Compare",
"sqlDatabaseProjects.newScript": "Add Script", "sqlDatabaseProjects.newScript": "Add Script",
"sqlDatabaseProjects.newTable": "Add Table", "sqlDatabaseProjects.newTable": "Add Table",
"sqlDatabaseProjects.newView": "Add View", "sqlDatabaseProjects.newView": "Add View",
"sqlDatabaseProjects.newStoredProcedure": "Add Stored Procedure", "sqlDatabaseProjects.newStoredProcedure": "Add Stored Procedure",
"sqlDatabaseProjects.newItem": "Add Item...", "sqlDatabaseProjects.newItem": "Add Item...",
"sqlDatabaseProjects.newFolder": "Add Folder" "sqlDatabaseProjects.newFolder": "Add Folder",
"sqlDatabaseProjects.Settings": "Database Projects",
"sqlDatabaseProjects.netCoreInstallLocation": "Full Path to .Net Core SDK on the machine."
} }

View File

@@ -0,0 +1,149 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import * as azdata from 'azdata';
/**
* Wrapper class to act as a facade over VSCode and Data APIs and allow us to test / mock callbacks into
* this API from our code
*/
export class ApiWrapper {
public createOutputChannel(name: string): vscode.OutputChannel {
return vscode.window.createOutputChannel(name);
}
public createTerminalWithOptions(options: vscode.TerminalOptions): vscode.Terminal {
return vscode.window.createTerminal(options);
}
public getCurrentConnection(): Thenable<azdata.connection.ConnectionProfile> {
return azdata.connection.getCurrentConnection();
}
public getCredentials(connectionId: string): Thenable<{ [name: string]: string }> {
return azdata.connection.getCredentials(connectionId);
}
public registerCommand(command: string, callback: (...args: any[]) => any, thisArg?: any): vscode.Disposable {
return vscode.commands.registerCommand(command, callback, thisArg);
}
public executeCommand<T>(command: string, ...rest: any[]): Thenable<T | undefined> {
return vscode.commands.executeCommand(command, ...rest);
}
public registerTaskHandler(taskId: string, handler: (profile: azdata.IConnectionProfile) => void): void {
azdata.tasks.registerTask(taskId, handler);
}
public registerTreeDataProvider<T>(viewId: string, treeDataProvider: vscode.TreeDataProvider<T>): vscode.Disposable {
return vscode.window.registerTreeDataProvider(viewId, treeDataProvider);
}
public getUriForConnection(connectionId: string): Thenable<string> {
return azdata.connection.getUriForConnection(connectionId);
}
public getProvider<T extends azdata.DataProvider>(providerId: string, providerType: azdata.DataProviderType): T {
return azdata.dataprotocol.getProvider<T>(providerId, providerType);
}
public showErrorMessage(message: string, ...items: string[]): Thenable<string | undefined> {
return vscode.window.showErrorMessage(message, ...items);
}
public showInformationMessage(message: string, ...items: string[]): Thenable<string | undefined> {
return vscode.window.showInformationMessage(message, ...items);
}
public showOpenDialog(options: vscode.OpenDialogOptions): Thenable<vscode.Uri[] | undefined> {
return vscode.window.showOpenDialog(options);
}
public startBackgroundOperation(operationInfo: azdata.BackgroundOperationInfo): void {
azdata.tasks.startBackgroundOperation(operationInfo);
}
public openExternal(target: vscode.Uri): Thenable<boolean> {
return vscode.env.openExternal(target);
}
public getExtension(extensionId: string): vscode.Extension<any> | undefined {
return vscode.extensions.getExtension(extensionId);
}
public getConfiguration(section?: string, resource?: vscode.Uri | null): vscode.WorkspaceConfiguration {
return vscode.workspace.getConfiguration(section, resource);
}
public workspaceFolders(): readonly vscode.WorkspaceFolder[] | undefined {
return vscode.workspace.workspaceFolders;
}
public createTab(title: string): azdata.window.DialogTab {
return azdata.window.createTab(title);
}
public createModelViewDialog(title: string, dialogName?: string, isWide?: boolean): azdata.window.Dialog {
return azdata.window.createModelViewDialog(title, dialogName, isWide);
}
public createWizard(title: string): azdata.window.Wizard {
return azdata.window.createWizard(title);
}
public createWizardPage(title: string): azdata.window.WizardPage {
return azdata.window.createWizardPage(title);
}
public openDialog(dialog: azdata.window.Dialog): void {
return azdata.window.openDialog(dialog);
}
public getAllAccounts(): Thenable<azdata.Account[]> {
return azdata.accounts.getAllAccounts();
}
public getSecurityToken(account: azdata.Account, resource: azdata.AzureResource): Thenable<{ [key: string]: any }> {
return azdata.accounts.getSecurityToken(account, resource);
}
public showQuickPick<T extends vscode.QuickPickItem>(items: T[] | Thenable<T[]>, options?: vscode.QuickPickOptions, token?: vscode.CancellationToken): Thenable<T | undefined> {
return vscode.window.showQuickPick(items, options, token);
}
public showInputBox(options?: vscode.InputBoxOptions, token?: vscode.CancellationToken): Thenable<string | undefined> {
return vscode.window.showInputBox(options, token);
}
public listDatabases(connectionId: string): Thenable<string[]> {
return azdata.connection.listDatabases(connectionId);
}
public openTextDocument(options?: { language?: string; content?: string; }): Thenable<vscode.TextDocument> {
return vscode.workspace.openTextDocument(options);
}
public connect(fileUri: string, connectionId: string): Thenable<void> {
return azdata.queryeditor.connect(fileUri, connectionId);
}
public runQuery(fileUri: string, options?: Map<string, string>, runCurrentQuery?: boolean): void {
azdata.queryeditor.runQuery(fileUri, options, runCurrentQuery);
}
public showTextDocument(uri: vscode.Uri, options?: vscode.TextDocumentShowOptions): Thenable<vscode.TextEditor> {
return vscode.window.showTextDocument(uri, options);
}
public createButton(label: string, position?: azdata.window.DialogButtonPosition): azdata.window.Button {
return azdata.window.createButton(label, position);
}
public registerWidget(widgetId: string, handler: (view: azdata.ModelView) => void): void {
azdata.ui.registerModelViewProvider(widgetId, handler);
}
}

View File

@@ -3,33 +3,36 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import * as templates from '../templates/templates'; import * as templates from '../templates/templates';
import * as constants from '../common/constants'; import * as constants from '../common/constants';
import * as path from 'path'; import * as path from 'path';
import { Uri, Disposable, ExtensionContext, WorkspaceFolder } from 'vscode';
import { ApiWrapper } from '../common/apiWrapper';
import { SqlDatabaseProjectTreeViewProvider } from './databaseProjectTreeViewProvider'; import { SqlDatabaseProjectTreeViewProvider } from './databaseProjectTreeViewProvider';
import { getErrorMessage } from '../common/utils'; import { getErrorMessage } from '../common/utils';
import { ProjectsController } from './projectController'; import { ProjectsController } from './projectController';
import { BaseProjectTreeItem } from '../models/tree/baseTreeItem'; import { BaseProjectTreeItem } from '../models/tree/baseTreeItem';
import { NetCoreTool } from '../tools/netcoreTool';
import { Project } from '../models/project';
const SQL_DATABASE_PROJECTS_VIEW_ID = 'sqlDatabaseProjectsView'; const SQL_DATABASE_PROJECTS_VIEW_ID = 'sqlDatabaseProjectsView';
/** /**
* The main controller class that initializes the extension * The main controller class that initializes the extension
*/ */
export default class MainController implements vscode.Disposable { export default class MainController implements Disposable {
protected _context: vscode.ExtensionContext;
protected dbProjectTreeViewProvider: SqlDatabaseProjectTreeViewProvider = new SqlDatabaseProjectTreeViewProvider(); protected dbProjectTreeViewProvider: SqlDatabaseProjectTreeViewProvider = new SqlDatabaseProjectTreeViewProvider();
protected projectsController: ProjectsController; protected projectsController: ProjectsController;
protected netcoreTool: NetCoreTool;
public constructor(context: vscode.ExtensionContext) { public constructor(private context: ExtensionContext, private apiWrapper: ApiWrapper) {
this._context = context; this.projectsController = new ProjectsController(apiWrapper, this.dbProjectTreeViewProvider);
this.projectsController = new ProjectsController(this.dbProjectTreeViewProvider); this.netcoreTool = new NetCoreTool();
} }
public get extensionContext(): vscode.ExtensionContext { public get extensionContext(): ExtensionContext {
return this._context; return this.context;
} }
public deactivate(): void { public deactivate(): void {
@@ -41,21 +44,29 @@ export default class MainController implements vscode.Disposable {
private async initializeDatabaseProjects(): Promise<void> { private async initializeDatabaseProjects(): Promise<void> {
// init commands // init commands
vscode.commands.registerCommand('sqlDatabaseProjects.new', async () => { await this.createNewProject(); }); this.apiWrapper.registerCommand('sqlDatabaseProjects.new', async () => { await this.createNewProject(); });
vscode.commands.registerCommand('sqlDatabaseProjects.open', async () => { await this.openProjectFromFile(); }); this.apiWrapper.registerCommand('sqlDatabaseProjects.open', async () => { await this.openProjectFromFile(); });
vscode.commands.registerCommand('sqlDatabaseProjects.close', (node: BaseProjectTreeItem) => { this.projectsController.closeProject(node); }); this.apiWrapper.registerCommand('sqlDatabaseProjects.close', (node: BaseProjectTreeItem) => { this.projectsController.closeProject(node); });
this.apiWrapper.registerCommand('sqlDatabaseProjects.properties', async (node: BaseProjectTreeItem) => { await this.apiWrapper.showErrorMessage(`Properties not yet implemented: ${node.uri.path}`); }); // TODO
vscode.commands.registerCommand('sqlDatabaseProjects.newScript', async (node: BaseProjectTreeItem) => { await this.projectsController.addItemPrompt(node, templates.script); }); this.apiWrapper.registerCommand('sqlDatabaseProjects.build', async (node: BaseProjectTreeItem) => { await this.projectsController.build(node); });
vscode.commands.registerCommand('sqlDatabaseProjects.newTable', async (node: BaseProjectTreeItem) => { await this.projectsController.addItemPrompt(node, templates.table); }); this.apiWrapper.registerCommand('sqlDatabaseProjects.deploy', async (node: BaseProjectTreeItem) => { await this.projectsController.deploy(node); });
vscode.commands.registerCommand('sqlDatabaseProjects.newView', async (node: BaseProjectTreeItem) => { await this.projectsController.addItemPrompt(node, templates.view); }); this.apiWrapper.registerCommand('sqlDatabaseProjects.import', async (node: BaseProjectTreeItem) => { await this.projectsController.import(node); });
vscode.commands.registerCommand('sqlDatabaseProjects.newStoredProcedure', async (node: BaseProjectTreeItem) => { await this.projectsController.addItemPrompt(node, templates.storedProcedure); });
vscode.commands.registerCommand('sqlDatabaseProjects.newItem', async (node: BaseProjectTreeItem) => { await this.projectsController.addItemPrompt(node); }); this.apiWrapper.registerCommand('sqlDatabaseProjects.newScript', async (node: BaseProjectTreeItem) => { await this.projectsController.addItemPromptFromNode(node, templates.script); });
vscode.commands.registerCommand('sqlDatabaseProjects.newFolder', async (node: BaseProjectTreeItem) => { await this.projectsController.addFolderPrompt(node); }); this.apiWrapper.registerCommand('sqlDatabaseProjects.newTable', async (node: BaseProjectTreeItem) => { await this.projectsController.addItemPromptFromNode(node, templates.table); });
this.apiWrapper.registerCommand('sqlDatabaseProjects.newView', async (node: BaseProjectTreeItem) => { await this.projectsController.addItemPromptFromNode(node, templates.view); });
this.apiWrapper.registerCommand('sqlDatabaseProjects.newStoredProcedure', async (node: BaseProjectTreeItem) => { await this.projectsController.addItemPromptFromNode(node, templates.storedProcedure); });
this.apiWrapper.registerCommand('sqlDatabaseProjects.newItem', async (node: BaseProjectTreeItem) => { await this.projectsController.addItemPromptFromNode(node); });
this.apiWrapper.registerCommand('sqlDatabaseProjects.newFolder', async (node: BaseProjectTreeItem) => { await this.projectsController.addFolderPrompt(node); });
// init view // init view
this.extensionContext.subscriptions.push(vscode.window.registerTreeDataProvider(SQL_DATABASE_PROJECTS_VIEW_ID, this.dbProjectTreeViewProvider)); this.extensionContext.subscriptions.push(this.apiWrapper.registerTreeDataProvider(SQL_DATABASE_PROJECTS_VIEW_ID, this.dbProjectTreeViewProvider));
await templates.loadTemplates(path.join(this._context.extensionPath, 'resources', 'templates')); await templates.loadTemplates(path.join(this.context.extensionPath, 'resources', 'templates'));
// ensure .net core is installed
this.netcoreTool.findOrInstallNetCore();
} }
/** /**
@@ -68,7 +79,7 @@ export default class MainController implements vscode.Disposable {
filter[constants.sqlDatabaseProject] = ['sqlproj']; filter[constants.sqlDatabaseProject] = ['sqlproj'];
let files: vscode.Uri[] | undefined = await vscode.window.showOpenDialog({ filters: filter }); let files: Uri[] | undefined = await this.apiWrapper.showOpenDialog({ filters: filter });
if (files) { if (files) {
for (const file of files) { for (const file of files) {
@@ -77,48 +88,50 @@ export default class MainController implements vscode.Disposable {
} }
} }
catch (err) { catch (err) {
vscode.window.showErrorMessage(getErrorMessage(err)); this.apiWrapper.showErrorMessage(getErrorMessage(err));
} }
} }
/** /**
* Creates a new SQL database project from a template, prompting the user for a name and location * Creates a new SQL database project from a template, prompting the user for a name and location
*/ */
public async createNewProject(): Promise<void> { public async createNewProject(): Promise<Project | undefined> {
try { try {
let newProjName = await vscode.window.showInputBox({ let newProjName = await this.apiWrapper.showInputBox({
prompt: constants.newDatabaseProjectName, prompt: constants.newDatabaseProjectName,
value: `DatabaseProject${this.projectsController.projects.length + 1}` value: `DatabaseProject${this.projectsController.projects.length + 1}`
// TODO: Smarter way to suggest a name. Easy if we prompt for location first, but that feels odd... // TODO: Smarter way to suggest a name. Easy if we prompt for location first, but that feels odd...
}); });
newProjName = newProjName?.trim();
if (!newProjName) { if (!newProjName) {
// TODO: is this case considered an intentional cancellation (shouldn't warn) or an error case (should warn)? // TODO: is this case considered an intentional cancellation (shouldn't warn) or an error case (should warn)?
vscode.window.showErrorMessage(constants.projectNameRequired); this.apiWrapper.showErrorMessage(constants.projectNameRequired);
return; return undefined;
} }
let selectionResult = await vscode.window.showOpenDialog({ let selectionResult = await this.apiWrapper.showOpenDialog({
canSelectFiles: false, canSelectFiles: false,
canSelectFolders: true, canSelectFolders: true,
canSelectMany: false, canSelectMany: false,
defaultUri: vscode.workspace.workspaceFolders ? vscode.workspace.workspaceFolders[0].uri : undefined defaultUri: this.apiWrapper.workspaceFolders() ? (this.apiWrapper.workspaceFolders() as WorkspaceFolder[])[0].uri : undefined
}); });
if (!selectionResult) { if (!selectionResult) {
vscode.window.showErrorMessage(constants.projectLocationRequired); this.apiWrapper.showErrorMessage(constants.projectLocationRequired);
return; return undefined;
} }
// TODO: what if the selected folder is outside the workspace? // TODO: what if the selected folder is outside the workspace?
const newProjFolderUri = (selectionResult as vscode.Uri[])[0]; const newProjFolderUri = (selectionResult as Uri[])[0];
console.log(newProjFolderUri.fsPath); const newProjFilePath = await this.projectsController.createNewProject(newProjName as string, newProjFolderUri as Uri);
const newProjFilePath = await this.projectsController.createNewProject(newProjName as string, newProjFolderUri as vscode.Uri); return this.projectsController.openProject(Uri.file(newProjFilePath));
await this.projectsController.openProject(vscode.Uri.file(newProjFilePath));
} }
catch (err) { catch (err) {
vscode.window.showErrorMessage(getErrorMessage(err)); this.apiWrapper.showErrorMessage(getErrorMessage(err));
return undefined;
} }
} }

View File

@@ -3,7 +3,6 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import * as path from 'path'; import * as path from 'path';
import * as constants from '../common/constants'; import * as constants from '../common/constants';
import * as dataSources from '../models/dataSources/dataSources'; import * as dataSources from '../models/dataSources/dataSources';
@@ -11,6 +10,8 @@ import * as utils from '../common/utils';
import * as UUID from 'vscode-languageclient/lib/utils/uuid'; import * as UUID from 'vscode-languageclient/lib/utils/uuid';
import * as templates from '../templates/templates'; import * as templates from '../templates/templates';
import { Uri, QuickPickItem } from 'vscode';
import { ApiWrapper } from '../common/apiWrapper';
import { Project } from '../models/project'; import { Project } from '../models/project';
import { SqlDatabaseProjectTreeViewProvider } from './databaseProjectTreeViewProvider'; import { SqlDatabaseProjectTreeViewProvider } from './databaseProjectTreeViewProvider';
import { promises as fs } from 'fs'; import { promises as fs } from 'fs';
@@ -26,7 +27,7 @@ export class ProjectsController {
projects: Project[] = []; projects: Project[] = [];
constructor(projTreeViewProvider: SqlDatabaseProjectTreeViewProvider) { constructor(private apiWrapper: ApiWrapper, projTreeViewProvider: SqlDatabaseProjectTreeViewProvider) {
this.projectTreeViewProvider = projTreeViewProvider; this.projectTreeViewProvider = projTreeViewProvider;
} }
@@ -35,29 +36,29 @@ export class ProjectsController {
this.projectTreeViewProvider.load(this.projects); this.projectTreeViewProvider.load(this.projects);
} }
public async openProject(projectFile: vscode.Uri): Promise<Project> { public async openProject(projectFile: Uri): Promise<Project> {
for (const proj of this.projects) { for (const proj of this.projects) {
if (proj.projectFilePath === projectFile.fsPath) { if (proj.projectFilePath === projectFile.fsPath) {
vscode.window.showInformationMessage(constants.projectAlreadyOpened(projectFile.fsPath)); this.apiWrapper.showInformationMessage(constants.projectAlreadyOpened(projectFile.fsPath));
return proj; return proj;
} }
} }
// Read project file
const newProject = new Project(projectFile.fsPath); const newProject = new Project(projectFile.fsPath);
await newProject.readProjFile();
this.projects.push(newProject);
// Read datasources.json (if present)
const dataSourcesFilePath = path.join(path.dirname(projectFile.fsPath), constants.dataSourcesFileName);
try { try {
// Read project file
await newProject.readProjFile();
this.projects.push(newProject);
// Read datasources.json (if present)
const dataSourcesFilePath = path.join(path.dirname(projectFile.fsPath), constants.dataSourcesFileName);
newProject.dataSources = await dataSources.load(dataSourcesFilePath); newProject.dataSources = await dataSources.load(dataSourcesFilePath);
} }
catch (err) { catch (err) {
if (err instanceof dataSources.NoDataSourcesFileError) { if (err instanceof dataSources.NoDataSourcesFileError) {
// TODO: prompt to create new datasources.json; for now, swallow // TODO: prompt to create new datasources.json; for now, swallow
console.log(`No ${constants.dataSourcesFileName} file found.`);
} }
else { else {
throw err; throw err;
@@ -69,7 +70,7 @@ export class ProjectsController {
return newProject; return newProject;
} }
public async createNewProject(newProjName: string, folderUri: vscode.Uri, projectGuid?: string): Promise<string> { public async createNewProject(newProjName: string, folderUri: Uri, projectGuid?: string): Promise<string> {
if (projectGuid && !UUID.isUUID(projectGuid)) { if (projectGuid && !UUID.isUUID(projectGuid)) {
throw new Error(`Specified GUID is invalid: '${projectGuid}'`); throw new Error(`Specified GUID is invalid: '${projectGuid}'`);
} }
@@ -112,6 +113,21 @@ export class ProjectsController {
this.refreshProjectsTree(); this.refreshProjectsTree();
} }
public async build(treeNode: BaseProjectTreeItem) {
const project = this.getProjectContextFromTreeNode(treeNode);
await this.apiWrapper.showErrorMessage(`Build not yet implemented: ${project.projectFilePath}`); // TODO
}
public async deploy(treeNode: BaseProjectTreeItem) {
const project = this.getProjectContextFromTreeNode(treeNode);
await this.apiWrapper.showErrorMessage(`Deploy not yet implemented: ${project.projectFilePath}`); // TODO
}
public async import(treeNode: BaseProjectTreeItem) {
const project = this.getProjectContextFromTreeNode(treeNode);
await this.apiWrapper.showErrorMessage(`Import not yet implemented: ${project.projectFilePath}`); // TODO
}
public async addFolderPrompt(treeNode: BaseProjectTreeItem) { public async addFolderPrompt(treeNode: BaseProjectTreeItem) {
const project = this.getProjectContextFromTreeNode(treeNode); const project = this.getProjectContextFromTreeNode(treeNode);
const newFolderName = await this.promptForNewObjectName(new templates.ProjectScriptType(templates.folder, constants.folderFriendlyName, ''), project); const newFolderName = await this.promptForNewObjectName(new templates.ProjectScriptType(templates.folder, constants.folderFriendlyName, ''), project);
@@ -120,26 +136,28 @@ export class ProjectsController {
return; // user cancelled return; // user cancelled
} }
const relativeFolderPath = this.prependContextPath(treeNode, newFolderName); const relativeFolderPath = path.join(this.getRelativePath(treeNode), newFolderName);
await project.addFolderItem(relativeFolderPath); await project.addFolderItem(relativeFolderPath);
this.refreshProjectsTree(); this.refreshProjectsTree();
} }
public async addItemPrompt(treeNode: BaseProjectTreeItem, itemTypeName?: string) { public async addItemPromptFromNode(treeNode: BaseProjectTreeItem, itemTypeName?: string) {
const project = this.getProjectContextFromTreeNode(treeNode); await this.addItemPrompt(this.getProjectContextFromTreeNode(treeNode), this.getRelativePath(treeNode), itemTypeName);
}
public async addItemPrompt(project: Project, relativePath: string, itemTypeName?: string) {
if (!itemTypeName) { if (!itemTypeName) {
let itemFriendlyNames: string[] = []; const items: QuickPickItem[] = [];
for (const itemType of templates.projectScriptTypes()) { for (const itemType of templates.projectScriptTypes()) {
itemFriendlyNames.push(itemType.friendlyName); items.push({ label: itemType.friendlyName });
} }
itemTypeName = await vscode.window.showQuickPick(itemFriendlyNames, { itemTypeName = (await this.apiWrapper.showQuickPick(items, {
canPickMany: false canPickMany: false
}); }))?.label;
if (!itemTypeName) { if (!itemTypeName) {
return; // user cancelled return; // user cancelled
@@ -147,7 +165,9 @@ export class ProjectsController {
} }
const itemType = templates.projectScriptTypeMap()[itemTypeName.toLocaleLowerCase()]; const itemType = templates.projectScriptTypeMap()[itemTypeName.toLocaleLowerCase()];
const itemObjectName = await this.promptForNewObjectName(itemType, project); let itemObjectName = await this.promptForNewObjectName(itemType, project);
itemObjectName = itemObjectName?.trim();
if (!itemObjectName) { if (!itemObjectName) {
return; // user cancelled return; // user cancelled
@@ -156,11 +176,11 @@ export class ProjectsController {
// TODO: file already exists? // TODO: file already exists?
const newFileText = this.macroExpansion(itemType.templateScript, { 'OBJECT_NAME': itemObjectName }); const newFileText = this.macroExpansion(itemType.templateScript, { 'OBJECT_NAME': itemObjectName });
const relativeFilePath = this.prependContextPath(treeNode, itemObjectName + '.sql'); const relativeFilePath = path.join(relativePath, itemObjectName + '.sql');
const newEntry = await project.addScriptItem(relativeFilePath, newFileText); const newEntry = await project.addScriptItem(relativeFilePath, newFileText);
vscode.commands.executeCommand('vscode.open', newEntry.fsUri); this.apiWrapper.executeCommand('vscode.open', newEntry.fsUri);
this.refreshProjectsTree(); this.refreshProjectsTree();
} }
@@ -193,7 +213,7 @@ export class ProjectsController {
return (treeNode.root as ProjectRootTreeItem).project; return (treeNode.root as ProjectRootTreeItem).project;
} }
else { else {
throw new Error('"Add item" command invoked from unexpected location: ' + treeNode.uri.path); throw new Error('Unable to establish project context. Command invoked from unexpected location: ' + treeNode.uri.path);
} }
} }
@@ -201,7 +221,7 @@ export class ProjectsController {
// TODO: ask project for suggested name that doesn't conflict // TODO: ask project for suggested name that doesn't conflict
const suggestedName = itemType.friendlyName.replace(new RegExp('\s', 'g'), '') + '1'; const suggestedName = itemType.friendlyName.replace(new RegExp('\s', 'g'), '') + '1';
const itemObjectName = await vscode.window.showInputBox({ const itemObjectName = await this.apiWrapper.showInputBox({
prompt: constants.newObjectNamePrompt(itemType.friendlyName), prompt: constants.newObjectNamePrompt(itemType.friendlyName),
value: suggestedName, value: suggestedName,
}); });
@@ -209,13 +229,8 @@ export class ProjectsController {
return itemObjectName; return itemObjectName;
} }
private prependContextPath(treeNode: BaseProjectTreeItem, objectName: string): string { private getRelativePath(treeNode: BaseProjectTreeItem): string {
if (treeNode instanceof FolderNode) { return treeNode instanceof FolderNode ? utils.trimUri(treeNode.root.uri, treeNode.uri) : '';
return path.join(utils.trimUri(treeNode.root.uri, treeNode.uri), objectName);
}
else {
return objectName;
}
} }
//#endregion //#endregion

View File

@@ -5,12 +5,13 @@
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import MainController from './controllers/mainController'; import MainController from './controllers/mainController';
import { ApiWrapper } from './common/apiWrapper';
let controllers: MainController[] = []; let controllers: MainController[] = [];
export async function activate(context: vscode.ExtensionContext): Promise<void> { export async function activate(context: vscode.ExtensionContext): Promise<void> {
// Start the main controller // Start the main controller
const mainController = new MainController(context); const mainController = new MainController(context, new ApiWrapper());
controllers.push(mainController); controllers.push(mainController);
context.subscriptions.push(mainController); context.subscriptions.push(mainController);

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