mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-17 11:01:37 -05:00
Compare commits
87 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2acd37a1b1 | ||
|
|
85786b48c3 | ||
|
|
acd6257bae | ||
|
|
f007d707b6 | ||
|
|
b118b4bc7a | ||
|
|
f3edece70b | ||
|
|
4de376c357 | ||
|
|
0753b63ad0 | ||
|
|
0523190fbb | ||
|
|
de994972db | ||
|
|
3b06473c49 | ||
|
|
bb75143282 | ||
|
|
3857f11dc9 | ||
|
|
0918d93a18 | ||
|
|
25d96d041e | ||
|
|
0dc88501cf | ||
|
|
bac7eccbaf | ||
|
|
585d609ebb | ||
|
|
168385b6f1 | ||
|
|
eb4612100d | ||
|
|
d89ce8f9ec | ||
|
|
d84dd31491 | ||
|
|
b6632547a2 | ||
|
|
79669f073c | ||
|
|
e078e3bc48 | ||
|
|
72d035be98 | ||
|
|
f6e7b56946 | ||
|
|
2893659983 | ||
|
|
f220f6486a | ||
|
|
f94a9d0d58 | ||
|
|
9bcd7cdd80 | ||
|
|
5e1978e29f | ||
|
|
e4614582cd | ||
|
|
0fbc3d1cb6 | ||
|
|
3a70bea70d | ||
|
|
ea84e60fa0 | ||
|
|
10ac5fdfab | ||
|
|
1c621da4c7 | ||
|
|
6e5fc9c495 | ||
|
|
e3daec38c6 | ||
|
|
11636f8fc3 | ||
|
|
8c08d5117b | ||
|
|
fa62ec1f34 | ||
|
|
dac1970c43 | ||
|
|
e27a57715c | ||
|
|
e6ca724571 | ||
|
|
57d2ceec9d | ||
|
|
d87855cf1f | ||
|
|
1656a9c285 | ||
|
|
89a845d994 | ||
|
|
80901c9a7b | ||
|
|
0ace033a6f | ||
|
|
df5df38a55 | ||
|
|
4199cec393 | ||
|
|
9b296c9f42 | ||
|
|
dbb40d820c | ||
|
|
2ca6e7ae64 | ||
|
|
d7d13c7218 | ||
|
|
485e185270 | ||
|
|
8420d9f04e | ||
|
|
9a7810cbee | ||
|
|
0fd3b25ccd | ||
|
|
921e546fd7 | ||
|
|
5fe72d318b | ||
|
|
5105694fb3 | ||
|
|
53cb97af9b | ||
|
|
a7c4a259ad | ||
|
|
5498c1c3a9 | ||
|
|
3e5b7fd3e6 | ||
|
|
e3873828e0 | ||
|
|
db57eb9581 | ||
|
|
8d46aef40c | ||
|
|
cb2a02d82c | ||
|
|
52abcd68af | ||
|
|
5cb9b36329 | ||
|
|
13d2ce7d5d | ||
|
|
64c375a12d | ||
|
|
8449888db8 | ||
|
|
cebbd04d10 | ||
|
|
d7a425239b | ||
|
|
b4deca3b4b | ||
|
|
92e0b2e130 | ||
|
|
28230b67d4 | ||
|
|
af36d79c53 | ||
|
|
0999060827 | ||
|
|
0e6117f044 | ||
|
|
fdc21bfb9e |
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -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'
|
||||
|
||||
1
.github/workflows/on-issue-open.yml
vendored
1
.github/workflows/on-issue-open.yml
vendored
@@ -19,5 +19,6 @@ jobs:
|
||||
- name: Run CopyCat
|
||||
uses: ./actions/build/actions/copycat
|
||||
with:
|
||||
token: ${{secrets.TRIAGE_PAT}}
|
||||
owner: anthonydresser
|
||||
repo: testissues
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"useTabs": true,
|
||||
"printWidth": 120,
|
||||
"semi": true,
|
||||
"singleQuote": true
|
||||
}
|
||||
19
.vscode/launch.json
vendored
19
.vscode/launch.json
vendored
@@ -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",
|
||||
|
||||
19
.vscode/searches/es6.code-search
vendored
19
.vscode/searches/es6.code-search
vendored
@@ -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
|
||||
|
||||
2
.yarnrc
2
.yarnrc
@@ -1,3 +1,3 @@
|
||||
disturl "https://atom.io/download/electron"
|
||||
target "7.2.2"
|
||||
target "7.2.4"
|
||||
runtime "electron"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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: |
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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: '.' })
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
|
||||
@@ -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' };
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>[] = [];
|
||||
|
||||
27
extensions/dacpac/src/test/mainController.test.ts
Normal file
27
extensions/dacpac/src/test/mainController.test.ts
Normal 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);
|
||||
});
|
||||
|
||||
});
|
||||
36
extensions/dacpac/src/test/testContext.ts
Normal file
36
extensions/dacpac/src/test/testContext.ts
Normal 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
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -409,6 +409,11 @@
|
||||
"command": "git.timeline.copyCommitMessage",
|
||||
"title": "%command.timelineCopyCommitMessage%",
|
||||
"category": "Git"
|
||||
},
|
||||
{
|
||||
"command": "git.rebaseAbort",
|
||||
"title": "%command.rebaseAbort%",
|
||||
"category": "Git"
|
||||
}
|
||||
],
|
||||
"keybindings": [
|
||||
|
||||
@@ -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...",
|
||||
|
||||
@@ -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[]) => {
|
||||
|
||||
@@ -1333,6 +1333,10 @@ export class Repository {
|
||||
}
|
||||
}
|
||||
|
||||
async rebaseAbort(): Promise<void> {
|
||||
await this.run(['rebase', '--abort']);
|
||||
}
|
||||
|
||||
async rebaseContinue(): Promise<void> {
|
||||
const args = ['rebase', '--continue'];
|
||||
|
||||
|
||||
@@ -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);
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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"));
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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."
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -32,4 +32,9 @@ export interface PackageConfigModel {
|
||||
* Package file name if package has download url
|
||||
*/
|
||||
fileName?: string;
|
||||
|
||||
/**
|
||||
* Package platform (Windows, Mac, Linux)
|
||||
*/
|
||||
platform?: string;
|
||||
}
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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)`,
|
||||
|
||||
@@ -90,7 +90,8 @@ function createContext(): TestContext {
|
||||
storagePath: '',
|
||||
globalStoragePath: '',
|
||||
logPath: '',
|
||||
extensionUri: vscode.Uri.parse('')
|
||||
extensionUri: vscode.Uri.parse(''),
|
||||
environmentVariableCollection: { } as any
|
||||
},
|
||||
outputChannel: {
|
||||
name: '',
|
||||
|
||||
@@ -51,7 +51,8 @@ export function createViewContext(): ViewTestContext {
|
||||
removeItem: () => true,
|
||||
insertItem: () => { },
|
||||
items: [],
|
||||
setLayout: () => { }
|
||||
setLayout: () => { },
|
||||
setItemLayout: () => { }
|
||||
};
|
||||
let form: azdata.FormContainer = Object.assign({}, componentBase, container, {
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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');
|
||||
|
||||
46
extensions/mssql/src/mssql.d.ts
vendored
46
extensions/mssql/src/mssql.d.ts
vendored
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
67
extensions/mssql/src/sqlAssessment/sqlAssessmentService.ts
Normal file
67
extensions/mssql/src/sqlAssessment/sqlAssessmentService.ts
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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()
|
||||
};
|
||||
|
||||
@@ -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==
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
33
extensions/notebook/src/dialog/configurePython/basePage.ts
Normal file
33
extensions/notebook/src/dialog/configurePython/basePage.ts
Normal 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>;
|
||||
}
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -22,6 +22,7 @@ export class MockExtensionContext implements vscode.ExtensionContext {
|
||||
constructor() {
|
||||
this.subscriptions = [];
|
||||
}
|
||||
environmentVariableCollection: vscode.EnvironmentVariableCollection;
|
||||
}
|
||||
|
||||
export class MockOutputChannel implements vscode.OutputChannel {
|
||||
|
||||
133
extensions/notebook/src/test/configurePython.test.ts
Normal file
133
extensions/notebook/src/test/configurePython.test.ts
Normal 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));
|
||||
});
|
||||
});
|
||||
@@ -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(); },
|
||||
|
||||
@@ -139,7 +139,8 @@ describe('Manage Package Dialog', () => {
|
||||
removeItem: () => true,
|
||||
insertItem: () => { },
|
||||
items: components,
|
||||
setLayout: () => { }
|
||||
setLayout: () => { },
|
||||
setItemLayout: () => { }
|
||||
};
|
||||
let form: azdata.FormContainer = Object.assign({}, componentBase, container, {
|
||||
});
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -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."
|
||||
}
|
||||
|
||||
149
extensions/sql-database-projects/src/common/apiWrapper.ts
Normal file
149
extensions/sql-database-projects/src/common/apiWrapper.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user