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
name: Merge coverage reports
- name: Upload Code Coverage
uses: coverallsapp/github-action@v1.0.1
uses: coverallsapp/github-action@v1.1.1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
path-to-lcov: '.build/coverage-combined/lcov.info'

View File

@@ -19,5 +19,6 @@ jobs:
- name: Run CopyCat
uses: ./actions/build/actions/copycat
with:
token: ${{secrets.TRIAGE_PAT}}
owner: anthonydresser
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
}
},
{
"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",
"request": "launch",

View File

@@ -2,7 +2,24 @@
# Flags: CaseSensitive WordMatch
# 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:
401

View File

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

View File

@@ -1,5 +1,10 @@
# 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
* Release date: April 27, 2020
* 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-pty: https://github.com/Tyriar/node-pty
nsfw: https://github.com/Axosoft/nsfw
optimist: https://github.com/substack/node-optimist
primeng: https://github.com/primefaces/primeng
process-nextick-args: https://github.com/calvinmetcalf/process-nextick-args
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
winreg: https://github.com/fresc81/node-winreg
xterm: https://github.com/sourcelair/xterm.js
yargs: https://github.com/yargs/yargs
yauzl: https://github.com/thejoshwolfe/yauzl
zone.js: https://www.npmjs.com/package/zone
@@ -1451,6 +1453,32 @@ SOFTWARE.
=========================================
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
=========================================
The MIT License (MIT)
@@ -2222,6 +2250,32 @@ THE SOFTWARE.
=========================================
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
=========================================
The MIT License (MIT)

View File

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

@@ -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)
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: |
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 default-keychain -s $(agent.tempdirectory)/buildagent.keychain
security unlock-keychain -p pwd $(agent.tempdirectory)/buildagent.keychain
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 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
- script: |

View File

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

View File

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

View File

@@ -140,6 +140,13 @@ steps:
displayName: Run integration tests (Browser)
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: |
set -e
yarn gulp "vscode-linux-x64-build-deb"

View File

@@ -16,12 +16,10 @@ steps:
- task: NodeTool@0
inputs:
versionSpec: "10.15.1"
condition: and(succeeded(), ne(variables['CacheRestored-Compilation'], 'true'))
- task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@3
inputs:
versionSpec: "1.x"
condition: and(succeeded(), ne(variables['CacheRestored-Compilation'], 'true'))
- task: AzureKeyVault@1
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'
targetfolder: '**/node_modules, !**/node_modules/**/node_modules, !samples/**/node_modules'
vstsFeed: 'BuildCache'
condition: and(succeeded(), ne(variables['CacheRestored-Compilation'], 'true'))
- script: |
set -e
CHILD_CONCURRENCY=1 yarn --frozen-lockfile
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
inputs:
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'
vstsFeed: 'BuildCache'
condition: and(succeeded(), ne(variables['CacheRestored-Compilation'], 'true'), ne(variables['CacheRestored'], 'true'))
condition: and(succeeded(), ne(variables['CacheRestored'], 'true'))
- script: |
set -e
yarn postinstall
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
# inline small SVGs
@@ -113,7 +110,6 @@ steps:
node build/azure-pipelines/common/copyArtifacts.js
displayName: Write Version Information
condition: and(succeeded(), ne(variables['CacheRestored-Compilation'], 'true'))
- task: PublishBuildArtifacts@1
displayName: 'Publish Artifact: drop'

View File

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

View File

@@ -149,6 +149,13 @@ steps:
displayName: Run integration tests (Browser)
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
inputs:
ConnectedServiceName: 'ESRP CodeSign'

View File

@@ -44,7 +44,7 @@ function prepareDebPackage(arch) {
.pipe(replace('@@NAME_SHORT@@', product.nameShort))
.pipe(replace('@@NAME@@', 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));
const appdata = gulp.src('resources/linux/code.appdata.xml', { base: '.' })

View File

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

View File

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

View File

@@ -48,7 +48,7 @@
"rollup-plugin-commonjs": "^10.1.0",
"rollup-plugin-node-resolve": "^5.2.0",
"terser": "4.3.8",
"typescript": "^3.9.0-dev.20200427",
"typescript": "^3.9.1-rc",
"vsce": "1.48.0",
"vscode-telemetry-extractor": "^1.5.4",
"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"
integrity sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g==
typescript@^3.9.0-dev.20200427:
version "3.9.0-dev.20200427"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.0-dev.20200427.tgz#e42d606d938575dfb7b0b66f04a31b5f0eb0be57"
integrity sha512-ja/GhL7BHT+VQZiLoYMGJt2CP1Pdr0EhYefv4LLw4tVooSuCDB8SDKT/i/HwsoPgQ4ZaYfg1vPl+1RhiO3bwJg==
typescript@^3.9.1-rc:
version "3.9.1-rc"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.1-rc.tgz#81d5a5a0a597e224b6e2af8dffb46524b2eaf5f3"
integrity sha512-+cPv8L2Vd4KidCotqi2wjegBZ5n47CDRUu/QiLVu2YbeXAz78hIfcai9ziBiNI6JTGTVwUqXRug2UZxDcxhvFw==
typical@^4.0.0:
version "4.0.0"

View File

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

View File

@@ -325,6 +325,7 @@ export abstract class AzureAuth implements vscode.Disposable {
return tenants;
} catch (ex) {
console.log(ex);
console.log(JSON.stringify(ex?.response?.data, undefined, 2));
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.
*--------------------------------------------------------------------------------------------*/
import { WorkspaceConfiguration, ConfigurationTarget } from 'vscode';
import { Account } from 'azdata';
import { azureResource } from '../azure-resource';
@@ -53,25 +52,8 @@ export class AzureResourceSubscriptionFilterService implements IAzureResourceSub
for (const accountId in selectedSubscriptionsCache) {
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 _cacheKey: string = undefined;
private static readonly filterConfigName = 'azure.resource.config.filter';
}

View File

@@ -59,7 +59,6 @@ export class IconPathHelper {
export namespace cssStyles {
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 hyperlink = { 'user-select': 'text', 'color': '#0078d4', 'text-decoration': 'underline', 'cursor': 'pointer' };
export const text = { 'margin-block-start': '0px', 'margin-block-end': '0px' };
export const overflowEllipsisText = { ...text, 'overflow': 'hidden', 'text-overflow': 'ellipsis' };
export const nonSelectableText = { ...cssStyles.text, 'user-select': 'none' };

View File

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

View File

@@ -304,7 +304,7 @@ export class BdcDashboardResourceStatusPage extends BdcDashboardPage {
url: instanceStatus.dashboards.nodeMetricsUrl,
title: instanceStatus.dashboards.nodeMetricsUrl,
ariaLabel: loc.viewNodeMetrics(instanceStatus.dashboards.nodeMetricsUrl),
CSSStyles: { ...cssStyles.text, ...cssStyles.hyperlink }
CSSStyles: { ...cssStyles.text }
}).component());
}
@@ -319,7 +319,7 @@ export class BdcDashboardResourceStatusPage extends BdcDashboardPage {
url: instanceStatus.dashboards.sqlMetricsUrl,
title: instanceStatus.dashboards.sqlMetricsUrl,
ariaLabel: loc.viewSqlMetrics(instanceStatus.dashboards.sqlMetricsUrl),
CSSStyles: { ...cssStyles.text, ...cssStyles.hyperlink }
CSSStyles: { ...cssStyles.text }
}).component());
}
}
@@ -332,7 +332,7 @@ export class BdcDashboardResourceStatusPage extends BdcDashboardPage {
url: instanceStatus.dashboards.logsUrl,
title: instanceStatus.dashboards.logsUrl,
ariaLabel: loc.viewLogs(instanceStatus.dashboards.logsUrl),
CSSStyles: { ...cssStyles.text, ...cssStyles.hyperlink }
CSSStyles: { ...cssStyles.text }
}).component());
}
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 ControllerBase from './controllerBase';
import * as vscode from 'vscode';
import { DataTierApplicationWizard } from '../wizard/dataTierApplicationWizard';
/**
* 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) {
super(context);
public constructor(private context: vscode.ExtensionContext) {
}
/**
*/
public deactivate(): void {
}
@@ -29,4 +26,12 @@ export default class MainController extends ControllerBase {
private initializeDacFxWizard() {
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 ControllerBase from './controllers/controllerBase';
import MainController from './controllers/mainController';
let controllers: ControllerBase[] = [];
let controllers: MainController[] = [];
export function activate(context: vscode.ExtensionContext) {
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 loc from '../localizedConstants';
import { DataTierApplicationWizard, Operation } from '../wizard/dataTierApplicationWizard';
import { DacFxDataModel } from '../wizard/api/models';
let wizard: DataTierApplicationWizard;
describe('Dacfx wizard', function (): void {
beforeEach(async function (): Promise<void> {
wizard = new DataTierApplicationWizard();
wizard.model = <DacFxDataModel>{};
});
it('Should initialize wizard correctly', async () => {
let wizard = new DataTierApplicationWizard();
should.notEqual(wizard.wizard, undefined);
should.equal(wizard.wizard.title, loc.wizardTitle);
@@ -18,8 +24,50 @@ describe('Dacfx wizard', function (): void {
should.notEqual(wizard.pages, undefined);
should.equal(wizard.pages.size, 7);
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);
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 {
public wizard: azdata.window.Wizard;
private connection: azdata.connection.ConnectionProfile;
private model: DacFxDataModel;
public model: DacFxDataModel;
public pages: Map<string, Page> = new Map<string, Page>();
public selectedOperation: Operation;
@@ -331,7 +331,7 @@ export class DataTierApplicationWizard {
return page;
}
private isSummaryPage(idx: number): boolean {
public isSummaryPage(idx: number): boolean {
return this.selectedOperation === Operation.import && idx === ImportOperationPath.summary
|| this.selectedOperation === Operation.export && idx === ExportOperationPath.summary
|| this.selectedOperation === Operation.extract && idx === ExtractOperationPath.summary

View File

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

View File

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

View File

@@ -2494,6 +2494,10 @@ export class CommandCenter {
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 {
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> {
const args = ['rebase', '--continue'];

View File

@@ -6,7 +6,7 @@
import * as nls from 'vscode-nls';
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 { Model } from './model';
import { CommandCenter } from './commands';
@@ -78,7 +78,7 @@ async function createModel(context: ExtensionContext, outputChannel: OutputChann
new GitTimelineProvider(model)
);
await checkGitVersion(info);
// await checkGitVersion(info); {{SQL CARBON EDIT}} Don't check git version
return model;
}
@@ -180,13 +180,8 @@ export async function activate(context: ExtensionContext): Promise<GitExtension>
return result;
}
async function checkGitVersion(_info: IGit): Promise<void> { // {{SQL CARBON EDIT}} - Rename info to _info to prevent error due to unused variable
return; /* {{SQL CARBON EDIT}} return immediately
/*const config = workspace.getConfiguration('git');
const shouldIgnore = config.get<boolean>('ignoreLegacyWarning') === true;
// @ts-expect-error
async function checkGitVersion(info: IGit): Promise<void> {
const config = workspace.getConfiguration('git');
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/'));
} else if (choice === neverShowAgain) {
await config.update('ignoreLegacyWarning', true, true);
}*/
}
}

View File

@@ -303,6 +303,7 @@ export const enum Operation {
CheckIgnore = 'CheckIgnore',
GetObjectDetails = 'GetObjectDetails',
SubmoduleUpdate = 'SubmoduleUpdate',
RebaseAbort = 'RebaseAbort',
RebaseContinue = 'RebaseContinue',
FindTrackingBranches = 'GetTracking',
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>> {
return this.run(Operation.CheckIgnore, () => {
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 connectionId = (await azdata.connection.getCurrentConnection())?.connectionId ??
(await azdata.connection.openConnectionDialog())?.connectionId;
let currentConnection = await azdata.connection.getCurrentConnection();
if (!connectionId) {
vscode.window.showErrorMessage(localize('import.needConnection', "Please connect to a server before using this wizard."));
return;
let connectionId: string;
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;
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.model.database = (<azdata.CategoryValue>this.databaseDropdown.value).name;
//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();
});
@@ -146,13 +149,25 @@ export class FileConfigPage extends ImportPage {
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({
values: values
});
this.databaseDropdown.value = { displayName: this.model.database, name: this.model.database };
this.databaseLoader.loading = false;
return true;
@@ -266,11 +281,11 @@ export class FileConfigPage extends ImportPage {
private async populateSchemaDropdown(): Promise<boolean> {
this.schemaLoader.loading = true;
let connectionUri = await azdata.connection.getUriForConnection(this.model.server.connectionId);
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 ${escapedQuotedDb}sys.schemas`;
const query = `SELECT name FROM sys.schemas`;
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>({
data: undefined,
columns: undefined,
forceFitColumns: azdata.ColumnSizingMode.AutoFit
forceFitColumns: azdata.ColumnSizingMode.DataFit
}).component();
this.refresh = this.view.modelBuilder.button().withProperties({
label: localize('flatFileImport.refresh', "Refresh"),

View File

@@ -28,13 +28,11 @@
"devDependencies": {
"@types/chai": "3.4.34",
"@types/node": "^10.14.8",
"azure-keyvault": "^3.0.4",
"chai": "3.5.0",
"mocha-junit-reporter": "^1.17.0",
"mocha-multi-reporters": "^1.1.7",
"ms-rest-azure": "^2.6.0",
"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;
}
namespace SettingIds {
export const enableFormatter = 'json.format.enable';
export const enableSchemaDownload = 'json.schemaDownload.enable';
export const maxItemsComputed = 'json.maxItemsComputed';
}
let telemetryReporter: TelemetryReporter | undefined;
export function activate(context: ExtensionContext) {
@@ -107,10 +113,8 @@ export function activate(context: ExtensionContext) {
id: 'status.json.resolveError',
name: localize('json.resolveError', "JSON: Schema Resolution Error"),
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)';
toDispose.push(schemaResolutionErrorStatusBarItem);
@@ -200,6 +204,7 @@ export function activate(context: ExtensionContext) {
toDispose.push(disposable);
client.onReady().then(() => {
const schemaDocuments: { [uri: string]: boolean } = {};
let schemaDownloadEnabled = true;
// handle content request
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())));
}
if (uri.scheme !== 'http' && uri.scheme !== 'https') {
return workspace.openTextDocument(uri).then(doc => {
schemaDocuments[uri.toString()] = true;
return doc.getText();
}, error => {
return Promise.reject(error);
});
if (schemaDownloadEnabled) {
return workspace.openTextDocument(uri).then(doc => {
schemaDocuments[uri.toString()] = true;
return doc.getText();
}, error => {
return Promise.reject(error);
});
} else {
return Promise.reject(localize('schemaDownloadDisabled', 'Downloading schemas is disabled through setting \'{0}\'', SettingIds.enableSchemaDownload));
}
} else {
if (telemetryReporter && uri.authority === 'schema.management.azure.com') {
/* __GDPR__
@@ -294,16 +303,61 @@ export function activate(context: ExtensionContext) {
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();
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 => {
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 = {
@@ -316,30 +370,6 @@ export function activate(context: ExtensionContext) {
languages.setLanguageConfiguration('json', 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 {
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 = {
http: {

View File

@@ -95,7 +95,13 @@
"type": "number",
"default": 5000,
"description": "%json.maxItemsComputed.desc%"
}
},
"json.schemaDownload.enable": {
"type": "boolean",
"default": true,
"description": "%json.enableSchemaDownload.desc%",
"tags": ["usesOnlineServices"]
}
}
},
"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.schemaResolutionErrorMessage": "Unable to resolve schema.",
"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": [
{
"name": "RODBCext",
"repository": "https://cran.microsoft.com"
"repository": "https://mran.microsoft.com/snapshot/2019-02-01/"
},
{
"name": "sqlmlutils",
"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"

View File

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

View File

@@ -67,7 +67,6 @@ export class PackageManager {
*/
public async managePackages(): Promise<void> {
try {
await this.enableExternalScript();
// 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 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[] = [
'formals(quit)$save <- formals(q)$save <- "no"',
'library(sqlmlutils)',
`connection <- connectionInfo(${connectionParts})`,
`connection <- connectionInfo(driver= "ODBC Driver 17 for SQL Server", ${connectionParts})`,
`r = getOption("repos")`,
`r["CRAN"] = "${this._config.rPackagesRepository}"`,
`options(repos = r)`,

View File

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

View File

@@ -51,7 +51,8 @@ export function createViewContext(): ViewTestContext {
removeItem: () => true,
insertItem: () => { },
items: [],
setLayout: () => { }
setLayout: () => { },
setItemLayout: () => { }
};
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,
state: any
): Promise<void> {
console.log(state);
const resource = vscode.Uri.parse(state.resource);
const locked = state.locked;
const line = state.line;

View File

@@ -1,6 +1,6 @@
{
"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": {
"Windows_86": "win-x86-netcoreapp2.2.zip",
"Windows_64": "win-x64-netcoreapp2.2.zip",

View File

@@ -366,6 +366,28 @@
"displayName": "%onprem.serverProperties.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%",
"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%",
"value": "serverEdition"
}
],
"databasesListProperties": [
{
"displayName": "%databasesListProperties.name%",
"value": "name",
"widthWeight": 100
}
]
}
]
@@ -1043,7 +1089,7 @@
"figures": "^2.0.0",
"find-remove": "1.2.1",
"request": "^2.88.0",
"request-promise": "^4.2.2",
"request-light": "^0.3.0",
"service-downloader": "0.2.1",
"stream-meter": "^1.0.4",
"through2": "^3.0.1",
@@ -1057,7 +1103,6 @@
"@types/chai": "^4.2.11",
"@types/mocha": "^7.0.2",
"@types/request": "^2.48.2",
"@types/request-promise": "^4.1.44",
"@types/stream-meter": "^0.0.22",
"@types/through2": "^2.0.34",
"chai": "^4.2.0",

View File

@@ -140,5 +140,10 @@
"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.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 objectExplorerPrefix: string = 'objectexplorer://';
export const ViewType = 'view';
export const SqlAssessmentService = 'sqlAssessmentService';
export enum BuiltInCommands {
SetContext = 'setContext'

View File

@@ -674,6 +674,35 @@ export namespace SchemaCompareCancellationRequest {
// ------------------------------- <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> -----------------------------
export namespace SerializeDataStartRequest {
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 dacFx: IDacFxService;
readonly sqlAssessment: ISqlAssessmentService;
}
/**
@@ -463,3 +465,47 @@ export interface ListRegisteredServersResult {
registeredServerGroups: Array<RegisteredServerGroup>;
}
//#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 { IExtension, ICmsService, IDacFxService, ISchemaCompareService, MssqlObjectExplorerBrowser, ILanguageExtensionService } from './mssql';
import { IExtension, ICmsService, IDacFxService, ISchemaCompareService, MssqlObjectExplorerBrowser, ILanguageExtensionService, ISqlAssessmentService } from './mssql';
import * as constants from './constants';
import { MssqlObjectExplorerNodeProvider } from './objectExplorerNodeProvider/objectExplorerNodeProvider';
import * as azdata from 'azdata';
@@ -30,6 +30,9 @@ export function createMssqlApi(context: AppContext): IExtension {
return <any>oeProvider.findSqlClusterNodeByContext(explorerContext);
}
};
},
get sqlAssessment() {
return context.getService<ISqlAssessmentService>(constants.SqlAssessmentService);
}
};
}

View File

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

View File

@@ -10,21 +10,9 @@ import * as constants from '../../../constants';
import { SqlClusterConnection } from '../../../objectExplorerNodeProvider/connection';
import * as utils from '../../../utils';
import * as auth from '../../../util/auth';
import { Options } from 'request-promise';
import * as request from 'request-light';
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> {
try {
let livyUrl: string = `https://${submissionArgs.host}:${submissionArgs.port}${submissionArgs.livyPath}/`;
@@ -32,12 +20,11 @@ export class SparkJobSubmissionService {
// Get correct authentication headers
let headers = await this.getAuthenticationHeaders(submissionArgs);
let options: Options = {
uri: livyUrl,
method: 'POST',
json: true,
rejectUnauthorized: !auth.getIgnoreSslVerificationConfigSetting(),
body: {
let options: request.XHROptions = {
url: livyUrl,
type: 'POST',
strictSSL: !auth.getIgnoreSslVerificationConfigSetting(),
data: {
file: submissionArgs.sparkFile,
proxyUser: submissionArgs.user,
className: submissionArgs.mainClass,
@@ -51,7 +38,7 @@ export class SparkJobSubmissionService {
if (submissionArgs.jobArguments && submissionArgs.jobArguments.trim()) {
let argsList = submissionArgs.jobArguments.split(' ');
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()) {
let jarList = submissionArgs.jarFileList.split(';');
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()) {
let pyList = submissionArgs.pyFileList.split(';');
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()) {
let otherList = submissionArgs.otherFileList.split(';');
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)) {
return response.id;
}
@@ -108,16 +101,19 @@ export class SparkJobSubmissionService {
let livyUrl = `https://${submissionArgs.host}:${submissionArgs.port}${submissionArgs.livyPath}/${livyBatchId}/log`;
let headers = await this.getAuthenticationHeaders(submissionArgs);
let options = {
uri: livyUrl,
method: 'GET',
json: true,
rejectUnauthorized: !auth.getIgnoreSslVerificationConfigSetting(),
let options: request.XHROptions = {
url: livyUrl,
type: 'GET',
strictSSL: !auth.getIgnoreSslVerificationConfigSetting(),
// authentication 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) {
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 * as nls from 'vscode-nls';
import { LanguageExtensionService } from './languageExtension/languageExtensionService';
import { SqlAssessmentService } from './sqlAssessment/sqlAssessmentService';
const localize = nls.loadMessageBundle();
const outputChannel = vscode.window.createOutputChannel(Constants.serviceName);
@@ -157,7 +158,8 @@ function getClientOptions(context: AppContext): ClientOptions {
SchemaCompareService.asFeature(context),
LanguageExtensionService.asFeature(context),
DacFxService.asFeature(context),
CmsService.asFeature(context)
CmsService.asFeature(context),
SqlAssessmentService.asFeature(context)
],
outputChannel: new CustomOutputChannel()
};

View File

@@ -2,11 +2,6 @@
# 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":
version "3.1.0"
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"
integrity sha512-eWQGP3qtxwL8FGneRrC5DwrJLGN4/dH1clNTuLfN81HCrxVtxRjygDTUoZJ5ASlDEeo0ppYFQjQIlXhtXpOn6g==
"@types/request-promise@^4.1.44":
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":
"@types/request@^2.48.2":
version "2.48.4"
resolved "https://registry.yarnpkg.com/@types/request/-/request-2.48.4.tgz#df3d43d7b9ed3550feaa1286c6eabf0738e6cf7e"
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"
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:
version "1.1.11"
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"
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"
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz#4ee7a737abd92678a293d9b34a1af4d0d08c787b"
integrity sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==
@@ -1493,22 +1475,14 @@ remap-istanbul@^0.11.1:
source-map "^0.6.1"
through2 "2.0.1"
request-promise-core@1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.3.tgz#e9a3c081b51380dfea677336061fea879a829ee9"
integrity sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ==
request-light@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.3.0.tgz#04daa783e7f0a70392328dda4b546f3e27845f2d"
integrity sha512-xlVlZVT0ZvCT+c3zm3SjeFCzchoQxsUUmx5fkal0I6RIDJK+lmb1UYyKJ7WM4dTfnzHP4ElWwAf8Dli8c0/tVA==
dependencies:
lodash "^4.17.15"
request-promise@^4.2.2:
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"
http-proxy-agent "^2.1.0"
https-proxy-agent "^2.2.4"
vscode-nls "^4.1.1"
request@^2.88.0:
version "2.88.2"
@@ -1642,11 +1616,6 @@ sshpk@^1.7.0:
safer-buffer "^2.0.2"
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:
version "1.0.4"
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:
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:
version "3.0.1"
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"
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:
version "0.6.0"
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"
integrity sha512-lTmS6AlAlMHOvPQemVwo3CezxBp0sNB95KNPkqp3Nxd5VFEnuG1ByM0zlRWos0zjO3ZWtkvhal0COgiV1xIA4A==
vscode-nls@^4.0.0:
vscode-nls@^4.0.0, vscode-nls@^4.1.1:
version "4.1.2"
resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.1.2.tgz#ca8bf8bb82a0987b32801f9fddfdd2fb9fd3c167"
integrity sha512-7bOHxPsfyuCqmP+hZXscLhiHwe7CSuFE4hyhbs22xPIhQ4jv99FcR4eBzfYYVLP356HNFpdvz63FFb/xw6T4Iw==

View File

@@ -110,12 +110,11 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
this.currentBook = existingBook;
} else {
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);
bookViewer.reveal(this.currentBook.bookItems[0], { expand: vscode.TreeItemCollapsibleState.Expanded, focus: true, select: true });
}
if (showPreview) {
this._bookViewer.reveal(this.currentBook.bookItems[0], { expand: vscode.TreeItemCollapsibleState.Expanded, focus: true, select: true });
await this.showPreviewFile(urlToOpen);
}
@@ -223,9 +222,7 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
openDocumentListenerUnsubscriber.dispose();
});
}
let doc = await vscode.workspace.openTextDocument(resource);
vscode.window.showTextDocument(doc);
azdata.nb.showNotebookDocument(vscode.Uri.file(resource));
}
} catch (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 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 enum BuiltInCommands {

View File

@@ -294,3 +294,7 @@ function decorate(decorator: (fn: Function, key: string) => Function): Function
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 azdata from 'azdata';
import { promises as fs } from 'fs';
import * as utils from '../common/utils';
import * as utils from '../../common/utils';
import { JupyterServerInstallation } from '../jupyter/jupyterServerInstallation';
import { ApiWrapper } from '../common/apiWrapper';
import { Deferred } from '../common/promise';
import { PythonPathLookup, PythonPathInfo } from './pythonPathLookup';
import { JupyterServerInstallation } from '../../jupyter/jupyterServerInstallation';
import { ApiWrapper } from '../../common/apiWrapper';
import { Deferred } from '../../common/promise';
import { PythonPathLookup, PythonPathInfo } from '../pythonPathLookup';
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
*/
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 { NotebookCompletionItemProvider } from '../intellisense/completionItemProvider';
import { JupyterNotebookProvider } from './jupyterNotebookProvider';
import { ConfigurePythonDialog } from '../dialog/configurePythonDialog';
import { ConfigurePythonWizard } from '../dialog/configurePython/configurePythonWizard';
import CodeAdapter from '../prompts/adapter';
import { ManagePackagesDialog } from '../dialog/managePackages/managePackagesDialog';
import { IPackageManageProvider } from '../types';
@@ -30,6 +30,7 @@ import { LocalPipPackageManageProvider } from './localPipPackageManageProvider';
import { LocalCondaPackageManageProvider } from './localCondaPackageManageProvider';
import { ManagePackagesDialogModel, ManagePackageDialogOptions } from '../dialog/managePackages/managePackagesDialogModel';
import { PiPyClient } from './pipyClient';
import { ConfigurePythonDialog } from '../dialog/configurePython/configurePythonDialog';
let untitledCounter = 0;
@@ -250,10 +251,21 @@ export class JupyterController implements vscode.Disposable {
}
public doConfigurePython(jupyterInstaller: JupyterServerInstallation): void {
let pythonDialog = new ConfigurePythonDialog(this.apiWrapper, jupyterInstaller);
pythonDialog.showDialog().catch((err: any) => {
this.apiWrapper.showErrorMessage(utils.getErrorMessage(err));
});
let enablePreviewFeatures = this.apiWrapper.getConfiguration('workbench').get('enablePreviewFeatures');
if (enablePreviewFeatures) {
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() {

View File

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

View File

@@ -17,9 +17,10 @@ import * as constants from '../common/constants';
import * as utils from '../common/utils';
import { OutputChannel, ConfigurationTarget, window } from 'vscode';
import { Deferred } from '../common/promise';
import { ConfigurePythonDialog } from '../dialog/configurePythonDialog';
import { ConfigurePythonWizard } from '../dialog/configurePython/configurePythonWizard';
import { IPrompter, IQuestion, QuestionTypes } from '../prompts/question';
import CodeAdapter from '../prompts/adapter';
import { ConfigurePythonDialog } from '../dialog/configurePython/configurePythonDialog';
const localize = nls.loadMessageBundle();
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 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 {
installCondaPackages(packages: PythonPkgDetails[], useMinVersion: boolean): 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[]>;
getInstalledCondaPackages(): Promise<PythonPkgDetails[]>;
uninstallCondaPackages(packages: PythonPkgDetails[]): Promise<void>;
@@ -66,7 +72,6 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
private _pythonInstallationPath: string;
private _pythonExecutable: string;
private _pythonPackageDir: string;
private _usingExistingPython: boolean;
private _usingConda: boolean;
@@ -104,6 +109,8 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
private readonly _expectedCondaPipPackages = this._commonPipPackages;
private readonly _expectedCondaPackages: PythonPkgDetails[];
private _kernelSetupCache: Map<string, boolean>;
constructor(extensionPath: string, outputChannel: OutputChannel, apiWrapper: ApiWrapper, pythonInstallationPath?: string) {
this.extensionPath = extensionPath;
this.outputChannel = outputChannel;
@@ -120,9 +127,11 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
} else {
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) {
window.showInformationMessage(msgInstallPkgStart);
@@ -132,12 +141,7 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
try {
await this.installPythonPackage(backgroundOperation, this._usingExistingPython, this._pythonInstallationPath, this.outputChannel);
if (this._usingExistingPython) {
await this.upgradePythonPackages(false, forceInstall);
} else {
await this.installOfflinePipDependencies();
}
await this.upgradePythonPackages(false, forceInstall, specificPackages);
} catch (err) {
this.outputChannel.appendLine(msgDependenciesInstallationFailed(utils.getErrorMessage(err)));
throw err;
@@ -282,19 +286,13 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
? this._pythonInstallationPath
: 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.
let pythonBinPathSuffix = process.platform === constants.winPlatform ? '' : 'bin';
this._pythonExecutable = JupyterServerInstallation.getPythonExePath(this._pythonInstallationPath, this._usingExistingPython);
this.pythonBinPath = path.join(pythonSourcePath, pythonBinPathSuffix);
this._usingConda = this.checkCondaExists();
this._usingConda = this.isCondaInstalled();
// Store paths to python libraries required to run jupyter.
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.
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 = {
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.
* 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;
if (installSettings) {
isPythonRunning = await this.isPythonRunning(installSettings.installPath, installSettings.existingPython);
@@ -404,7 +397,7 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
description: msgTaskName,
isCancelable: false,
operation: op => {
this.installDependencies(op, forceInstall)
this.installDependencies(op, forceInstall, installSettings?.specificPackages)
.then(async () => {
await updateConfig();
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.
*/
public async promptForPythonInstall(): Promise<void> {
public async promptForPythonInstall(kernelDisplayName: string): Promise<void> {
if (!JupyterServerInstallation.isPythonInstalled(this.apiWrapper)) {
let pythonDialog = new ConfigurePythonDialog(this.apiWrapper, this);
return pythonDialog.showDialog(true);
let enablePreviewFeatures = this.apiWrapper.getConfiguration('workbench').get('enablePreviewFeatures');
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.
*/
public async promptForPackageUpgrade(): Promise<void> {
public async promptForPackageUpgrade(kernelName: string): Promise<void> {
if (this._installInProgress) {
this.apiWrapper.showInfoMessage(msgWaitingForInstall);
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._installCompletion = new Deferred<void>();
this.upgradePythonPackages(true, false)
this.upgradePythonPackages(true, false, requiredPackages)
.then(() => {
this._installCompletion.resolve();
this._installInProgress = false;
this._kernelSetupCache.set(kernelName, true);
})
.catch(err => {
let errorMsg = msgDependenciesInstallationFailed(utils.getErrorMessage(err));
@@ -463,10 +473,14 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
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 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;
expectedPipPackages = this._expectedCondaPipPackages;
} else {
@@ -515,9 +529,12 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
if (promptForUpgrade) {
doUpgrade = await this._prompter.promptSingle<boolean>(<IQuestion>{
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
});
if (!doUpgrade) {
throw new Error(localize('configurePython.packageInstallDeclined', "Package installation was declined."));
}
} else {
doUpgrade = true;
}
@@ -568,9 +585,17 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
}
}
public async getInstalledPipPackages(): Promise<PythonPkgDetails[]> {
public async getInstalledPipPackages(pythonExePath?: string): Promise<PythonPkgDetails[]> {
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 packagesResult: PythonPkgDetails[] = [];
if (packagesInfo) {
@@ -603,6 +628,10 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
public async getInstalledCondaPackages(): Promise<PythonPkgDetails[]> {
try {
if (!this.isCondaInstalled()) {
return [];
}
let condaExe = this.getCondaExePath();
let cmd = `"${condaExe}" list --json`;
let packagesInfo = await this.executeBufferedCommand(cmd);
@@ -642,32 +671,6 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
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> {
await utils.executeStreamedCommand(command, { env: this.execOptions.env }, this.outputChannel);
}
@@ -696,7 +699,7 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
return this._usingConda;
}
private checkCondaExists(): boolean {
private isCondaInstalled(): boolean {
if (!this._usingExistingPython) {
return false;
}
@@ -805,6 +808,55 @@ export class JupyterServerInstallation implements IJupyterServerInstallation {
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 {

View File

@@ -56,16 +56,15 @@ export class LocalJupyterServerManager implements nb.ServerManager, vscode.Dispo
return this.options && this.options.jupyterInstallation;
}
public async startServer(): Promise<void> {
public async startServer(kernelSpec: nb.IKernelSpec): Promise<void> {
try {
if (!this._jupyterServer) {
this._jupyterServer = await this.doStartServer();
this._jupyterServer = await this.doStartServer(kernelSpec);
this.options.extensionContext.subscriptions.push(this);
let partialSettings = LocalJupyterServerManager.getLocalConnectionSettings(this._jupyterServer.uri);
this._serverSettings = partialSettings;
this._onServerStarted.fire();
}
} catch (error) {
// this is caught and notified up the stack, no longer showing a message here
throw error;
@@ -107,10 +106,10 @@ export class LocalJupyterServerManager implements nb.ServerManager, vscode.Dispo
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;
await installation.promptForPythonInstall();
await installation.promptForPackageUpgrade();
await installation.promptForPythonInstall(kernelSpec.display_name);
await installation.promptForPackageUpgrade(kernelSpec.display_name);
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

View File

@@ -15,6 +15,7 @@ const localize = nls.loadMessageBundle();
import { JupyterKernel } from './jupyterKernel';
import { Deferred } from '../common/promise';
import { JupyterServerInstallation } from './jupyterServerInstallation';
const configBase = {
'kernel_python_credentials': {
@@ -66,6 +67,7 @@ export class JupyterSessionManager implements nb.SessionManager {
private _isReady: boolean;
private _sessionManager: Session.IManager;
private static _sessions: JupyterSession[] = [];
private _installation: JupyterServerInstallation;
constructor(private _pythonEnvVarPath?: string) {
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 {
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")));
}
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;
let index = JupyterSessionManager._sessions.findIndex(session => session.path === options.path);
if (index > -1) {
@@ -173,7 +181,7 @@ export class JupyterSession implements nb.ISession {
private _kernel: nb.IKernel;
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 => {
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
@@ -221,7 +229,20 @@ export class JupyterSession implements nb.ISession {
return this._messagesComplete.promise;
}
public set installation(installation: JupyterServerInstallation) {
this._installation = installation;
}
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
// null out our kernel and let the changeKernel call handle this
this._kernel = undefined;
@@ -348,11 +369,6 @@ export class JupyterSession implements nb.ISession {
}
for (let i = 0; i < Object.keys(process.env).length; 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) {
allCode += `%set_env ${key}=${this._pythonEnvVarPath}${EOL}`;
} else {

View File

@@ -3,6 +3,7 @@
* 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 { IServerInstance } from '../jupyter/common';
@@ -255,3 +256,160 @@ export class FutureStub implements Kernel.IFuture {
}
}
//#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() {
this.subscriptions = [];
}
environmentVariableCollection: vscode.EnvironmentVariableCollection;
}
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 'mocha';
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 * as constants from '../../common/constants';
import { LocalPipPackageManageProvider } from '../../jupyter/localPipPackageManageProvider';
@@ -188,7 +188,7 @@ describe('Manage Package Providers', () => {
serverInstallation: {
installCondaPackages: (packages: PythonPkgDetails[], useMinVersion: boolean) => { 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([]); },
installPipPackages: (packages: PythonPkgDetails[], useMinVersion: boolean) => { return Promise.resolve(); },
uninstallPipPackages: (packages: PythonPkgDetails[]) => { return Promise.resolve(); },

View File

@@ -139,7 +139,8 @@ describe('Manage Package Dialog', () => {
removeItem: () => true,
insertItem: () => { },
items: components,
setLayout: () => { }
setLayout: () => { },
setItemLayout: () => { }
};
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'}]);
});
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> {
let testContext1 = createContext();
testContext1.provider.providerId = 'providerId1';

View File

@@ -6,6 +6,7 @@
import * as should from 'should';
import * as TypeMoq from 'typemoq';
import * as vscode from 'vscode';
import * as azdata from 'azdata';
import 'mocha';
import { JupyterServerInstanceStub } from '../common';
@@ -18,6 +19,10 @@ import { IServerInstance } from '../../jupyter/common';
import { MockExtensionContext } from '../common/stubs';
describe('Local Jupyter Server Manager', function (): void {
const pythonKernelSpec: azdata.nb.IKernelSpec = {
name: 'python3',
display_name: 'Python 3'
};
let expectedPath = 'my/notebook.ipynb';
let serverManager: LocalJupyterServerManager;
let deferredInstall: Deferred<void>;
@@ -33,7 +38,7 @@ describe('Local Jupyter Server Manager', function (): void {
deferredInstall = new Deferred<void>();
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) };
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> {
let error = '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> {
@@ -65,7 +70,7 @@ describe('Local Jupyter Server Manager', function (): void {
// When I start the server
let notified = false;
serverManager.onServerStarted(() => notified = true);
await serverManager.startServer();
await serverManager.startServer(pythonKernelSpec);
// Then I expect the port to be included in settings
should(serverManager.serverSettings.baseUrl.indexOf('1234') > -1).be.true();
@@ -89,7 +94,7 @@ describe('Local Jupyter Server Manager', function (): void {
deferredInstall.resolve();
// When I start and then the server
await serverManager.startServer();
await serverManager.startServer(pythonKernelSpec);
await serverManager.stopServer();
// 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();
// When I start and then dispose the extension
await serverManager.startServer();
await serverManager.startServer(pythonKernelSpec);
should(mockExtensionContext.subscriptions).have.length(1);
mockExtensionContext.subscriptions[0].dispose();

View File

@@ -108,7 +108,7 @@ describe('Jupyter Session', function (): void {
beforeEach(() => {
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 {

View File

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

View File

@@ -26,6 +26,17 @@
"Microsoft.mssql"
],
"contributes": {
"configuration": [
{
"title": "%sqlDatabaseProjects.Settings%",
"properties": {
"sqlDatabaseProjects.netCoreSDKLocation": {
"type": "string",
"description": "%sqlDatabaseProjects.netCoreInstallLocation%"
}
}
}
],
"commands": [
{
"command": "sqlDatabaseProjects.new",
@@ -71,6 +82,31 @@
"command": "sqlDatabaseProjects.newFolder",
"title": "%sqlDatabaseProjects.newFolder%",
"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": {
@@ -108,36 +144,87 @@
{
"command": "sqlDatabaseProjects.newFolder",
"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": [
{
"command": "sqlDatabaseProjects.close",
"when": "view == sqlDatabaseProjectsView"
"command": "sqlDatabaseProjects.build",
"when": "view == sqlDatabaseProjectsView",
"group": "1_dbProjectsFirst@1"
},
{
"command": "sqlDatabaseProjects.newScript",
"when": "view == sqlDatabaseProjectsView"
"command": "sqlDatabaseProjects.deploy",
"when": "view == sqlDatabaseProjectsView",
"group": "1_dbProjectsFirst@2"
},
{
"command": "sqlDatabaseProjects.newTable",
"when": "view == sqlDatabaseProjectsView"
"command": "sqlDatabaseProjects.schemaCompare",
"when": "view == sqlDatabaseProjectsView",
"group": "1_dbProjectsFirst@3"
},
{
"command": "sqlDatabaseProjects.newView",
"when": "view == sqlDatabaseProjectsView"
},
{
"command": "sqlDatabaseProjects.newStoredProcedure",
"when": "view == sqlDatabaseProjectsView"
"command": "sqlDatabaseProjects.import",
"when": "view == sqlDatabaseProjectsView",
"group": "1_dbProjectsFirst@4"
},
{
"command": "sqlDatabaseProjects.newItem",
"when": "view == sqlDatabaseProjectsView"
"when": "view == sqlDatabaseProjectsView",
"group": "2_dbProjects_newMain@1"
},
{
"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.open": "Open 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.newTable": "Add Table",
"sqlDatabaseProjects.newView": "Add View",
"sqlDatabaseProjects.newStoredProcedure": "Add Stored Procedure",
"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.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import * as templates from '../templates/templates';
import * as constants from '../common/constants';
import * as path from 'path';
import { Uri, Disposable, ExtensionContext, WorkspaceFolder } from 'vscode';
import { ApiWrapper } from '../common/apiWrapper';
import { SqlDatabaseProjectTreeViewProvider } from './databaseProjectTreeViewProvider';
import { getErrorMessage } from '../common/utils';
import { ProjectsController } from './projectController';
import { BaseProjectTreeItem } from '../models/tree/baseTreeItem';
import { NetCoreTool } from '../tools/netcoreTool';
import { Project } from '../models/project';
const SQL_DATABASE_PROJECTS_VIEW_ID = 'sqlDatabaseProjectsView';
/**
* The main controller class that initializes the extension
*/
export default class MainController implements vscode.Disposable {
protected _context: vscode.ExtensionContext;
export default class MainController implements Disposable {
protected dbProjectTreeViewProvider: SqlDatabaseProjectTreeViewProvider = new SqlDatabaseProjectTreeViewProvider();
protected projectsController: ProjectsController;
protected netcoreTool: NetCoreTool;
public constructor(context: vscode.ExtensionContext) {
this._context = context;
this.projectsController = new ProjectsController(this.dbProjectTreeViewProvider);
public constructor(private context: ExtensionContext, private apiWrapper: ApiWrapper) {
this.projectsController = new ProjectsController(apiWrapper, this.dbProjectTreeViewProvider);
this.netcoreTool = new NetCoreTool();
}
public get extensionContext(): vscode.ExtensionContext {
return this._context;
public get extensionContext(): ExtensionContext {
return this.context;
}
public deactivate(): void {
@@ -41,21 +44,29 @@ export default class MainController implements vscode.Disposable {
private async initializeDatabaseProjects(): Promise<void> {
// init commands
vscode.commands.registerCommand('sqlDatabaseProjects.new', async () => { await this.createNewProject(); });
vscode.commands.registerCommand('sqlDatabaseProjects.open', async () => { await this.openProjectFromFile(); });
vscode.commands.registerCommand('sqlDatabaseProjects.close', (node: BaseProjectTreeItem) => { this.projectsController.closeProject(node); });
this.apiWrapper.registerCommand('sqlDatabaseProjects.new', async () => { await this.createNewProject(); });
this.apiWrapper.registerCommand('sqlDatabaseProjects.open', async () => { await this.openProjectFromFile(); });
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); });
vscode.commands.registerCommand('sqlDatabaseProjects.newTable', async (node: BaseProjectTreeItem) => { await this.projectsController.addItemPrompt(node, templates.table); });
vscode.commands.registerCommand('sqlDatabaseProjects.newView', async (node: BaseProjectTreeItem) => { await this.projectsController.addItemPrompt(node, templates.view); });
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); });
vscode.commands.registerCommand('sqlDatabaseProjects.newFolder', async (node: BaseProjectTreeItem) => { await this.projectsController.addFolderPrompt(node); });
this.apiWrapper.registerCommand('sqlDatabaseProjects.build', async (node: BaseProjectTreeItem) => { await this.projectsController.build(node); });
this.apiWrapper.registerCommand('sqlDatabaseProjects.deploy', async (node: BaseProjectTreeItem) => { await this.projectsController.deploy(node); });
this.apiWrapper.registerCommand('sqlDatabaseProjects.import', async (node: BaseProjectTreeItem) => { await this.projectsController.import(node); });
this.apiWrapper.registerCommand('sqlDatabaseProjects.newScript', async (node: BaseProjectTreeItem) => { await this.projectsController.addItemPromptFromNode(node, templates.script); });
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
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'];
let files: vscode.Uri[] | undefined = await vscode.window.showOpenDialog({ filters: filter });
let files: Uri[] | undefined = await this.apiWrapper.showOpenDialog({ filters: filter });
if (files) {
for (const file of files) {
@@ -77,48 +88,50 @@ export default class MainController implements vscode.Disposable {
}
}
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
*/
public async createNewProject(): Promise<void> {
public async createNewProject(): Promise<Project | undefined> {
try {
let newProjName = await vscode.window.showInputBox({
let newProjName = await this.apiWrapper.showInputBox({
prompt: constants.newDatabaseProjectName,
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...
});
newProjName = newProjName?.trim();
if (!newProjName) {
// TODO: is this case considered an intentional cancellation (shouldn't warn) or an error case (should warn)?
vscode.window.showErrorMessage(constants.projectNameRequired);
return;
this.apiWrapper.showErrorMessage(constants.projectNameRequired);
return undefined;
}
let selectionResult = await vscode.window.showOpenDialog({
let selectionResult = await this.apiWrapper.showOpenDialog({
canSelectFiles: false,
canSelectFolders: true,
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) {
vscode.window.showErrorMessage(constants.projectLocationRequired);
return;
this.apiWrapper.showErrorMessage(constants.projectLocationRequired);
return undefined;
}
// TODO: what if the selected folder is outside the workspace?
const newProjFolderUri = (selectionResult as vscode.Uri[])[0];
console.log(newProjFolderUri.fsPath);
const newProjFilePath = await this.projectsController.createNewProject(newProjName as string, newProjFolderUri as vscode.Uri);
await this.projectsController.openProject(vscode.Uri.file(newProjFilePath));
const newProjFolderUri = (selectionResult as Uri[])[0];
const newProjFilePath = await this.projectsController.createNewProject(newProjName as string, newProjFolderUri as Uri);
return this.projectsController.openProject(Uri.file(newProjFilePath));
}
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.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import * as path from 'path';
import * as constants from '../common/constants';
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 templates from '../templates/templates';
import { Uri, QuickPickItem } from 'vscode';
import { ApiWrapper } from '../common/apiWrapper';
import { Project } from '../models/project';
import { SqlDatabaseProjectTreeViewProvider } from './databaseProjectTreeViewProvider';
import { promises as fs } from 'fs';
@@ -26,7 +27,7 @@ export class ProjectsController {
projects: Project[] = [];
constructor(projTreeViewProvider: SqlDatabaseProjectTreeViewProvider) {
constructor(private apiWrapper: ApiWrapper, projTreeViewProvider: SqlDatabaseProjectTreeViewProvider) {
this.projectTreeViewProvider = projTreeViewProvider;
}
@@ -35,29 +36,29 @@ export class ProjectsController {
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) {
if (proj.projectFilePath === projectFile.fsPath) {
vscode.window.showInformationMessage(constants.projectAlreadyOpened(projectFile.fsPath));
this.apiWrapper.showInformationMessage(constants.projectAlreadyOpened(projectFile.fsPath));
return proj;
}
}
// Read project file
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 {
// 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);
}
catch (err) {
if (err instanceof dataSources.NoDataSourcesFileError) {
// TODO: prompt to create new datasources.json; for now, swallow
console.log(`No ${constants.dataSourcesFileName} file found.`);
}
else {
throw err;
@@ -69,7 +70,7 @@ export class ProjectsController {
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)) {
throw new Error(`Specified GUID is invalid: '${projectGuid}'`);
}
@@ -112,6 +113,21 @@ export class ProjectsController {
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) {
const project = this.getProjectContextFromTreeNode(treeNode);
const newFolderName = await this.promptForNewObjectName(new templates.ProjectScriptType(templates.folder, constants.folderFriendlyName, ''), project);
@@ -120,26 +136,28 @@ export class ProjectsController {
return; // user cancelled
}
const relativeFolderPath = this.prependContextPath(treeNode, newFolderName);
const relativeFolderPath = path.join(this.getRelativePath(treeNode), newFolderName);
await project.addFolderItem(relativeFolderPath);
this.refreshProjectsTree();
}
public async addItemPrompt(treeNode: BaseProjectTreeItem, itemTypeName?: string) {
const project = this.getProjectContextFromTreeNode(treeNode);
public async addItemPromptFromNode(treeNode: BaseProjectTreeItem, itemTypeName?: string) {
await this.addItemPrompt(this.getProjectContextFromTreeNode(treeNode), this.getRelativePath(treeNode), itemTypeName);
}
public async addItemPrompt(project: Project, relativePath: string, itemTypeName?: string) {
if (!itemTypeName) {
let itemFriendlyNames: string[] = [];
const items: QuickPickItem[] = [];
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
});
}))?.label;
if (!itemTypeName) {
return; // user cancelled
@@ -147,7 +165,9 @@ export class ProjectsController {
}
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) {
return; // user cancelled
@@ -156,11 +176,11 @@ export class ProjectsController {
// TODO: file already exists?
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);
vscode.commands.executeCommand('vscode.open', newEntry.fsUri);
this.apiWrapper.executeCommand('vscode.open', newEntry.fsUri);
this.refreshProjectsTree();
}
@@ -193,7 +213,7 @@ export class ProjectsController {
return (treeNode.root as ProjectRootTreeItem).project;
}
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
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),
value: suggestedName,
});
@@ -209,13 +229,8 @@ export class ProjectsController {
return itemObjectName;
}
private prependContextPath(treeNode: BaseProjectTreeItem, objectName: string): string {
if (treeNode instanceof FolderNode) {
return path.join(utils.trimUri(treeNode.root.uri, treeNode.uri), objectName);
}
else {
return objectName;
}
private getRelativePath(treeNode: BaseProjectTreeItem): string {
return treeNode instanceof FolderNode ? utils.trimUri(treeNode.root.uri, treeNode.uri) : '';
}
//#endregion

View File

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

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