Compare commits

..

57 Commits
1.3.1 ... 1.3.3

Author SHA1 Message Date
Karl Burtram
15ae55136f Bump event-stream to 3.3.4 2018-11-26 16:34:03 -08:00
Karl Burtram
b18abd954f Update Azure Data Studio to 1.3.3 2018-11-26 16:32:34 -08:00
Allen Cook
b45f79a1f8 Add Routine_Type to CreateStoredProc fixes #3257 (#3286) 2018-11-26 15:26:12 -08:00
Alan Ren
01a03b4c84 revert the taskbar icon size change (#3306) 2018-11-26 15:14:36 -08:00
Anthony Dresser
e48328af34 Handle qp clear input correctly (#3258)
* handle clearing xml in qp view

* formatting
2018-11-26 14:49:06 -08:00
Anthony Dresser
8925d44807 Result Streaming (#3124)
* handle releasing data when the grid is unrendered

* update sqlops

* add complete to sqlops

* update protocol

* update protocol

* formatting

* update sqlops.d.ts

* stash

* better handling of results streaming

* formatting

* improvments to result streaming

* bump slickgrid and address a performance bottleneck

* remove unnecessary code

* formatting

* update locks

* optimize large values in the grid

* formatting

* formatting

* update yarn

* bump packages

* yarn

* bump

* yarn lock

* locking

* yarn
2018-11-26 14:30:35 -08:00
Karl Burtram
213283510f Bump SQL Tools to 1.5.0-alpha.58 for results streaming 2018-11-26 13:54:56 -08:00
Alan Ren
9f8190dc28 make taskbar more readable (#3290) 2018-11-20 15:09:24 -08:00
Alan Ren
c32d4ee2f7 fix for 3245 (#3281) 2018-11-19 14:47:41 -08:00
Aditya Bist
2c867a4b2c unlocalized string (#3259) 2018-11-17 08:37:42 -08:00
Alan Ren
da5194bdcb fix for 3262 (#3263) 2018-11-16 21:19:32 -08:00
Chris LaFreniere
bbb27aed10 Notebook: Re-Enable Attach to Dropdown Functionality (#3250)
* first attach to working

* Transfer changes from sqlopsstudioextensions PR 448

* Transfer changes from sqlopsstudioextensions PR 447

* Transfer changes from sqlopsstudioextensions PR 456

* Transfer changes from sqlopsstudioextensions PR 465

* Transfer changes from sqlopsstudioextensions PR 463

* Transfer changes from sqlopsstudioextensions PR 482

* Transfer changes from sqlopsstudioextensions PR 485

* Session and Kernel implementation except executeRequest

* Attach to port compiling

* Further tweaks to attach to dropdown, re-enable opening connection dialog

* Revert "Merge remote-tracking branch 'origin/Notebook/sessionExtension' into feature/workingAttachTo"

This reverts commit 94703db87c85416c4ae36762afc1094d6e71166a, reversing
changes made to e4dc25331036d259e9c762cfe8741f957bb5c590.

* Fix code formatting

* Fix for new Add new connection issue
2018-11-16 20:47:58 -08:00
Alan Ren
c02fbaeae7 min height for event detail view when expanding (#3255) 2018-11-16 15:07:56 -08:00
Karl Burtram
847218da73 Disconnect Object Explorer node when session is disconnected from SQL Tools Service (#3249)
* WIP

* WIP

* Send disconnect event to OE

* Bump dataprotocol to 0.2.9

* Cleanupps

* Address a couple feedback
2018-11-16 13:08:20 -08:00
Kevin Cunnane
90dc788893 Implement Session support through the extension host (#3228)
Full plumb through of Session support. Also fixed some test issues

- Load session and get necessary information in kernels list
- Run Cell button now works as expected
- Added a ToggleAction base class which can be used for anything that switches icons. I'd still prefer to have this be dynamic and as clean as the extension classes
- Fixed account test unhandled promise rejections (caused by incorrect / invalid tests) that made it hard to see all the test run output.
2018-11-16 10:35:03 -08:00
Karl Burtram
f3525cc555 Bump Azure Data Studio to 1.3.2 2018-11-15 17:24:12 -08:00
abist
198f243181 scoped the agent action bar class style 2018-11-15 16:11:20 -08:00
Alan Ren
8e049f4af5 align the radio button with text (#3241) 2018-11-15 14:01:32 -08:00
Danny McCormick
ff465a59b6 Remove travis and appveyor (#3234)
* Delete .travis.yml

* Delete appveyor.yml
2018-11-15 11:52:36 -08:00
Danny McCormick
68b4f3ca04 Add Azure Pipelines status badge (#3233) 2018-11-15 11:51:42 -08:00
Chris LaFreniere
6b31f2b3f2 Stop Showing Overview Rulers in Notebooks (#3226)
* Stop showing decorationsOverviewRuler in notebook code cells by setting hidden visibility in css

* Also change overviewRuler options in IEdtorOptions for safety

* address CR comments in css file
2018-11-15 11:47:03 -08:00
Raj
63cf0f1548 Trusted/Not-trusted functionality implementation (#3211)
* 3194: Hookup trusted/not-trusted functionality and css changes

* 3194: Trusted implementation changes

* 3194: Code review changes

* 3225: No border in between code-cell and output
2018-11-14 19:07:10 -08:00
Yurong He
e607f68b3e Add more actions to cell (#3217)
* Added toggle more actions to cell

* Resolve PR comments
-- Added INotificationService for notification msg

* Reduced ToggleMoreAction to smaller size. So the dropdown could be displayed closer to it instead of at the buttom of the cell.
2018-11-14 17:51:59 -08:00
Yurong He
db3bb82dbd Change code and text font to 11 (#3216)
Ajust the padding top to 0
Change task bar height to 36
2018-11-14 15:36:24 -08:00
Yurong He
5889c600fa Resend selectBox change to on against master. PR against nativeNotebook was approved already. (#3215) 2018-11-14 13:48:41 -08:00
Matt Irvine
d7d4c7236c Update azure-pipelines-windows file test path (#3214) 2018-11-14 13:41:55 -08:00
Kevin Cunnane
f54d8ce36f Merge changes from the Notebook feature branch.
These will be preserved as they have important history.
2018-11-14 11:33:59 -08:00
Kevin Cunnane
43faa13cb5 Add notebook feature flag that is enabled by default (#3210)
* Add notebook feature flag that is enabled by default
- After this, the `notebook.enabled` flag must be set to true when testing notebook integration
2018-11-14 11:33:22 -08:00
Matt Irvine
85a2d994f3 Don't close connection dialog when cancelling a connection (#3207) 2018-11-13 16:44:26 -08:00
Kevin Cunnane
d8cd78cd6b Merge master 2018-11-13 16:35:17 -08:00
Alan Ren
3e59a5bcd2 update version to 0.4.0 (#3205) 2018-11-13 15:20:31 -08:00
Aditya Bist
0efb89d6ff fix loading perf when switching tabs (#3169) 2018-11-13 13:06:45 -08:00
Matt Irvine
6697c075cb Use newer version of request in azurecore (#3202) 2018-11-13 12:59:16 -08:00
Chris LaFreniere
06660160e7 Vertical Toolbar Improvements and Fix for Untitled File Load Issue (#3189)
* Add horizontal toolbar

* Add vertical toolbar

* Fix for untitled scheme file load

* further fixes to vertical toolbar

* Addressing PR comments
2018-11-13 11:05:54 -08:00
Kevin Cunnane
0b571737b7 Support notebook file types contribution (#3196)
* Support notebook file types contribution
- Extensions can define a provider and what file types it should be used for
- Verified that this works for Jupyter Content & Server Managers.
- Starts Jupyter server as expected

Not in this PR:
- Support for session manager end to end
- Tests
2018-11-12 17:32:53 -08:00
Raj
0a486a280d 3190: Code and Text cells from tool bar (#3191)
* 3190: Code and Text cells from tool bar

* 3190: Adding images under media folder and to css
2018-11-12 11:41:02 -08:00
Raj
a2bbf3f44e 3147: Notebook markdown cell should be opened in preview mode (#3168)
* 3147: Notebook markdown cell should be opened in preview mode

* 3147: Default ability to double click a cell to add text

* Misc changes

* CSS cleanup

* Localization stuff

* Remove constants file in Notebook codebase
2018-11-09 10:38:14 -08:00
Yurong He
7508192ab9 Added empty kernel and hook up with Kernel drop down (#3173)
* Initial toolbar work
- This is in-progress and needs additional fixes

* Resolve PR comments

* Added empty kernel and hook up with Kernel dropdown

* Resolve PR comments
2018-11-08 14:27:20 -08:00
Raj
bbf6cbd8fb Hookup trusted flag to both code cell and markdown preview (#3166)
* 1133: Notebook file registration changes

* File registration stuff

* Yarn files

* Outputview Changes

* Misc changes

* Changes to code component name space

* Output view changes

* notebook output view changes

* Latest changes

* Output view changes

* Code review changes on output view

* CSS file and misc changes

* Hookup trusted mode to code cell and markdown

* Return default sanitizer

* Misc changes - code review comments
2018-11-08 14:12:59 -08:00
Kevin Cunnane
9765269d27 Begin defining Extension-based Notebook Provider (#3172)
Implements provider contribution in the MainThreadNotebook, with matching function calls in the ExtHostNotebook class. This will allow us to proxy through notebook providers (specifically, creation of a notebook manager with required content, server managers) from an extension up through to the main process.

Implemented in this PR:
- Callthroughs for content and server manager APIs
- Very basic unit tests covering provider & manager registration

Not implemented:
- Fuller unit tests on the specific callthrough methods for content & server manager.
- Contribution point needed to test this (so we can actually pass through the extension's existing Notebook implementation)
2018-11-08 13:06:40 -08:00
Raj
71c14a0837 Output view changes (#3146)
* 1133: Notebook file registration changes

* File registration stuff

* Yarn files

* Outputview Changes

* Misc changes

* Changes to code component name space

* Output view changes

* notebook output view changes

* Latest changes

* Output view changes

* Code review changes on output view

* CSS file and misc changes
2018-11-07 14:19:33 -08:00
Kevin Cunnane
ecd40de7ec Integrate notebook service with notebook UI (#3143)
Port notebookView code over to notebook.component.ts.
Integrate loading of notebook contents into the UI
2018-11-06 16:31:37 -08:00
Yurong He
5da89ac05b Add localContentManger and dummy sessionManager (#3130)
* - keyboard binding to arrow keys
- toggle markdown editor by double click

* Added localContentManger and dummpy sessionManager
2018-11-05 17:55:13 -08:00
Kevin Cunnane
3c785ae7d8 Merge master 2018-11-04 15:15:27 -08:00
Kevin Cunnane
d434724a54 Remove handle from API (#3093)
- This was missed in previous checkin
2018-11-01 17:00:46 -07:00
Kevin Cunnane
fc3bf45a7f Port most notebook model code over to be behind a service (#3068)
- Defines a new NotebookService in Azure Data Studio which will be used to interact with notebooks. Since notebooks can require per-file instantiation the provider is just used to create & track managers for a given URI.
- Inject this into notebook.component.ts and pass required parameters that'll be used to properly initialize a manger into the method. Actual initialization not done yet.
- Port over & recompile notebook model code
- Define most required APIs in sqlops.proposed.d.ts. In the future, these will be used by extensions to contribute their own providers.
2018-10-31 22:01:40 -07:00
Kevin Cunnane
ac0ffab99c Merge remote-tracking branch 'origin/master' into feature/nativeNotebook 2018-10-30 16:08:19 -07:00
Abbie Petchtes
533f2734f1 Add markdown cell to Notebook (#3014)
* add markdown cell

* add markdown preview for Notebook

* formatting

* address comment
2018-10-26 16:20:06 -07:00
Raj
2859bee4c0 1133: Notebook file registration changes (#2969)
* 1133: Notebook file registration changes

* File registration stuff
2018-10-24 13:47:41 -07:00
Abbie Petchtes
972f857c71 Cell code fit and finish (#2972)
* add the look and feel for code cell

* formatting

* adding the active cell

* formatting
2018-10-23 12:22:19 -07:00
AbbiePetcht
d2eb1488fd Merge branch 'master' into feature/nativeNotebook 2018-10-23 10:23:10 -07:00
AbbiePetcht
508e4eac61 Merge branch 'master' into feature/nativeNotebook 2018-10-17 10:58:49 -07:00
AbbiePetcht
fd1d807012 Merge branch 'master' into feature/nativeNotebook 2018-10-16 16:30:11 -07:00
Abbie Petchtes
906c4c7f39 Add code cell (#2909)
* initial work for addig code and code cell type

* add cell model and create editor for each cell

* formatting

* fix resizing issue

* small changes

* address comment
2018-10-16 16:28:15 -07:00
AbbiePetcht
668e43f57c Merge branch 'master' into feature/nativeNotebook 2018-10-12 13:01:43 -07:00
Abbie Petchtes
6d260c195f create new notebook editor and add the place holder for toolbar and cell list (#2817)
* create new notebook editor and add the place holder for toolbar and cell list

* address comments
2018-10-11 16:14:54 -07:00
AbbiePetcht
fdfecbb3f7 Merge branch 'master' into feature/nativeNotebook 2018-10-11 10:23:30 -07:00
152 changed files with 13252 additions and 2275 deletions

View File

@@ -1,58 +0,0 @@
sudo: false
language: cpp
os:
- linux
- osx
cache:
directories:
- $HOME/.cache/yarn
notifications:
email: false
webhooks:
- http://vscode-probot.westus.cloudapp.azure.com:3450/travis/notifications
- http://vscode-test-probot.westus.cloudapp.azure.com:3450/travis/notifications
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- gcc-4.9
- g++-4.9
- gcc-4.9-multilib
- g++-4.9-multilib
- zip
- libgtk2.0-0
- libx11-dev
- libxkbfile-dev
- libsecret-1-dev
before_install:
- git submodule update --init --recursive
- nvm install 8.9.1
- nvm use 8.9.1
- npm i -g yarn
# - npm config set python `which python`
- if [ $TRAVIS_OS_NAME == "linux" ]; then
export CXX="g++-4.9" CC="gcc-4.9" DISPLAY=:99.0;
sh -e /etc/init.d/xvfb start;
sleep 3;
fi
# Make npm logs less verbose
# - npm config set depth 0
# - npm config set loglevel warn
install:
- yarn
script:
- node_modules/.bin/gulp electron --silent
- node_modules/.bin/gulp compile --silent --max_old_space_size=4096
- node_modules/.bin/gulp optimize-vscode --silent --max_old_space_size=4096
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then ./scripts/test.sh --coverage --reporter dot; else ./scripts/test.sh --reporter dot; fi
after_success:
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then node_modules/.bin/coveralls < .build/coverage/lcov.info; fi

View File

@@ -1,6 +1,7 @@
# Azure Data Studio
[![Join the chat at https://gitter.im/Microsoft/sqlopsstudio](https://badges.gitter.im/Microsoft/sqlopsstudio.svg)](https://gitter.im/Microsoft/sqlopsstudio?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Build Status](https://dev.azure.com/ms/azuredatastudio/_apis/build/status/Microsoft.azuredatastudio)](https://dev.azure.com/ms/azuredatastudio/_build/latest?definitionId=4)
Azure Data Studio is a data management tool that enables you to work with SQL Server, Azure SQL DB and SQL DW from Windows, macOS and Linux.

View File

@@ -34,6 +34,7 @@ expressly granted herein, whether by implication, estoppel or otherwise.
jquery-ui: https://github.com/jquery/jquery-ui
jquery.event.drag: https://github.com/devongovett/jquery.event.drag
jschardet: https://github.com/aadsm/jschardet
JupyterLab: https://github.com/jupyterlab/jupyterlab
make-error: https://github.com/JsCommunity/make-error
minimist: https://github.com/substack/minimist
moment: https://github.com/moment/moment
@@ -1166,6 +1167,43 @@ That's all there is to it!
=========================================
END OF jschardet NOTICES AND INFORMATION
%% JupyterLab NOTICES AND INFORMATION BEGIN HERE
Copyright (c) 2015 Project Jupyter Contributors
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Semver File License
===================
The semver.py file is from https://github.com/podhmo/python-semver
which is licensed under the "MIT" license. See the semver.py file for details.
END OF JupyterLab NOTICES AND INFORMATION
%% make-error NOTICES AND INFORMATION BEGIN HERE
=========================================
ISC © Julien Fontanet

View File

@@ -1,19 +0,0 @@
environment:
ELECTRON_RUN_AS_NODE: 1
VSCODE_BUILD_VERBOSE: true
cache:
- '%LOCALAPPDATA%\Yarn\cache'
install:
- ps: Install-Product node 8.9.1 x64
build_script:
- yarn
- .\node_modules\.bin\gulp electron
- npm run compile
test_script:
- node --version
- .\scripts\test.bat
- .\scripts\test-integration.bat

View File

@@ -17,11 +17,10 @@ steps:
displayName: 'Compile'
- script: |
.\scripts\test.bat
.\scripts\test-integration.bat
.\scripts\test.bat --reporter mocha-junit-reporter
displayName: 'Test'
- task: PublishTestResults@2
inputs:
testResultsFiles: '**/test-results.xml'
testResultsFiles: 'test-results.xml'
condition: succeededOrFailed()

View File

@@ -129,6 +129,7 @@ const vscodeResources = [
'out-build/sql/parts/jobManagement/common/media/*.svg',
'out-build/sql/media/objectTypes/*.svg',
'out-build/sql/media/icons/*.svg',
'out-build/sql/parts/notebook/media/**/*.svg',
'!**/test/**'
];

View File

@@ -9,20 +9,21 @@
"@types/mime": "0.0.29",
"@types/minimatch": "^3.0.3",
"@types/node": "8.0.33",
"@types/xml2js": "0.0.33",
"@types/request": "^2.47.0",
"@types/xml2js": "0.0.33",
"azure-storage": "^2.1.0",
"decompress": "^4.2.0",
"del": "^3.0.0",
"documentdb": "1.13.0",
"service-downloader": "github:anthonydresser/service-downloader#0.1.5",
"fs-extra-promise": "^1.0.1",
"github-releases": "^0.4.1",
"mime": "^1.3.4",
"minimist": "^1.2.0",
"request": "^2.85.0",
"service-downloader": "github:anthonydresser/service-downloader#0.1.5",
"typescript": "2.9.2",
"vscode": "^1.0.1",
"xml2js": "^0.4.17",
"github-releases": "^0.4.1",
"request": "^2.85.0"
"xml2js": "^0.4.17"
},
"scripts": {
"compile": "tsc -p tsconfig.build.json",
@@ -30,4 +31,4 @@
"postinstall": "npm run compile",
"npmCheckJs": "tsc --noEmit"
}
}
}

View File

@@ -46,6 +46,7 @@
"@types/minimatch@^3.0.3":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==
"@types/node@*":
version "8.0.51"
@@ -570,6 +571,18 @@ deep-assign@^1.0.0:
dependencies:
is-obj "^1.0.0"
del@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/del/-/del-3.0.0.tgz#53ecf699ffcbcb39637691ab13baf160819766e5"
integrity sha1-U+z2mf/LyzljdpGrE7rxYIGXZuU=
dependencies:
globby "^6.1.0"
is-path-cwd "^1.0.0"
is-path-in-cwd "^1.0.0"
p-map "^1.1.1"
pify "^3.0.0"
rimraf "^2.2.8"
delayed-stream@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
@@ -941,6 +954,29 @@ glob@^5.0.3:
once "^1.3.0"
path-is-absolute "^1.0.0"
glob@^7.0.3:
version "7.1.3"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1"
integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==
dependencies:
fs.realpath "^1.0.0"
inflight "^1.0.4"
inherits "2"
minimatch "^3.0.4"
once "^1.3.0"
path-is-absolute "^1.0.0"
globby@^6.1.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c"
integrity sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=
dependencies:
array-union "^1.0.1"
glob "^7.0.3"
object-assign "^4.0.1"
pify "^2.0.0"
pinkie-promise "^2.0.0"
glogg@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/glogg/-/glogg-1.0.1.tgz#dcf758e44789cc3f3d32c1f3562a3676e6a34810"
@@ -1312,6 +1348,25 @@ is-obj@^1.0.0:
resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f"
integrity sha1-PkcprB9f3gJc19g6iW2rn09n2w8=
is-path-cwd@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d"
integrity sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=
is-path-in-cwd@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz#5ac48b345ef675339bd6c7a48a912110b241cf52"
integrity sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==
dependencies:
is-path-inside "^1.0.0"
is-path-inside@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.1.tgz#8ef5b7de50437a3fdca6b4e865ef7aa55cb48036"
integrity sha1-jvW33lBDej/cprToZe96pVy0gDY=
dependencies:
path-is-inside "^1.0.1"
is-posix-bracket@^0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4"
@@ -1777,6 +1832,11 @@ os-tmpdir@~1.0.2:
resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=
p-map@^1.1.1:
version "1.2.0"
resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.2.0.tgz#e4e94f311eabbc8633a1e79908165fca26241b6b"
integrity sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==
parse-glob@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/parse-glob/-/parse-glob-3.0.4.tgz#b2c376cfb11f35513badd173ef0bb6e3a388391c"
@@ -1797,6 +1857,11 @@ path-is-absolute@^1.0.0:
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
path-is-inside@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53"
integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=
pause-stream@0.0.11:
version "0.0.11"
resolved "https://registry.yarnpkg.com/pause-stream/-/pause-stream-0.0.11.tgz#fe5a34b0cbce12b5aa6a2b403ee2e73b602f1445"
@@ -1819,7 +1884,7 @@ performance-now@^2.1.0:
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=
pify@^2.3.0:
pify@^2.0.0, pify@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw=
@@ -2117,7 +2182,7 @@ requires-port@~1.0.0:
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=
rimraf@2:
rimraf@2, rimraf@^2.2.8:
version "2.6.2"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36"
integrity sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==

View File

@@ -147,7 +147,7 @@
}
},
"dependencies": {
"request": "2.63.0",
"request": "2.88.0",
"azure-arm-resource": "^7.0.0",
"azure-arm-sql": "^5.0.1",
"vscode-nls": "^4.0.0"

View File

@@ -10,6 +10,7 @@
"@types/node@^8.0.24":
version "8.10.36"
resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.36.tgz#eac05d576fbcd0b4ea3c912dc58c20475c08d9e4"
integrity sha512-SL6KhfM7PTqiFmbCW3eVNwVBZ+88Mrzbuvn9olPsfv43mbiWaFY+nRcz/TGGku0/lc2FepdMbImdMY1JrQ+zbw==
"@types/node@^8.0.47":
version "8.10.30"
@@ -41,18 +42,6 @@ ajv@^5.3.0:
fast-json-stable-stringify "^2.0.0"
json-schema-traverse "^0.3.0"
ansi-regex@^2.0.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df"
ansi-styles@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
asn1@0.1.11:
version "0.1.11"
resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.1.11.tgz#559be18376d08a4ec4dbe80877d27818639b2df7"
asn1@~0.2.3:
version "0.2.4"
resolved "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136"
@@ -65,10 +54,6 @@ assert-plus@1.0.0, assert-plus@^1.0.0:
resolved "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525"
integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=
assert-plus@^0.1.5:
version "0.1.5"
resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.1.5.tgz#ee74009413002d84cec7219c6ac811812e723160"
async@2.6.0:
version "2.6.0"
resolved "https://registry.npmjs.org/async/-/async-2.6.0.tgz#61a29abb6fcc026fea77e56d1c6ec53a795951f4"
@@ -76,9 +61,10 @@ async@2.6.0:
dependencies:
lodash "^4.14.0"
async@>=0.6.0, async@^2.0.1:
async@>=0.6.0:
version "2.6.1"
resolved "https://registry.yarnpkg.com/async/-/async-2.6.1.tgz#b245a23ca71930044ec53fa46aa00a3e87c6a610"
integrity sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==
dependencies:
lodash "^4.17.10"
@@ -87,10 +73,6 @@ asynckit@^0.4.0:
resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
integrity sha1-x57Zf380y48robyXkLzDZkdLS3k=
aws-sign2@~0.5.0:
version "0.5.0"
resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.5.0.tgz#c57103f7a17fc037f02d7c2e64b602ea223f7d63"
aws-sign2@~0.7.0:
version "0.7.0"
resolved "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8"
@@ -129,22 +111,6 @@ bcrypt-pbkdf@^1.0.0:
dependencies:
tweetnacl "^0.14.3"
bl@~1.0.0:
version "1.0.3"
resolved "http://registry.npmjs.org/bl/-/bl-1.0.3.tgz#fc5421a28fd4226036c3b3891a66a25bc64d226e"
dependencies:
readable-stream "~2.0.5"
bluebird@^2.9.30:
version "2.11.0"
resolved "http://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz#534b9033c022c9579c56ba3b3e5a5caafbb650e1"
boom@2.x.x:
version "2.10.1"
resolved "https://registry.yarnpkg.com/boom/-/boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f"
dependencies:
hoek "2.x.x"
brace-expansion@^1.1.7:
version "1.1.11"
resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
@@ -163,25 +129,11 @@ buffer-equal-constant-time@1.0.1:
resolved "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=
caseless@~0.11.0:
version "0.11.0"
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.11.0.tgz#715b96ea9841593cc33067923f5ec60ebda4f7d7"
caseless@~0.12.0:
version "0.12.0"
resolved "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=
chalk@^1.0.0:
version "1.1.3"
resolved "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
dependencies:
ansi-styles "^2.2.1"
escape-string-regexp "^1.0.2"
has-ansi "^2.0.0"
strip-ansi "^3.0.0"
supports-color "^2.0.0"
circular-json@^0.3.1:
version "0.3.3"
resolved "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz#815c99ea84f6809529d2f45791bdf82711352d66"
@@ -199,9 +151,10 @@ combined-stream@1.0.6:
dependencies:
delayed-stream "~1.0.0"
combined-stream@^1.0.5, combined-stream@~1.0.1, combined-stream@~1.0.6:
combined-stream@~1.0.6:
version "1.0.7"
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.7.tgz#2d1d24317afb8abe95d6d2c0b07b57813539d828"
integrity sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==
dependencies:
delayed-stream "~1.0.0"
@@ -210,28 +163,15 @@ commander@2.15.1:
resolved "http://registry.npmjs.org/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f"
integrity sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==
commander@^2.8.1:
version "2.19.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a"
concat-map@0.0.1:
version "0.0.1"
resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
core-util-is@1.0.2, core-util-is@~1.0.0:
core-util-is@1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
cryptiles@2.x.x:
version "2.0.5"
resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8"
dependencies:
boom "2.x.x"
ctype@0.5.3:
version "0.5.3"
resolved "https://registry.yarnpkg.com/ctype/-/ctype-0.5.3.tgz#82c18c2461f74114ef16c135224ad0b9144ca12f"
integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
dashdash@^1.12.0:
version "1.14.1"
@@ -282,13 +222,15 @@ ecdsa-sig-formatter@1.0.10:
dependencies:
safe-buffer "^5.0.1"
escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2:
escape-string-regexp@1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
extend@~3.0.0, extend@~3.0.2:
extend@~3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
extsprintf@1.3.0:
version "1.3.0"
@@ -310,17 +252,10 @@ fast-json-stable-stringify@^2.0.0:
resolved "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2"
integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I=
forever-agent@~0.6.0, forever-agent@~0.6.1:
forever-agent@~0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
form-data@~1.0.0-rc1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-1.0.1.tgz#ae315db9a4907fa065502304a66d7733475ee37c"
dependencies:
async "^2.0.1"
combined-stream "^1.0.5"
mime-types "^2.1.11"
integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=
form-data@~2.3.2:
version "2.3.2"
@@ -336,18 +271,6 @@ fs.realpath@^1.0.0:
resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
generate-function@^2.0.0:
version "2.3.1"
resolved "https://registry.yarnpkg.com/generate-function/-/generate-function-2.3.1.tgz#f069617690c10c868e73b8465746764f97c3479f"
dependencies:
is-property "^1.0.2"
generate-object-property@^1.1.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/generate-object-property/-/generate-object-property-1.2.0.tgz#9c0e1c40308ce804f4783618b937fa88f99d50d0"
dependencies:
is-property "^1.0.0"
getpass@^0.1.1:
version "0.1.7"
resolved "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa"
@@ -377,15 +300,6 @@ har-schema@^2.0.0:
resolved "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=
har-validator@^1.6.1:
version "1.8.0"
resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-1.8.0.tgz#d83842b0eb4c435960aeb108a067a3aa94c0eeb2"
dependencies:
bluebird "^2.9.30"
chalk "^1.0.0"
commander "^2.8.1"
is-my-json-valid "^2.12.0"
har-validator@~5.1.0:
version "5.1.0"
resolved "https://registry.npmjs.org/har-validator/-/har-validator-5.1.0.tgz#44657f5688a22cfd4b72486e81b3a3fb11742c29"
@@ -394,43 +308,16 @@ har-validator@~5.1.0:
ajv "^5.3.0"
har-schema "^2.0.0"
has-ansi@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91"
dependencies:
ansi-regex "^2.0.0"
has-flag@^3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0=
hawk@~3.1.0:
version "3.1.3"
resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4"
dependencies:
boom "2.x.x"
cryptiles "2.x.x"
hoek "2.x.x"
sntp "1.x.x"
he@1.1.1:
version "1.1.1"
resolved "https://registry.npmjs.org/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd"
integrity sha1-k0EP0hsAlzUVH4howvJx80J+I/0=
hoek@2.x.x:
version "2.16.3"
resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed"
http-signature@~0.11.0:
version "0.11.0"
resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-0.11.0.tgz#1796cf67a001ad5cd6849dca0991485f09089fe6"
dependencies:
asn1 "0.1.11"
assert-plus "^0.1.5"
ctype "0.5.3"
http-signature@~1.2.0:
version "1.2.0"
resolved "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1"
@@ -448,33 +335,16 @@ inflight@^1.0.4:
once "^1.3.0"
wrappy "1"
inherits@2, inherits@~2.0.1:
inherits@2:
version "2.0.3"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
is-buffer@^1.1.6:
version "1.1.6"
resolved "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==
is-my-ip-valid@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz#7b351b8e8edd4d3995d4d066680e664d94696824"
is-my-json-valid@^2.12.0:
version "2.19.0"
resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.19.0.tgz#8fd6e40363cd06b963fa877d444bfb5eddc62175"
dependencies:
generate-function "^2.0.0"
generate-object-property "^1.1.0"
is-my-ip-valid "^1.0.0"
jsonpointer "^4.0.0"
xtend "^4.0.0"
is-property@^1.0.0, is-property@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84"
is-stream@^1.1.0:
version "1.1.0"
resolved "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
@@ -485,13 +355,10 @@ is-typedarray@~1.0.0:
resolved "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=
isarray@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
isstream@~0.1.1, isstream@~0.1.2:
isstream@~0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=
jsbn@~0.1.0:
version "0.1.1"
@@ -508,13 +375,10 @@ json-schema@0.2.3:
resolved "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13"
integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=
json-stringify-safe@~5.0.0, json-stringify-safe@~5.0.1:
json-stringify-safe@~5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
jsonpointer@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9"
integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=
jsprim@^1.2.2:
version "1.4.1"
@@ -553,9 +417,10 @@ mime-db@~1.36.0:
resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.36.0.tgz#5020478db3c7fe93aad7bbcc4dcf869c43363397"
integrity sha512-L+xvyD9MkoYMXb1jAmzI/lWYAxAMCPvIBSWur0PZ5nOf5euahRLVqH//FKW9mWp2lkqUgYiXPgkzfMUFi4zVDw==
mime-types@^2.1.11, mime-types@^2.1.12, mime-types@~2.1.19, mime-types@~2.1.2:
mime-types@^2.1.12, mime-types@~2.1.19:
version "2.1.20"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.20.tgz#930cb719d571e903738520f8470911548ca2cc19"
integrity sha512-HrkrPaP9vGuWbLK1B1FfgAkbqNjIuy4eHlIYnFi7kamZyLLrGlo2mpcx0bBmNpKqBtYtAfGbodDddIgddSJC2A==
dependencies:
mime-db "~1.36.0"
@@ -631,14 +496,6 @@ ms@2.0.0:
resolved "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
node-uuid@~1.4.0:
version "1.4.8"
resolved "https://registry.yarnpkg.com/node-uuid/-/node-uuid-1.4.8.tgz#b040eb0923968afabf8d32fb1f17f1167fdab907"
oauth-sign@~0.8.0:
version "0.8.2"
resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43"
oauth-sign@~0.9.0:
version "0.9.0"
resolved "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455"
@@ -666,10 +523,6 @@ postinstall-build@^5.0.1:
resolved "https://registry.npmjs.org/postinstall-build/-/postinstall-build-5.0.3.tgz#238692f712a481d8f5bc8960e94786036241efc7"
integrity sha512-vPvPe8TKgp4FLgY3+DfxCE5PIfoXBK2lyLfNCxsRbDsV6vS4oU5RG/IWxrblMn6heagbnMED3MemUQllQ2bQUg==
process-nextick-args@~1.0.6:
version "1.0.7"
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3"
psl@^1.1.24:
version "1.1.29"
resolved "https://registry.npmjs.org/psl/-/psl-1.1.29.tgz#60f580d360170bb722a797cc704411e6da850c67"
@@ -680,53 +533,14 @@ punycode@^1.4.1:
resolved "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
integrity sha1-wNWmOycYgArY4esPpSachN1BhF4=
qs@~5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-5.1.0.tgz#4d932e5c7ea411cca76a312d39a606200fd50cd9"
qs@~6.5.2:
version "6.5.2"
resolved "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==
readable-stream@~2.0.5:
version "2.0.6"
resolved "http://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz#8f90341e68a53ccc928788dacfcd11b36eb9b78e"
dependencies:
core-util-is "~1.0.0"
inherits "~2.0.1"
isarray "~1.0.0"
process-nextick-args "~1.0.6"
string_decoder "~0.10.x"
util-deprecate "~1.0.1"
request@2.63.0:
version "2.63.0"
resolved "http://registry.npmjs.org/request/-/request-2.63.0.tgz#c83e7c3485e5d9bf9b146318429bc48f1253d8be"
dependencies:
aws-sign2 "~0.5.0"
bl "~1.0.0"
caseless "~0.11.0"
combined-stream "~1.0.1"
extend "~3.0.0"
forever-agent "~0.6.0"
form-data "~1.0.0-rc1"
har-validator "^1.6.1"
hawk "~3.1.0"
http-signature "~0.11.0"
isstream "~0.1.1"
json-stringify-safe "~5.0.0"
mime-types "~2.1.2"
node-uuid "~1.4.0"
oauth-sign "~0.8.0"
qs "~5.1.0"
stringstream "~0.0.4"
tough-cookie ">=0.12.0"
tunnel-agent "~0.4.0"
"request@>= 2.52.0", request@^2.88.0:
request@2.88.0, "request@>= 2.52.0", request@^2.88.0:
version "2.88.0"
resolved "https://registry.npmjs.org/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef"
resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef"
integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==
dependencies:
aws-sign2 "~0.7.0"
@@ -804,12 +618,6 @@ should@^13.2.1:
should-type-adaptors "^1.0.1"
should-util "^1.0.0"
sntp@1.x.x:
version "1.0.9"
resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198"
dependencies:
hoek "2.x.x"
sshpk@^1.7.0:
version "1.14.2"
resolved "https://registry.npmjs.org/sshpk/-/sshpk-1.14.2.tgz#c6fc61648a3d9c4e764fd3fcdf4ea105e492ba98"
@@ -826,20 +634,6 @@ sshpk@^1.7.0:
jsbn "~0.1.0"
tweetnacl "~0.14.0"
string_decoder@~0.10.x:
version "0.10.31"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
stringstream@~0.0.4:
version "0.0.6"
resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.6.tgz#7880225b0d4ad10e30927d167a1d6f2fd3b33a72"
strip-ansi@^3.0.0:
version "3.0.1"
resolved "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf"
dependencies:
ansi-regex "^2.0.0"
supports-color@5.4.0:
version "5.4.0"
resolved "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz#1c6b337402c2137605efe19f10fec390f6faab54"
@@ -847,18 +641,15 @@ supports-color@5.4.0:
dependencies:
has-flag "^3.0.0"
supports-color@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
through@^2.3.8:
version "2.3.8"
resolved "http://registry.npmjs.org/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=
tough-cookie@>=0.12.0, tough-cookie@~2.4.3:
tough-cookie@~2.4.3:
version "2.4.3"
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781"
integrity sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==
dependencies:
psl "^1.1.24"
punycode "^1.4.1"
@@ -870,10 +661,6 @@ tunnel-agent@^0.6.0:
dependencies:
safe-buffer "^5.0.1"
tunnel-agent@~0.4.0:
version "0.4.3"
resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.4.3.tgz#6373db76909fe570e08d73583365ed828a74eeeb"
tunnel@0.0.5:
version "0.0.5"
resolved "https://registry.npmjs.org/tunnel/-/tunnel-0.0.5.tgz#d1532254749ed36620fcd1010865495a1fa9d0ae"
@@ -898,10 +685,6 @@ typemoq@^2.1.0:
resolved "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz#06dce34a0e68a7babc29b365b8e74b8925203961"
integrity sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg==
util-deprecate@~1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
uuid@^3.1.0, uuid@^3.2.1, uuid@^3.3.2:
version "3.3.2"
resolved "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131"
@@ -934,7 +717,3 @@ wrappy@1:
xpath.js@~1.1.0:
version "1.1.0"
resolved "https://registry.npmjs.org/xpath.js/-/xpath.js-1.1.0.tgz#3816a44ed4bb352091083d002a383dd5104a5ff1"
xtend@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"

View File

@@ -53,7 +53,7 @@
}
},
"dependencies": {
"dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#0.2.7",
"dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#0.2.10",
"opener": "^1.4.3",
"service-downloader": "github:anthonydresser/service-downloader#0.1.5",
"vscode-extension-telemetry": "0.0.18",

View File

@@ -11,7 +11,8 @@ agent-base@4, agent-base@^4.1.0:
applicationinsights@1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/applicationinsights/-/applicationinsights-1.0.1.tgz#53446b830fe8d5d619eee2a278b31d3d25030927"
resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-1.0.1.tgz#53446b830fe8d5d619eee2a278b31d3d25030927"
integrity sha1-U0Rrgw/o1dYZ7uKieLMdPSUDCSc=
dependencies:
diagnostic-channel "0.2.0"
diagnostic-channel-publishers "0.2.1"
@@ -35,7 +36,7 @@ buffer-alloc-unsafe@^1.1.0:
resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0"
integrity sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==
buffer-alloc@^1.1.0:
buffer-alloc@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/buffer-alloc/-/buffer-alloc-1.2.0.tgz#890dd90d923a873e08e10e5fd51a57e5b7cce0ec"
integrity sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==
@@ -74,19 +75,26 @@ core-util-is@~1.0.0:
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
"dataprotocol-client@github:Microsoft/sqlops-dataprotocolclient#0.2.7":
version "0.2.6"
resolved "https://codeload.github.com/Microsoft/sqlops-dataprotocolclient/tar.gz/85653d8b305af8aef334728d71f07bdc240dfcb7"
"dataprotocol-client@github:Microsoft/sqlops-dataprotocolclient#0.2.10":
version "0.2.10"
resolved "https://codeload.github.com/Microsoft/sqlops-dataprotocolclient/tar.gz/4de3f7caf0eba54159911b977ddb4f5d7c0a9ca8"
dependencies:
vscode-languageclient "3.5.1"
debug@3.1.0, debug@^3.1.0:
debug@3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
dependencies:
ms "2.0.0"
debug@^3.1.0:
version "3.2.6"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
dependencies:
ms "^2.1.1"
decompress-tar@^4.0.0, decompress-tar@^4.1.0, decompress-tar@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/decompress-tar/-/decompress-tar-4.1.1.tgz#718cbd3fcb16209716e70a26b84e7ba4592e5af1"
@@ -142,11 +150,13 @@ decompress@^4.2.0:
diagnostic-channel-publishers@0.2.1:
version "0.2.1"
resolved "https://registry.npmjs.org/diagnostic-channel-publishers/-/diagnostic-channel-publishers-0.2.1.tgz#8e2d607a8b6d79fe880b548bc58cc6beb288c4f3"
resolved "https://registry.yarnpkg.com/diagnostic-channel-publishers/-/diagnostic-channel-publishers-0.2.1.tgz#8e2d607a8b6d79fe880b548bc58cc6beb288c4f3"
integrity sha1-ji1geottef6IC1SLxYzGvrKIxPM=
diagnostic-channel@0.2.0:
version "0.2.0"
resolved "https://registry.npmjs.org/diagnostic-channel/-/diagnostic-channel-0.2.0.tgz#cc99af9612c23fb1fff13612c72f2cbfaa8d5a17"
resolved "https://registry.yarnpkg.com/diagnostic-channel/-/diagnostic-channel-0.2.0.tgz#cc99af9612c23fb1fff13612c72f2cbfaa8d5a17"
integrity sha1-zJmvlhLCP7H/8TYSxy8sv6qNWhc=
dependencies:
semver "^5.3.0"
@@ -158,9 +168,9 @@ end-of-stream@^1.0.0:
once "^1.4.0"
es6-promise@^4.0.3:
version "4.2.4"
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.4.tgz#dc4221c2b16518760bd8c39a52d8f356fc00ed29"
integrity sha512-/NdNZVJg+uZgtm9eS3O6lrOLYmQag2DjdEXuPaHlZ6RuVqgqaVZfgYCepEIKsLqwdQArOPtC3XzRLqGGfT8KQQ==
version "4.2.5"
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.5.tgz#da6d0d5692efb461e082c14817fe2427d8f5d054"
integrity sha512-n6wvpdE43VFtJq+lUDYDBFUwV8TZbuGXLV4D6wKafg13ldznKsyEvatubnmUe31zcvelSzOHF+XbaT+Bl9ObDg==
es6-promisify@^5.0.0:
version "5.0.0"
@@ -210,9 +220,9 @@ get-stream@^2.2.0:
pinkie-promise "^2.0.0"
graceful-fs@^4.1.10:
version "4.1.11"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658"
integrity sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=
version "4.1.15"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00"
integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==
"graceful-readlink@>= 1.0.0":
version "1.0.1"
@@ -284,6 +294,11 @@ ms@2.0.0:
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
ms@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"
integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==
object-assign@^4.0.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
@@ -297,9 +312,9 @@ once@^1.4.0:
wrappy "1"
opener@^1.4.3:
version "1.4.3"
resolved "https://registry.yarnpkg.com/opener/-/opener-1.4.3.tgz#5c6da2c5d7e5831e8ffa3964950f8d6674ac90b8"
integrity sha1-XG2ixdflgx6P+jlklQ+NZnSskLg=
version "1.5.1"
resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.1.tgz#6d2f0e77f1a0af0032aca716c2c1fbb8e7e8abed"
integrity sha512-goYSy5c2UXE4Ra1xixabeVh1guIX/ZV/YokJksb6q2lubWu6UbvPQ20p542/sFIll1nl8JnCyK9oBaOcCWXwvA==
os-tmpdir@~1.0.2:
version "1.0.2"
@@ -365,7 +380,8 @@ seek-bzip@^1.0.5:
semver@^5.3.0:
version "5.6.0"
resolved "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004"
integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==
"service-downloader@github:anthonydresser/service-downloader#0.1.5":
version "0.1.5"
@@ -393,16 +409,16 @@ strip-dirs@^2.0.0:
is-natural-number "^4.0.1"
tar-stream@^1.5.2:
version "1.6.1"
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.6.1.tgz#f84ef1696269d6223ca48f6e1eeede3f7e81f395"
integrity sha512-IFLM5wp3QrJODQFPm6/to3LJZrONdBY/otxcvDIQzu217zKye6yVR3hhi9lAjrC2Z+m/j5oDxMPb1qcd8cIvpA==
version "1.6.2"
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.6.2.tgz#8ea55dab37972253d9a9af90fdcd559ae435c555"
integrity sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==
dependencies:
bl "^1.0.0"
buffer-alloc "^1.1.0"
buffer-alloc "^1.2.0"
end-of-stream "^1.0.0"
fs-constants "^1.0.0"
readable-stream "^2.3.0"
to-buffer "^1.1.0"
to-buffer "^1.1.1"
xtend "^4.0.0"
through@^2.3.6:
@@ -417,15 +433,15 @@ tmp@^0.0.33:
dependencies:
os-tmpdir "~1.0.2"
to-buffer@^1.1.0:
to-buffer@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/to-buffer/-/to-buffer-1.1.1.tgz#493bd48f62d7c43fcded313a03dcadb2e1213a80"
integrity sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==
unbzip2-stream@^1.0.9:
version "1.2.5"
resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.2.5.tgz#73a033a567bbbde59654b193c44d48a7e4f43c47"
integrity sha512-izD3jxT8xkzwtXRUZjtmRwKnZoeECrfZ8ra/ketwOcusbZEp4mjULMnJOCfTDZBgGQAAY1AJ/IgxcwkavcX9Og==
version "1.3.1"
resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.3.1.tgz#7854da51622a7e63624221196357803b552966a1"
integrity sha512-fIZnvdjblYs7Cru/xC6tCPVhz7JkYcVQQkePwMLyQELzYTds2Xn8QefPVnvdVhhZqubxNA1cASXEH5wcK0Bucw==
dependencies:
buffer "^3.0.1"
through "^2.3.6"
@@ -437,7 +453,8 @@ util-deprecate@~1.0.1:
vscode-extension-telemetry@0.0.18:
version "0.0.18"
resolved "https://registry.npmjs.org/vscode-extension-telemetry/-/vscode-extension-telemetry-0.0.18.tgz#602ba20d8c71453aa34533a291e7638f6e5c0327"
resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.0.18.tgz#602ba20d8c71453aa34533a291e7638f6e5c0327"
integrity sha512-Vw3Sr+dZwl+c6PlsUwrTtCOJkgrmvS3OUVDQGcmpXWAgq9xGq6as0K4pUx+aGqTjzLAESmWSrs6HlJm6J6Khcg==
dependencies:
applicationinsights "1.0.1"
@@ -467,9 +484,9 @@ vscode-languageserver-types@3.5.0:
integrity sha1-5I15li8LjgLelV4/UkkI4rGcA3Q=
vscode-nls@^3.2.1:
version "3.2.4"
resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.4.tgz#2166b4183c8aea884d20727f5449e62be69fd398"
integrity sha512-FTjdqa4jDDoBjJqr36O8lmmZf/55kQ2w4ZY/+GL6K92fq765BqO3aYw21atnXUno/P04V5DWagNl4ybDIndJsw==
version "3.2.5"
resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.5.tgz#25520c1955108036dec607c85e00a522f247f1a4"
integrity sha512-ITtoh3V4AkWXMmp3TB97vsMaHRgHhsSFPsUdzlueSL+dRZbSNTZeOmdQv60kjCV306ghPxhDeoNUEm3+EZMuyw==
wrappy@1:
version "1.0.2"
@@ -491,4 +508,5 @@ yauzl@^2.4.2:
zone.js@0.7.6:
version "0.7.6"
resolved "https://registry.npmjs.org/zone.js/-/zone.js-0.7.6.tgz#fbbc39d3e0261d0986f1ba06306eb3aeb0d22009"
resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.7.6.tgz#fbbc39d3e0261d0986f1ba06306eb3aeb0d22009"
integrity sha1-+7w50+AmHQmG8boGMG6zrrDSIAk=

View File

@@ -23,7 +23,8 @@
"onCommand:markdown.showLockedPreviewToSide",
"onCommand:markdown.showSource",
"onCommand:markdown.showPreviewSecuritySelector",
"onWebviewPanel:markdown.preview"
"onWebviewPanel:markdown.preview",
"onCommand:notebook.showPreview"
],
"contributes": {
"commands": [
@@ -77,6 +78,11 @@
"command": "markdown.preview.toggleLock",
"title": "%markdown.preview.toggleLock.title%",
"category": "Markdown"
},
{
"command": "notebook.showPreview",
"title": "notebook.showPreview",
"category": "Notebook"
}
],
"menus": {
@@ -154,6 +160,10 @@
{
"command": "markdown.preview.toggleLock",
"when": "markdownPreviewFocus"
},
{
"command": "notebook.showPreview",
"when": "false"
}
]
},

View File

@@ -8,7 +8,8 @@ import * as vscode from 'vscode';
export interface Command {
readonly id: string;
execute(...args: any[]): void;
// {{SQL CARBON EDIT}}
execute(...args: any[]): any;
}
export class CommandManager {
@@ -26,7 +27,8 @@ export class CommandManager {
return command;
}
private registerCommand(id: string, impl: (...args: any[]) => void, thisArg?: any) {
// {{SQL CARBON EDIT}}
private registerCommand(id: string, impl: (...args: any[]) => any, thisArg?: any) {
if (this.commands.has(id)) {
return;
}

View File

@@ -11,3 +11,5 @@ export { RefreshPreviewCommand } from './refreshPreview';
export { ShowPreviewSecuritySelectorCommand } from './showPreviewSecuritySelector';
export { MoveCursorToPositionCommand } from './moveCursorToPosition';
export { ToggleLockCommand } from './toggleLock';
// {{SQL CARBON EDIT}}
export { ShowNotebookPreview } from './showNotebookPreview';

View File

@@ -0,0 +1,20 @@
/*---------------------------------------------------------------------------------------------
* 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 { Command } from '../commandManager';
import { MarkdownEngine } from '../markdownEngine';
export class ShowNotebookPreview implements Command {
public readonly id = 'notebook.showPreview';
public constructor(
private readonly engine: MarkdownEngine
) { }
public async execute(document: vscode.Uri, textContent: string): Promise<string> {
return this.engine.renderText(document, textContent);
}
}

View File

@@ -59,6 +59,8 @@ export function activate(context: vscode.ExtensionContext) {
commandManager.register(new commands.OnPreviewStyleLoadErrorCommand());
commandManager.register(new commands.OpenDocumentLinkCommand(engine));
commandManager.register(new commands.ToggleLockCommand(previewManager));
// {{SQL CARBON EDIT}}
commandManager.register(new commands.ShowNotebookPreview(engine));
context.subscriptions.push(vscode.workspace.onDidChangeConfiguration(() => {
logger.updateConfiguration();

View File

@@ -86,6 +86,12 @@ export class MarkdownEngine {
return { text, offset };
}
// {{SQL CARBON EDIT}}
public async renderText(document: vscode.Uri, text: string): Promise<string> {
const engine = await this.getEngine(document);
return engine.render(text);
}
public async render(document: vscode.Uri, stripFrontmatter: boolean, text: string): Promise<string> {
let offset = 0;
if (stripFrontmatter) {

View File

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

View File

@@ -163,6 +163,7 @@
"\tFROM INFORMATION_SCHEMA.ROUTINES",
"WHERE SPECIFIC_SCHEMA = N'${2:dbo}'",
"\tAND SPECIFIC_NAME = N'${1:StoredProcedureName}'",
"\tAND ROUTINE_TYPE = N'PROCEDURE'"
")",
"DROP PROCEDURE ${2:dbo}.${1:StoredProcedureName}",
"GO",

View File

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

View File

@@ -36,7 +36,7 @@ buffer-alloc-unsafe@^1.1.0:
resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0"
integrity sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==
buffer-alloc@^1.1.0:
buffer-alloc@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/buffer-alloc/-/buffer-alloc-1.2.0.tgz#890dd90d923a873e08e10e5fd51a57e5b7cce0ec"
integrity sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==
@@ -75,19 +75,26 @@ core-util-is@~1.0.0:
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
"dataprotocol-client@github:Microsoft/sqlops-dataprotocolclient#0.2.8":
version "0.2.8"
resolved "https://codeload.github.com/Microsoft/sqlops-dataprotocolclient/tar.gz/3ec3ec3fa63a8dae958c18a85b91fec74c50aec5"
"dataprotocol-client@github:Microsoft/sqlops-dataprotocolclient#0.2.10":
version "0.2.10"
resolved "https://codeload.github.com/Microsoft/sqlops-dataprotocolclient/tar.gz/4de3f7caf0eba54159911b977ddb4f5d7c0a9ca8"
dependencies:
vscode-languageclient "3.5.1"
debug@3.1.0, debug@^3.1.0:
debug@3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
dependencies:
ms "2.0.0"
debug@^3.1.0:
version "3.2.6"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
dependencies:
ms "^2.1.1"
decompress-tar@^4.0.0, decompress-tar@^4.1.0, decompress-tar@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/decompress-tar/-/decompress-tar-4.1.1.tgz#718cbd3fcb16209716e70a26b84e7ba4592e5af1"
@@ -161,9 +168,9 @@ end-of-stream@^1.0.0:
once "^1.4.0"
es6-promise@^4.0.3:
version "4.2.4"
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.4.tgz#dc4221c2b16518760bd8c39a52d8f356fc00ed29"
integrity sha512-/NdNZVJg+uZgtm9eS3O6lrOLYmQag2DjdEXuPaHlZ6RuVqgqaVZfgYCepEIKsLqwdQArOPtC3XzRLqGGfT8KQQ==
version "4.2.5"
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.5.tgz#da6d0d5692efb461e082c14817fe2427d8f5d054"
integrity sha512-n6wvpdE43VFtJq+lUDYDBFUwV8TZbuGXLV4D6wKafg13ldznKsyEvatubnmUe31zcvelSzOHF+XbaT+Bl9ObDg==
es6-promisify@^5.0.0:
version "5.0.0"
@@ -213,9 +220,9 @@ get-stream@^2.2.0:
pinkie-promise "^2.0.0"
graceful-fs@^4.1.10:
version "4.1.11"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658"
integrity sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=
version "4.1.15"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00"
integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==
"graceful-readlink@>= 1.0.0":
version "1.0.1"
@@ -287,6 +294,11 @@ ms@2.0.0:
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
ms@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"
integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==
object-assign@^4.0.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
@@ -300,9 +312,9 @@ once@^1.4.0:
wrappy "1"
opener@^1.4.3:
version "1.4.3"
resolved "https://registry.yarnpkg.com/opener/-/opener-1.4.3.tgz#5c6da2c5d7e5831e8ffa3964950f8d6674ac90b8"
integrity sha1-XG2ixdflgx6P+jlklQ+NZnSskLg=
version "1.5.1"
resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.1.tgz#6d2f0e77f1a0af0032aca716c2c1fbb8e7e8abed"
integrity sha512-goYSy5c2UXE4Ra1xixabeVh1guIX/ZV/YokJksb6q2lubWu6UbvPQ20p542/sFIll1nl8JnCyK9oBaOcCWXwvA==
os-tmpdir@~1.0.2:
version "1.0.2"
@@ -367,9 +379,9 @@ seek-bzip@^1.0.5:
commander "~2.8.1"
semver@^5.3.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab"
integrity sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==
version "5.6.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004"
integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==
"service-downloader@github:anthonydresser/service-downloader#0.1.5":
version "0.1.5"
@@ -397,16 +409,16 @@ strip-dirs@^2.0.0:
is-natural-number "^4.0.1"
tar-stream@^1.5.2:
version "1.6.1"
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.6.1.tgz#f84ef1696269d6223ca48f6e1eeede3f7e81f395"
integrity sha512-IFLM5wp3QrJODQFPm6/to3LJZrONdBY/otxcvDIQzu217zKye6yVR3hhi9lAjrC2Z+m/j5oDxMPb1qcd8cIvpA==
version "1.6.2"
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.6.2.tgz#8ea55dab37972253d9a9af90fdcd559ae435c555"
integrity sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==
dependencies:
bl "^1.0.0"
buffer-alloc "^1.1.0"
buffer-alloc "^1.2.0"
end-of-stream "^1.0.0"
fs-constants "^1.0.0"
readable-stream "^2.3.0"
to-buffer "^1.1.0"
to-buffer "^1.1.1"
xtend "^4.0.0"
through@^2.3.6:
@@ -421,15 +433,15 @@ tmp@^0.0.33:
dependencies:
os-tmpdir "~1.0.2"
to-buffer@^1.1.0:
to-buffer@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/to-buffer/-/to-buffer-1.1.1.tgz#493bd48f62d7c43fcded313a03dcadb2e1213a80"
integrity sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==
unbzip2-stream@^1.0.9:
version "1.2.5"
resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.2.5.tgz#73a033a567bbbde59654b193c44d48a7e4f43c47"
integrity sha512-izD3jxT8xkzwtXRUZjtmRwKnZoeECrfZ8ra/ketwOcusbZEp4mjULMnJOCfTDZBgGQAAY1AJ/IgxcwkavcX9Og==
version "1.3.1"
resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.3.1.tgz#7854da51622a7e63624221196357803b552966a1"
integrity sha512-fIZnvdjblYs7Cru/xC6tCPVhz7JkYcVQQkePwMLyQELzYTds2Xn8QefPVnvdVhhZqubxNA1cASXEH5wcK0Bucw==
dependencies:
buffer "^3.0.1"
through "^2.3.6"

View File

@@ -14,8 +14,8 @@ const localize = nls.loadMessageBundle();
export class CreateSessionDialog {
// Top level
private readonly CancelButtonText: string = localize('createSessionDialog.cancel', 'Cancel');
private readonly CreateButtonText: string = localize('createSessionDialog.create', 'Create');
private readonly DialogTitleText: string = localize('createSessionDialog.title', 'Create New Profiler Session');
private readonly CreateButtonText: string = localize('createSessionDialog.create', 'Start');
private readonly DialogTitleText: string = localize('createSessionDialog.title', 'Start New Profiler Session');
// UI Components
private dialog: sqlops.window.modelviewdialog.Dialog;
@@ -68,6 +68,10 @@ export class CreateSessionDialog {
value: ''
}).component();
this.templatesBox.onValueChanged(() => {
this.updateSessionName();
});
let formModel = view.modelBuilder.formContainer()
.withFormItems([{
components: [{
@@ -86,6 +90,7 @@ export class CreateSessionDialog {
if (this.model.templates) {
this.templatesBox.values = this.model.getTemplateNames();
this.updateSessionName();
}
this.sessionNameBox.onTextChanged(() => {
@@ -99,6 +104,12 @@ export class CreateSessionDialog {
});
}
private updateSessionName() {
if (this.templatesBox.value) {
this.sessionNameBox.value = `ADS_${this.templatesBox.value.toString()}`
}
}
private async execute(): Promise<void> {
let profilerService = sqlops.dataprotocol.getProvider<sqlops.ProfilerProvider>(this._providerType, sqlops.DataProviderType.ProfilerProvider);

View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "azuredatastudio",
"version": "1.3.1",
"version": "1.3.3",
"distro": "8c3e97e3425cc9814496472ab73e076de2ba99ee",
"author": {
"name": "Microsoft Corporation"
@@ -37,8 +37,10 @@
"@angular/router": "~4.1.3",
"@angular/upgrade": "~4.1.3",
"@types/chart.js": "^2.7.31",
"@types/htmlparser2": "^3.7.31",
"angular2-grid": "2.0.6",
"angular2-slickgrid": "github:Microsoft/angular2-slickgrid#1.4.6",
"ansi_up": "^3.0.0",
"applicationinsights": "0.18.0",
"chart.js": "^2.6.0",
"fast-plist": "0.1.2",
@@ -64,8 +66,9 @@
"pretty-data": "^0.40.0",
"reflect-metadata": "^0.1.8",
"rxjs": "5.4.0",
"sanitize-html": "^1.19.1",
"semver": "^5.5.0",
"slickgrid": "github:anthonydresser/SlickGrid#2.3.28",
"slickgrid": "github:anthonydresser/SlickGrid#2.3.29",
"spdlog": "0.7.1",
"sudo-prompt": "8.2.0",
"svg.js": "^2.2.5",
@@ -85,6 +88,7 @@
"@types/keytar": "4.0.1",
"@types/minimist": "1.2.0",
"@types/mocha": "2.2.39",
"@types/sanitize-html": "^1.18.2",
"@types/semver": "5.3.30",
"@types/sinon": "1.16.34",
"@types/winreg": "^1.2.30",
@@ -98,7 +102,7 @@
"documentdb": "^1.5.1",
"electron-mksnapshot": "~1.7.0",
"eslint": "^3.4.0",
"event-stream": "^3.1.7",
"event-stream": "^3.3.4",
"express": "^4.13.1",
"glob": "^5.0.13",
"gulp": "^3.9.1",

View File

@@ -28,8 +28,11 @@ export class RadioButton extends Widget {
super();
this.inputElement = document.createElement('input');
this.inputElement.type = 'radio';
this.inputElement.style.verticalAlign = 'middle';
this.inputElement.style.margin = '3px';
this._label = document.createElement('span');
this._label.style.verticalAlign = 'middle';
this.label = opts.label;
this.enabled = opts.enabled || true;

View File

@@ -0,0 +1,10 @@
.labelOnTopContainer {
display: flex;
flex-direction: column;
}
.labelOnLeftContainer {
display: flex;
flex-direction: row;
margin-right: 5px;
}

View File

@@ -4,6 +4,8 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import 'vs/css!./media/selectBox';
import { SelectBox as vsSelectBox, ISelectBoxStyles as vsISelectBoxStyles, ISelectBoxOptions } from 'vs/base/browser/ui/selectBox/selectBox';
import { Color } from 'vs/base/common/color';
import { IContextViewProvider, AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview';
@@ -30,6 +32,7 @@ export class SelectBox extends vsSelectBox {
private _optionsDictionary;
private _dialogOptions: string[];
private _selectedOption: string;
private _selectBoxOptions: ISelectBoxOptions;
private enabledSelectBackground: Color;
private enabledSelectForeground: Color;
private enabledSelectBorder: Color;
@@ -72,6 +75,7 @@ export class SelectBox extends vsSelectBox {
// explicitly set the accessible role so that the screen readers can read the control type properly
this.selectElement.setAttribute('role', 'combobox');
this._selectBoxOptions = selectBoxOptions;
}
public style(styles: ISelectBoxStyles): void {
@@ -118,6 +122,10 @@ export class SelectBox extends vsSelectBox {
return this._selectedOption;
}
public get values(): string[] {
return this._dialogOptions;
}
public enable(): void {
this.selectElement.disabled = false;
this.selectBackground = this.enabledSelectBackground;
@@ -226,4 +234,34 @@ export class SelectBox extends vsSelectBox {
default: return { border: this.inputValidationErrorBorder, background: this.inputValidationErrorBackground };
}
}
}
public render(container: HTMLElement): void {
let selectOptions: ISelectBoxOptionsWithLabel = this._selectBoxOptions as ISelectBoxOptionsWithLabel;
if (selectOptions && selectOptions.labelText && selectOptions.labelText !== undefined) {
let outerContainer = document.createElement('div');
let selectContainer = document.createElement('div');
outerContainer.className = selectOptions.labelOnTop ? 'labelOnTopContainer' : 'labelOnLeftContainer';
let labelText = document.createElement('div');
labelText.className = 'action-item-label';
labelText.innerHTML = selectOptions.labelText;
container.appendChild(outerContainer);
outerContainer.appendChild(labelText);
outerContainer.appendChild(selectContainer);
super.render(selectContainer);
this.selectElement.classList.add('action-item-label');
}
else {
super.render(container);
}
}
}
export interface ISelectBoxOptionsWithLabel extends ISelectBoxOptions {
labelText?: string;
labelOnTop?: boolean;
}

View File

@@ -9,36 +9,27 @@ export interface IObservableCollection<T> {
getLength(): number;
at(index: number): T;
getRange(start: number, end: number): T[];
setCollectionChangedCallback(callback: (change: CollectionChange, startIndex: number, count: number) => void): void;
setCollectionChangedCallback(callback: (startIndex: number, count: number) => void): void;
setLength(number): void;
dispose(): void;
}
export interface IGridDataRow {
row?: number;
values: any[];
}
export enum CollectionChange {
ItemsReplaced
}
class LoadCancellationToken {
isCancelled: boolean;
}
class DataWindow<TData> {
private _data: TData[];
class DataWindow<T> {
private _data: T[];
private _length: number = 0;
private _offsetFromDataSource: number = -1;
private lastLoadCancellationToken: LoadCancellationToken;
constructor(
private loadFunction: (offset: number, count: number) => Thenable<TData[]>,
private placeholderItemGenerator: (index: number) => TData,
private loadFunction: (offset: number, count: number) => Thenable<T[]>,
private placeholderItemGenerator: (index: number) => T,
private loadCompleteCallback: (start: number, end: number) => void
) {
}
) { }
dispose() {
this._data = undefined;
@@ -50,26 +41,26 @@ class DataWindow<TData> {
}
}
getStartIndex(): number {
public getStartIndex(): number {
return this._offsetFromDataSource;
}
getEndIndex(): number {
public getEndIndex(): number {
return this._offsetFromDataSource + this._length;
}
contains(dataSourceIndex: number): boolean {
public contains(dataSourceIndex: number): boolean {
return dataSourceIndex >= this.getStartIndex() && dataSourceIndex < this.getEndIndex();
}
getItem(index: number): TData {
public getItem(index: number): T {
if (!this._data) {
return this.placeholderItemGenerator(index);
}
return this._data[index - this._offsetFromDataSource];
}
positionWindow(offset: number, length: number): void {
public positionWindow(offset: number, length: number): void {
this._offsetFromDataSource = offset;
this._length = length;
this._data = undefined;
@@ -92,34 +83,28 @@ class DataWindow<TData> {
}
}
export class VirtualizedCollection<TData> implements IObservableCollection<TData> {
export class VirtualizedCollection<T extends Slick.SlickData> implements IObservableCollection<T> {
private _bufferWindowBefore: DataWindow<T>;
private _window: DataWindow<T>;
private _bufferWindowAfter: DataWindow<T>;
private _length: number;
private _windowSize: number;
private _bufferWindowBefore: DataWindow<TData>;
private _window: DataWindow<TData>;
private _bufferWindowAfter: DataWindow<TData>;
private collectionChangedCallback: (change: CollectionChange, startIndex: number, count: number) => void;
private collectionChangedCallback: (startIndex: number, count: number) => void;
constructor(
windowSize: number,
length: number,
loadFn: (offset: number, count: number) => Thenable<TData[]>,
private _placeHolderGenerator: (index: number) => TData
private readonly windowSize: number,
private placeHolderGenerator: (index: number) => T,
private length: number,
loadFn: (offset: number, count: number) => Thenable<T[]>
) {
this._windowSize = windowSize;
this._length = length;
let loadCompleteCallback = (start: number, end: number) => {
if (this.collectionChangedCallback) {
this.collectionChangedCallback(CollectionChange.ItemsReplaced, start, end - start);
this.collectionChangedCallback(start, end - start);
}
};
this._bufferWindowBefore = new DataWindow(loadFn, _placeHolderGenerator, loadCompleteCallback);
this._window = new DataWindow(loadFn, _placeHolderGenerator, loadCompleteCallback);
this._bufferWindowAfter = new DataWindow(loadFn, _placeHolderGenerator, loadCompleteCallback);
this._bufferWindowBefore = new DataWindow(loadFn, placeHolderGenerator, loadCompleteCallback);
this._window = new DataWindow(loadFn, placeHolderGenerator, loadCompleteCallback);
this._bufferWindowAfter = new DataWindow(loadFn, placeHolderGenerator, loadCompleteCallback);
}
dispose() {
@@ -128,19 +113,23 @@ export class VirtualizedCollection<TData> implements IObservableCollection<TData
this._window.dispose();
}
setCollectionChangedCallback(callback: (change: CollectionChange, startIndex: number, count: number) => void): void {
public setCollectionChangedCallback(callback: (startIndex: number, count: number) => void): void {
this.collectionChangedCallback = callback;
}
getLength(): number {
return this._length;
public getLength(): number {
return this.length;
}
at(index: number): TData {
setLength(number: any): void {
this.length = number;
}
public at(index: number): T {
return this.getRange(index, index + 1)[0];
}
getRange(start: number, end: number): TData[] {
public getRange(start: number, end: number): T[] {
// current data may contain placeholders
let currentData = this.getRangeFromCurrent(start, end);
@@ -155,7 +144,7 @@ export class VirtualizedCollection<TData> implements IObservableCollection<TData
this._bufferWindowAfter = this._window;
this._window = this._bufferWindowBefore;
this._bufferWindowBefore = windowToRecycle;
let newWindowOffset = Math.max(0, this._window.getStartIndex() - this._windowSize);
let newWindowOffset = Math.max(0, this._window.getStartIndex() - this.windowSize);
this._bufferWindowBefore.positionWindow(newWindowOffset, this._window.getStartIndex() - newWindowOffset);
} else if (start >= this._bufferWindowAfter.getStartIndex()) {
@@ -164,8 +153,8 @@ export class VirtualizedCollection<TData> implements IObservableCollection<TData
this._bufferWindowBefore = this._window;
this._window = this._bufferWindowAfter;
this._bufferWindowAfter = windowToRecycle;
let newWindowOffset = Math.min(this._window.getStartIndex() + this._windowSize, this._length);
let newWindowLength = Math.min(this._length - newWindowOffset, this._windowSize);
let newWindowOffset = Math.min(this._window.getStartIndex() + this.windowSize, this.length);
let newWindowLength = Math.min(this.length - newWindowOffset, this.windowSize);
this._bufferWindowAfter.positionWindow(newWindowOffset, newWindowLength);
}
@@ -173,7 +162,7 @@ export class VirtualizedCollection<TData> implements IObservableCollection<TData
return currentData;
}
private getRangeFromCurrent(start: number, end: number): TData[] {
private getRangeFromCurrent(start: number, end: number): T[] {
let currentData = [];
for (let i = 0; i < end - start; i++) {
currentData.push(this.getDataFromCurrent(start + i));
@@ -182,7 +171,7 @@ export class VirtualizedCollection<TData> implements IObservableCollection<TData
return currentData;
}
private getDataFromCurrent(index: number): TData {
private getDataFromCurrent(index: number): T {
if (this._bufferWindowBefore.contains(index)) {
return this._bufferWindowBefore.getItem(index);
} else if (this._bufferWindowAfter.contains(index)) {
@@ -191,39 +180,47 @@ export class VirtualizedCollection<TData> implements IObservableCollection<TData
return this._window.getItem(index);
}
return this._placeHolderGenerator(index);
return this.placeHolderGenerator(index);
}
private resetWindowsAroundIndex(index: number): void {
let bufferWindowBeforeStart = Math.max(0, index - this._windowSize * 1.5);
let bufferWindowBeforeEnd = Math.max(0, index - this._windowSize / 2);
let bufferWindowBeforeStart = Math.max(0, index - this.windowSize * 1.5);
let bufferWindowBeforeEnd = Math.max(0, index - this.windowSize / 2);
this._bufferWindowBefore.positionWindow(bufferWindowBeforeStart, bufferWindowBeforeEnd - bufferWindowBeforeStart);
let mainWindowStart = bufferWindowBeforeEnd;
let mainWindowEnd = Math.min(mainWindowStart + this._windowSize, this._length);
let mainWindowEnd = Math.min(mainWindowStart + this.windowSize, this.length);
this._window.positionWindow(mainWindowStart, mainWindowEnd - mainWindowStart);
let bufferWindowAfterStart = mainWindowEnd;
let bufferWindowAfterEnd = Math.min(bufferWindowAfterStart + this._windowSize, this._length);
let bufferWindowAfterEnd = Math.min(bufferWindowAfterStart + this.windowSize, this.length);
this._bufferWindowAfter.positionWindow(bufferWindowAfterStart, bufferWindowAfterEnd - bufferWindowAfterStart);
}
}
export class AsyncDataProvider<TData extends IGridDataRow> implements IDisposableDataProvider<TData> {
export class AsyncDataProvider<T extends Slick.SlickData> implements IDisposableDataProvider<T> {
constructor(private dataRows: IObservableCollection<TData>) { }
constructor(public dataRows: IObservableCollection<T>) { }
public getLength(): number {
return this.dataRows ? this.dataRows.getLength() : 0;
return this.dataRows.getLength();
}
public getItem(index: number): TData {
return !this.dataRows ? undefined : this.dataRows.at(index);
public getItem(index: number): T {
return this.dataRows.at(index);
}
public getRange(start: number, end: number): TData[] {
return !this.dataRows ? undefined : this.dataRows.getRange(start, end);
public getRange(start: number, end: number): T[] {
return this.dataRows.getRange(start, end);
}
public set length(length: number) {
this.dataRows.setLength(length);
}
public get length(): number {
return this.dataRows.getLength();
}
dispose() {

View File

@@ -5,8 +5,6 @@
'use strict';
import { range } from 'vs/base/common/arrays';
export interface IRowNumberColumnOptions {
numberOfRows: number;
cssClass?: string;
@@ -17,7 +15,7 @@ const sizePerDigit = 15;
export class RowNumberColumn<T> implements Slick.Plugin<T> {
private handler = new Slick.EventHandler();
private grid: Slick.Grid<T>;
private currentColumnWidth: number;
constructor(private options: IRowNumberColumnOptions) {
}
@@ -52,19 +50,25 @@ export class RowNumberColumn<T> implements Slick.Plugin<T> {
}
}
public updateRowCount(rowNum: number) {
this.options.numberOfRows = rowNum;
let columnWidth = Math.max(this.options.numberOfRows.toString().length * sizePerDigit, 22);
if (columnWidth !== this.currentColumnWidth) {
this.grid.setColumnWidths([this.getColumnDefinition()]);
}
}
public getColumnDefinition(): Slick.Column<T> {
// that smallest we can make it is 22 due to padding and margins in the cells
let columnWidth = Math.max(this.options.numberOfRows.toString().length * sizePerDigit, 22);
this.currentColumnWidth = Math.max(this.options.numberOfRows.toString().length * sizePerDigit, 22);
return {
id: 'rowNumber',
name: '',
field: 'rowNumber',
width: columnWidth,
minWidth: columnWidth,
maxWidth: columnWidth,
width: this.currentColumnWidth,
resizable: false,
cssClass: this.options.cssClass,
focusable: false,
focusable: true,
selectable: false,
formatter: (r, c, v, cd, dc) => this.formatter(r, c, v, cd, dc)
};

View File

@@ -19,11 +19,12 @@
.carbon-taskbar {
width: 100%;
position: relative;
margin: 2px 0px 2px 0px;
}
.carbon-taskbar.monaco-toolbar .monaco-action-bar.animated .actions-container {
justify-content: flex-start;
padding: 5px 5px 5px 15px;
padding-left: 15px;
flex-wrap: wrap;
}
@@ -39,11 +40,6 @@
align-items: center;
}
.carbon-taskbar {
margin-top: 2px;
margin-bottom: 2px;
}
.carbon-taskbar .action-item {
margin-right: 5px;
}
@@ -54,6 +50,11 @@
padding-left: 15px;
}
.carbon-taskbar .action-item-label {
display: flex;
padding-right: 5px;
}
.listDatabasesSelectBox {
padding-left: 2px;
min-width: 100px;

View File

@@ -146,4 +146,5 @@ export class Taskbar {
public dispose(): void {
this.actionBar.dispose();
}
}

View File

@@ -3,15 +3,22 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as path from 'path';
import { Registry } from 'vs/platform/registry/common/platform';
import { EditorInput, IEditorInput } from 'vs/workbench/common/editor';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
import { FileEditorInput } from 'vs/workbench/parts/files/common/editors/fileEditorInput';
import URI from 'vs/base/common/uri';
import { QueryResultsInput } from 'sql/parts/query/common/queryResultsInput';
import { QueryInput } from 'sql/parts/query/common/queryInput';
import URI from 'vs/base/common/uri';
import { IQueryEditorOptions } from 'sql/parts/query/common/queryEditorService';
import { QueryPlanInput } from 'sql/parts/queryPlan/queryPlanInput';
import { NotebookInput, NotebookInputModel, NotebookInputValidator } from 'sql/parts/notebook/notebookInput';
import { Extensions, INotebookProviderRegistry } from 'sql/services/notebook/notebookRegistry';
import { DEFAULT_NOTEBOOK_PROVIDER } from 'sql/services/notebook/notebookService';
const fs = require('fs');
@@ -28,7 +35,7 @@ export const sqlModeId = 'sql';
* to that type.
* @param input The input to check for conversion
* @param options Editor options for controlling the conversion
* @param instantiationService The instatianation service to use to create the new input types
* @param instantiationService The instantiation service to use to create the new input types
*/
export function convertEditorInput(input: EditorInput, options: IQueryEditorOptions, instantiationService: IInstantiationService): EditorInput {
let denyQueryEditor = options && options.denyQueryEditor;
@@ -48,8 +55,25 @@ export function convertEditorInput(input: EditorInput, options: IQueryEditorOpti
let queryPlanInput: QueryPlanInput = instantiationService.createInstance(QueryPlanInput, queryPlanXml, 'aaa', undefined);
return queryPlanInput;
}
}
//Notebook
let notebookValidator = instantiationService.createInstance(NotebookInputValidator);
uri = getNotebookEditorUri(input);
if(uri && notebookValidator.isNotebookEnabled()){
//TODO: We need to pass in notebook data either through notebook input or notebook service
let fileName: string = 'untitled';
let providerId: string = DEFAULT_NOTEBOOK_PROVIDER;
if (input) {
fileName = input.getName();
providerId = getProviderForFileName(fileName);
}
let notebookInputModel = new NotebookInputModel(uri, undefined, false, undefined);
notebookInputModel.providerId = providerId;
//TO DO: Second parameter has to be the content.
let notebookInput: NotebookInput = instantiationService.createInstance(NotebookInput, fileName, notebookInputModel);
return notebookInput;
}
}
return input;
}
@@ -129,6 +153,47 @@ function getQueryPlanEditorUri(input: EditorInput): URI {
return undefined;
}
/**
* If input is a supported notebook editor file (.ipynb), return it's URI. Otherwise return undefined.
* @param input The EditorInput to get the URI of.
*/
function getNotebookEditorUri(input: EditorInput): URI {
if (!input || !input.getName()) {
return undefined;
}
// If this editor is not already of type notebook input
if (!(input instanceof NotebookInput)) {
let uri: URI = getSupportedInputResource(input);
if (uri) {
if (hasFileExtension(getNotebookFileExtensions(), input, false)) {
return uri;
}
}
}
return undefined;
}
function getNotebookFileExtensions() {
let notebookRegistry = Registry.as<INotebookProviderRegistry>(Extensions.NotebookProviderContribution);
return notebookRegistry.getSupportedFileExtensions();
}
function getProviderForFileName(fileName: string) {
let fileExt = path.extname(fileName);
if (fileExt && fileExt.startsWith('.')) {
fileExt = fileExt.slice(1,fileExt.length);
let notebookRegistry = Registry.as<INotebookProviderRegistry>(Extensions.NotebookProviderContribution);
return notebookRegistry.getProviderForFileType(fileExt);
}
return DEFAULT_NOTEBOOK_PROVIDER;
}
/**
* Checks whether the given EditorInput is set to either undefined or sql mode
* @param input The EditorInput to check the mode of

View File

@@ -83,6 +83,8 @@ export class ConnectionDialogWidget extends Modal {
private _onResetConnection = new Emitter<void>();
public onResetConnection: Event<void> = this._onResetConnection.event;
private _connecting = false;
constructor(
private providerTypeOptions: string[],
private selectedProviderType: string,
@@ -248,6 +250,7 @@ export class ConnectionDialogWidget extends Modal {
private connect(element?: IConnectionProfile): void {
if (this._connectButton.enabled) {
this._connecting = true;
this._connectButton.enabled = false;
this._providerTypeSelectBox.disable();
this.showSpinner();
@@ -268,8 +271,9 @@ export class ConnectionDialogWidget extends Modal {
}
private cancel() {
let wasConnecting = this._connecting;
this._onCancel.fire();
if (!this._databaseDropdownExpanded) {
if (!this._databaseDropdownExpanded && !wasConnecting) {
this.close();
}
}
@@ -426,6 +430,7 @@ export class ConnectionDialogWidget extends Modal {
this._connectButton.enabled = true;
this._providerTypeSelectBox.enable();
this._onResetConnection.fire();
this._connecting = false;
}
public get newConnectionParams(): INewConnectionParams {

View File

@@ -3,7 +3,6 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { $ } from 'vs/base/browser/dom';
import { escape } from 'sql/base/common/strings';
export class DBCellValue {
@@ -41,20 +40,24 @@ export function hyperLinkFormatter(row: number, cell: any, value: any, columnDef
*/
export function textFormatter(row: number, cell: any, value: any, columnDef: any, dataContext: any): string {
let cellClasses = 'grid-cell-value-container';
let valueToDisplay: string = '';
let valueToDisplay = '';
let titleValue = '';
if (DBCellValue.isDBCellValue(value)) {
valueToDisplay = 'NULL';
if (!value.isNull) {
valueToDisplay = escape(value.displayValue.replace(/(\r\n|\n|\r)/g, ' '));
valueToDisplay = value.displayValue.replace(/(\r\n|\n|\r)/g, ' ');
valueToDisplay = escape(valueToDisplay.length > 250 ? valueToDisplay.slice(0, 250) + '...' : valueToDisplay);
titleValue = value.displayValue;
} else {
cellClasses += ' missing-value';
}
} else if (typeof value === 'string') {
valueToDisplay = escape(value);
valueToDisplay = escape(value.length > 250 ? value.slice(0, 250) + '...' : value);
titleValue = value;
}
return `<span title="${valueToDisplay}" class="${cellClasses}">${valueToDisplay}</span>`;
return `<span title="${titleValue}" class="${cellClasses}">${valueToDisplay}</span>`;
}
/** The following code is a rewrite over the both formatter function using dom builder

View File

@@ -33,7 +33,7 @@ export class JobManagementUtilities {
case(4): return nls.localize('agentUtilities.idle', 'Idle');
case(5): return nls.localize('agentUtilities.suspended', 'Suspended');
case(6): return nls.localize('agentUtilities.obsolete', '[Obsolete]');
case(7): return nls.localize('agentUtilities.performingCompletionActions', 'PerformingCompletionActions');
case(7): return 'PerformingCompletionActions';
default: return nls.localize('agentUtilities.statusUnknown', 'Status Unknown');
}
}

View File

@@ -304,15 +304,15 @@ table.jobprevruns > tbody {
background-image: url('refresh_inverse.svg');
}
.actionbar-container .monaco-action-bar > ul.actions-container {
.agent-actionbar-container .monaco-action-bar > ul.actions-container {
padding-top: 10px;
}
jobsview-component .actionbar-container {
jobsview-component .agent-actionbar-container {
height: 40px;
}
.actionbar-container .monaco-action-bar > ul.actions-container > li.action-item {
.agent-actionbar-container .monaco-action-bar > ul.actions-container > li.action-item {
padding-left: 20px;
}

View File

@@ -10,6 +10,6 @@
<div class="icon in-progress" *ngIf="_showProgressWheel === true"></div>
</div>
<div #actionbarContainer class="actionbar-container"></div>
<div #actionbarContainer class="agent-actionbar-container"></div>
<div #jobalertsgrid class="jobalertsview-grid"></div>

View File

@@ -15,7 +15,7 @@ import 'vs/css!sql/base/browser/ui/table/media/table';
import * as dom from 'vs/base/browser/dom';
import * as nls from 'vs/nls';
import * as sqlops from 'sqlops';
import { Component, Inject, forwardRef, ElementRef, ChangeDetectorRef, ViewChild, OnInit } from '@angular/core';
import { Component, Inject, forwardRef, ElementRef, ChangeDetectorRef, ViewChild, OnInit, OnDestroy } from '@angular/core';
import { TabChild } from 'sql/base/browser/ui/panel/tab.component';
import { Table } from 'sql/base/browser/ui/table/table';
import { AgentViewComponent } from 'sql/parts/jobManagement/agent/agentView.component';
@@ -41,7 +41,7 @@ export const ROW_HEIGHT: number = 45;
templateUrl: decodeURI(require.toUrl('./alertsView.component.html')),
providers: [{ provide: TabChild, useExisting: forwardRef(() => AlertsViewComponent) }],
})
export class AlertsViewComponent extends JobManagementView implements OnInit {
export class AlertsViewComponent extends JobManagementView implements OnInit, OnDestroy {
private columns: Array<Slick.Column<any>> = [
{
@@ -69,6 +69,7 @@ export class AlertsViewComponent extends JobManagementView implements OnInit {
private _isCloud: boolean;
private _alertsCacheObject: AlertsCacheObject;
private _didTabChange: boolean;
@ViewChild('jobalertsgrid') _gridEl: ElementRef;
public alerts: sqlops.AgentAlertInfo[];
@@ -86,6 +87,7 @@ export class AlertsViewComponent extends JobManagementView implements OnInit {
@Inject(IKeybindingService) keybindingService: IKeybindingService,
@Inject(IDashboardService) _dashboardService: IDashboardService) {
super(commonService, _dashboardService, contextMenuService, keybindingService, instantiationService);
this._didTabChange = false;
this._isCloud = commonService.connectionManagementService.connectionInfo.serverInfo.isCloud;
let alertsCacheObjectMap = this._jobManagementService.alertsCacheObjectMap;
let alertsCache = alertsCacheObjectMap[this._serverName];
@@ -102,7 +104,11 @@ export class AlertsViewComponent extends JobManagementView implements OnInit {
// set base class elements
this._visibilityElement = this._gridEl;
this._parentComponent = this._agentViewComponent;
}
}
ngOnDestroy() {
this._didTabChange = true;
}
public layout() {
let height = dom.getContentHeight(this._gridEl.nativeElement) - 10;
@@ -166,8 +172,10 @@ export class AlertsViewComponent extends JobManagementView implements OnInit {
// TODO: handle error
}
this._showProgressWheel = false;
if (this.isVisible) {
if (this.isVisible && !this._didTabChange) {
this._cd.detectChanges();
} else if (this._didTabChange) {
return;
}
});
}

View File

@@ -15,7 +15,7 @@
</div>
<!-- Actions -->
<div #actionbarContainer class="actionbar-container"></div>
<div #actionbarContainer class="agent-actionbar-container"></div>
<!-- Overview -->
<div class="overview-container">

View File

@@ -257,14 +257,14 @@ jobhistory-component > .jobhistory-heading-container > .icon.in-progress {
padding-left: 20px;
}
jobhistory-component > .actionbar-container .monaco-action-bar > ul.actions-container {
jobhistory-component > .agent-actionbar-container .monaco-action-bar > ul.actions-container {
border-top: 3px solid #f4f4f4;
}
.vs-dark jobhistory-component > .actionbar-container .monaco-action-bar > ul.actions-container {
.vs-dark jobhistory-component > .agent-actionbar-container .monaco-action-bar > ul.actions-container {
border-top: 3px solid #444444;
}
.hc-black jobhistory-component > .actionbar-container .monaco-action-bar > ul.actions-container {
.hc-black jobhistory-component > .agent-actionbar-container .monaco-action-bar > ul.actions-container {
border-top: 3px solid #2b56f2;
}

View File

@@ -10,6 +10,6 @@
<div class="icon in-progress" *ngIf="_showProgressWheel === true"></div>
</div>
<div #actionbarContainer class="actionbar-container"></div>
<div #actionbarContainer class="agent-actionbar-container"></div>
<div #jobsgrid class="jobview-grid"></div>

View File

@@ -15,7 +15,7 @@ import 'vs/css!sql/base/browser/ui/table/media/table';
import * as sqlops from 'sqlops';
import * as nls from 'vs/nls';
import * as dom from 'vs/base/browser/dom';
import { Component, Inject, forwardRef, ElementRef, ChangeDetectorRef, ViewChild, OnInit } from '@angular/core';
import { Component, Inject, forwardRef, ElementRef, ChangeDetectorRef, ViewChild, OnInit, OnDestroy } from '@angular/core';
import { TabChild } from 'sql/base/browser/ui/panel/tab.component';
import { Table } from 'sql/base/browser/ui/table/table';
import { AgentViewComponent } from 'sql/parts/jobManagement/agent/agentView.component';
@@ -47,7 +47,7 @@ export const ROW_HEIGHT: number = 45;
providers: [{ provide: TabChild, useExisting: forwardRef(() => JobsViewComponent) }],
})
export class JobsViewComponent extends JobManagementView implements OnInit {
export class JobsViewComponent extends JobManagementView implements OnInit, OnDestroy {
private columns: Array<Slick.Column<any>> = [
{
@@ -91,6 +91,8 @@ export class JobsViewComponent extends JobManagementView implements OnInit {
private jobSchedules: { [jobId: string]: sqlops.AgentJobScheduleInfo[]; } = Object.create(null);
public contextAction = NewJobAction;
private _didTabChange: boolean;
@ViewChild('jobsgrid') _gridEl: ElementRef;
constructor(
@@ -107,6 +109,7 @@ export class JobsViewComponent extends JobManagementView implements OnInit {
@Inject(IDashboardService) _dashboardService: IDashboardService
) {
super(commonService, _dashboardService, contextMenuService, keybindingService, instantiationService);
this._didTabChange = false;
let jobCacheObjectMap = this._jobManagementService.jobCacheObjectMap;
let jobCache = jobCacheObjectMap[this._serverName];
if (jobCache) {
@@ -126,8 +129,12 @@ export class JobsViewComponent extends JobManagementView implements OnInit {
this._register(this._themeService.onDidColorThemeChange(e => this.updateTheme(e)));
}
ngOnDestroy() {
this._didTabChange = true;
}
public layout() {
let jobsViewToolbar = $('jobsview-component .actionbar-container').get(0);
let jobsViewToolbar = $('jobsview-component .agent-actionbar-container').get(0);
let statusBar = $('.part.statusbar').get(0);
if (jobsViewToolbar && statusBar) {
let toolbarBottom = jobsViewToolbar.getBoundingClientRect().bottom;
@@ -208,8 +215,10 @@ export class JobsViewComponent extends JobManagementView implements OnInit {
}
this._showProgressWheel = false;
if (this.isVisible) {
if (this.isVisible && !this._didTabChange) {
this._cd.detectChanges();
} else if (this._didTabChange) {
return;
}
});
}
@@ -579,7 +588,7 @@ export class JobsViewComponent extends JobManagementView implements OnInit {
private async curateJobHistory(jobs: sqlops.AgentJobInfo[], ownerUri: string) {
const self = this;
jobs.forEach(async (job) => {
await this._jobManagementService.getJobHistory(ownerUri, job.jobId, job.name).then((result) => {
await this._jobManagementService.getJobHistory(ownerUri, job.jobId, job.name).then(async(result) => {
if (result) {
self.jobSteps[job.jobId] = result.steps ? result.steps : [];
self.jobAlerts[job.jobId] = result.alerts ? result.alerts : [];
@@ -594,7 +603,10 @@ export class JobsViewComponent extends JobManagementView implements OnInit {
} else {
previousRuns = jobHistories;
}
self.createJobChart(job.jobId, previousRuns);
// dont create the charts if the tab changed
if (!self._didTabChange) {
self.createJobChart(job.jobId, previousRuns);
}
if (self._agentViewComponent.expanded.has(job.jobId)) {
let lastJobHistory = jobHistories[jobHistories.length - 1];
let item = self.dataView.getItemById(job.jobId + '.error');

View File

@@ -10,6 +10,6 @@
<div class="icon in-progress" *ngIf="_showProgressWheel === true"></div>
</div>
<div #actionbarContainer class="actionbar-container"></div>
<div #actionbarContainer class="agent-actionbar-container"></div>
<div #operatorsgrid class="joboperatorsview-grid"></div>

View File

@@ -15,7 +15,7 @@ import 'vs/css!sql/base/browser/ui/table/media/table';
import * as dom from 'vs/base/browser/dom';
import * as nls from 'vs/nls';
import * as sqlops from 'sqlops';
import { Component, Inject, forwardRef, ElementRef, ChangeDetectorRef, ViewChild, OnInit } from '@angular/core';
import { Component, Inject, forwardRef, ElementRef, ChangeDetectorRef, ViewChild, OnInit, OnDestroy } from '@angular/core';
import { Table } from 'sql/base/browser/ui/table/table';
import { AgentViewComponent } from 'sql/parts/jobManagement/agent/agentView.component';
import { IJobManagementService } from 'sql/parts/jobManagement/common/interfaces';
@@ -42,7 +42,7 @@ export const ROW_HEIGHT: number = 45;
providers: [{ provide: TabChild, useExisting: forwardRef(() => OperatorsViewComponent) }],
})
export class OperatorsViewComponent extends JobManagementView implements OnInit {
export class OperatorsViewComponent extends JobManagementView implements OnInit, OnDestroy {
private columns: Array<Slick.Column<any>> = [
{
@@ -68,6 +68,7 @@ export class OperatorsViewComponent extends JobManagementView implements OnInit
private _isCloud: boolean;
private _operatorsCacheObject: OperatorsCacheObject;
private _didTabChange: boolean;
@ViewChild('operatorsgrid') _gridEl: ElementRef;
public operators: sqlops.AgentOperatorInfo[];
@@ -104,6 +105,10 @@ export class OperatorsViewComponent extends JobManagementView implements OnInit
this._parentComponent = this._agentViewComponent;
}
ngOnDestroy() {
this._didTabChange = true;
}
public layout() {
let height = dom.getContentHeight(this._gridEl.nativeElement) - 10;
if (height < 0) {
@@ -169,8 +174,10 @@ export class OperatorsViewComponent extends JobManagementView implements OnInit
// TODO: handle error
}
this._showProgressWheel = false;
if (this.isVisible) {
if (this.isVisible && !this._didTabChange) {
this._cd.detectChanges();
} else if (this._didTabChange) {
return;
}
});
}

View File

@@ -10,6 +10,6 @@
<div class="icon in-progress" *ngIf="_showProgressWheel === true"></div>
</div>
<div #actionbarContainer class="actionbar-container"></div>
<div #actionbarContainer class="agent-actionbar-container"></div>
<div #proxiesgrid class="jobproxiesview-grid"></div>

View File

@@ -15,7 +15,7 @@ import 'vs/css!sql/base/browser/ui/table/media/table';
import * as dom from 'vs/base/browser/dom';
import * as sqlops from 'sqlops';
import * as nls from 'vs/nls';
import { Component, Inject, forwardRef, ElementRef, ChangeDetectorRef, ViewChild, OnInit } from '@angular/core';
import { Component, Inject, forwardRef, ElementRef, ChangeDetectorRef, ViewChild, OnInit, OnDestroy } from '@angular/core';
import { Table } from 'sql/base/browser/ui/table/table';
import { AgentViewComponent } from 'sql/parts/jobManagement/agent/agentView.component';
import { IJobManagementService } from 'sql/parts/jobManagement/common/interfaces';
@@ -42,7 +42,7 @@ export const ROW_HEIGHT: number = 45;
providers: [{ provide: TabChild, useExisting: forwardRef(() => ProxiesViewComponent) }],
})
export class ProxiesViewComponent extends JobManagementView implements OnInit {
export class ProxiesViewComponent extends JobManagementView implements OnInit, OnDestroy {
private NewProxyText: string = nls.localize('jobProxyToolbar-NewItem', "New Proxy");
private RefreshText: string = nls.localize('jobProxyToolbar-Refresh', "Refresh");
@@ -75,6 +75,7 @@ export class ProxiesViewComponent extends JobManagementView implements OnInit {
public proxies: sqlops.AgentProxyInfo[];
public readonly contextAction = NewProxyAction;
private _didTabChange: boolean;
@ViewChild('proxiesgrid') _gridEl: ElementRef;
constructor(
@@ -108,6 +109,10 @@ export class ProxiesViewComponent extends JobManagementView implements OnInit {
this._parentComponent = this._agentViewComponent;
}
ngOnDestroy() {
this._didTabChange = true;
}
public layout() {
let height = dom.getContentHeight(this._gridEl.nativeElement) - 10;
if (height < 0) {
@@ -172,8 +177,10 @@ export class ProxiesViewComponent extends JobManagementView implements OnInit {
// TODO: handle error
}
this._showProgressWheel = false;
if (this.isVisible) {
if (this.isVisible && !this._didTabChange) {
this._cd.detectChanges();
} else if (this._didTabChange) {
return;
}
});
}

View File

@@ -5,7 +5,7 @@
import 'vs/css!./loadingComponent';
import {
Component, Input, Inject, ChangeDetectorRef, forwardRef, OnDestroy, AfterViewInit, ViewChild, ElementRef
Component, Input, Inject, ChangeDetectorRef, forwardRef, OnDestroy, AfterViewInit, ElementRef
} from '@angular/core';
import * as sqlops from 'sqlops';
@@ -31,9 +31,6 @@ export default class LoadingComponent extends ComponentBase implements IComponen
@Input() descriptor: IComponentDescriptor;
@Input() modelStore: IModelStore;
@ViewChild('spinnerElement', { read: ElementRef }) private _spinnerElement: ElementRef;
@ViewChild('childElement', { read: ElementRef }) private _childElement: ElementRef;
constructor(
@Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef,
@Inject(forwardRef(() => ElementRef)) el: ElementRef) {

View File

@@ -0,0 +1,30 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./loadingComponent';
import {
Component, Input, Inject, ChangeDetectorRef, forwardRef, ElementRef
} from '@angular/core';
import * as nls from 'vs/nls';
@Component({
selector: 'loading-spinner',
template: `
<div class="modelview-loadingComponent-container" *ngIf="loading">
<div class="modelview-loadingComponent-spinner" *ngIf="loading" [title]=_loadingTitle #spinnerElement></div>
</div>
`
})
export default class LoadingSpinner {
private readonly _loadingTitle = nls.localize('loadingMessage', 'Loading');
@Input() loading: boolean;
constructor(
@Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef,
@Inject(forwardRef(() => ElementRef)) el: ElementRef) {
}
}

View File

@@ -70,6 +70,9 @@ export class QueryTextEditor extends BaseTextEditor {
options.minimap = {
enabled: false
};
options.overviewRulerLanes = 0;
options.overviewRulerBorder = false;
options.hideCursorInOverviewRuler = true;
}
return options;
}

View File

@@ -0,0 +1,14 @@
<!--
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-->
<div style="overflow: hidden; width: 100%; height: 100%; display: flex; flex-flow: row">
<div #toolbar class="toolbar" style="flex: 0 0 auto; display: flex; flex-flow:column; width: 40px; min-height: 40px; padding-top: 10px; orientation: portrait">
</div>
<div #editor class="editor" style="flex: 1 1 auto; overflow: hidden;">
</div>
<div #moreactions class="toolbar" style="flex: 0 0 auto; display: flex; flex-flow:column; width: 20px; min-height: 20px; max-height: 20px; padding-top: 10px; orientation: portrait">
</div>
</div>

View File

@@ -0,0 +1,187 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./code';
import { OnInit, Component, Input, Inject, forwardRef, ElementRef, ChangeDetectorRef, OnDestroy, ViewChild, Output, EventEmitter } from '@angular/core';
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
import { AngularDisposable } from 'sql/base/common/lifecycle';
import { QueryTextEditor } from 'sql/parts/modelComponents/queryTextEditor';
import { IColorTheme, IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
import * as themeColors from 'vs/workbench/common/theme';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { SimpleProgressService } from 'vs/editor/standalone/browser/simpleServices';
import { IProgressService } from 'vs/platform/progress/common/progress';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ITextModel } from 'vs/editor/common/model';
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
import URI from 'vs/base/common/uri';
import { localize } from 'vs/nls';
import { Action } from 'vs/base/common/actions';
import { ActionBar, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
import { Schemas } from 'vs/base/common/network';
import * as DOM from 'vs/base/browser/dom';
import { IModeService } from 'vs/editor/common/services/modeService';
import { IModelService } from 'vs/editor/common/services/modelService';
import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { ICellModel } from 'sql/parts/notebook/models/modelInterfaces';
import { Taskbar } from 'sql/base/browser/ui/taskbar/taskbar';
import { RunCellAction, DeleteCellAction, AddCellAction, CellContext } from 'sql/parts/notebook/cellViews/codeActions';
import { NotebookModel } from 'sql/parts/notebook/models/notebookModel';
import { ToggleMoreWidgetAction } from 'sql/parts/dashboard/common/actions';
import { CellTypes } from 'sql/parts/notebook/models/contracts';
import { INotificationService } from 'vs/platform/notification/common/notification';
export const CODE_SELECTOR: string = 'code-component';
@Component({
selector: CODE_SELECTOR,
templateUrl: decodeURI(require.toUrl('./code.component.html'))
})
export class CodeComponent extends AngularDisposable implements OnInit {
@ViewChild('toolbar', { read: ElementRef }) private toolbarElement: ElementRef;
@ViewChild('moreactions', { read: ElementRef }) private moreactionsElement: ElementRef;
@ViewChild('editor', { read: ElementRef }) private codeElement: ElementRef;
@Input() cellModel: ICellModel;
@Output() public onContentChanged = new EventEmitter<void>();
@Input() set model(value: NotebookModel) {
this._model = value;
}
protected _actionBar: Taskbar;
protected _moreActions: ActionBar;
private readonly _minimumHeight = 30;
private _editor: QueryTextEditor;
private _editorInput: UntitledEditorInput;
private _editorModel: ITextModel;
private _uri: string;
private _model: NotebookModel;
constructor(
@Inject(forwardRef(() => CommonServiceInterface)) private _bootstrapService: CommonServiceInterface,
@Inject(forwardRef(() => ChangeDetectorRef)) private _changeRef: ChangeDetectorRef,
@Inject(IWorkbenchThemeService) private themeService: IWorkbenchThemeService,
@Inject(IInstantiationService) private _instantiationService: IInstantiationService,
@Inject(IModelService) private _modelService: IModelService,
@Inject(IModeService) private _modeService: IModeService,
@Inject(IContextMenuService) private contextMenuService: IContextMenuService,
@Inject(IContextViewService) private contextViewService: IContextViewService,
@Inject(INotificationService) private notificationService: INotificationService,
) {
super();
}
ngOnInit() {
this._register(this.themeService.onDidColorThemeChange(this.updateTheme, this));
this.updateTheme(this.themeService.getColorTheme());
this.initActionBar();
}
ngOnChanges() {
this.updateLanguageMode();
this.updateModel();
}
ngAfterContentInit(): void {
this.createEditor();
this._register(DOM.addDisposableListener(window, DOM.EventType.RESIZE, e => {
this.layout();
}));
}
get model(): NotebookModel {
return this._model;
}
private createEditor(): void {
let instantiationService = this._instantiationService.createChild(new ServiceCollection([IProgressService, new SimpleProgressService()]));
this._editor = instantiationService.createInstance(QueryTextEditor);
this._editor.create(this.codeElement.nativeElement);
this._editor.setVisible(true);
this._editor.setMinimumHeight(this._minimumHeight);
let uri = this.createUri();
this._editorInput = instantiationService.createInstance(UntitledEditorInput, uri, false, this.cellModel.language, '', '');
this._editor.setInput(this._editorInput, undefined);
this._editorInput.resolve().then(model => {
this._editorModel = model.textEditorModel;
this._modelService.updateModel(this._editorModel, this.cellModel.source);
});
this._register(this._editor);
this._register(this._editorInput);
this._register(this._editorModel.onDidChangeContent(e => {
this._editor.setHeightToScrollHeight();
this.cellModel.source = this._editorModel.getValue();
this.onContentChanged.emit();
}));
this.layout();
}
public layout(): void {
this._editor.layout(new DOM.Dimension(
DOM.getContentWidth(this.codeElement.nativeElement),
DOM.getContentHeight(this.codeElement.nativeElement)));
this._editor.setHeightToScrollHeight();
}
protected initActionBar() {
let context = new CellContext(this.model, this.cellModel);
let runCellAction = this._instantiationService.createInstance(RunCellAction);
let taskbar = <HTMLElement>this.toolbarElement.nativeElement;
this._actionBar = new Taskbar(taskbar, this.contextMenuService);
this._actionBar.context = context;
this._actionBar.setContent([
{ action: runCellAction }
]);
let moreActionsElement = <HTMLElement>this.moreactionsElement.nativeElement;
this._moreActions = new ActionBar(moreActionsElement, { orientation: ActionsOrientation.VERTICAL });
this._moreActions.context = { target: moreActionsElement };
let actions: Action[] = [];
actions.push(this._instantiationService.createInstance(AddCellAction, 'codeBefore', localize('codeBefore', 'Insert Code before'), CellTypes.Code, false));
actions.push(this._instantiationService.createInstance(AddCellAction, 'codeAfter', localize('codeAfter', 'Insert Code after'), CellTypes.Code, true));
actions.push(this._instantiationService.createInstance(AddCellAction, 'markdownBefore', localize('markdownBefore', 'Insert Markdown before'), CellTypes.Markdown, false));
actions.push(this._instantiationService.createInstance(AddCellAction, 'markdownAfter', localize('markdownAfter', 'Insert Markdown after'), CellTypes.Markdown, true));
actions.push(this._instantiationService.createInstance(DeleteCellAction, 'delete', localize('delete', 'Delete')));
this._moreActions.push(this._instantiationService.createInstance(ToggleMoreWidgetAction, actions, context), { icon: true, label: false });
}
private createUri(): URI {
let uri = URI.from({ scheme: Schemas.untitled, path: `notebook-editor-${this.cellModel.id}` });
// Use this to set the internal (immutable) and public (shared with extension) uri properties
this.cellModel.cellUri = uri;
return uri;
}
/// Editor Functions
private updateModel() {
if (this._editorModel) {
this._modelService.updateModel(this._editorModel, this.cellModel.source);
}
}
private updateLanguageMode() {
if (this._editorModel && this._editor) {
this._modeService.getOrCreateMode(this.cellModel.language).then((modeValue) => {
this._modelService.setMode(this._editorModel, modeValue);
});
}
}
private updateTheme(theme: IColorTheme): void {
let toolbarEl = <HTMLElement>this.toolbarElement.nativeElement;
toolbarEl.style.borderRightColor = theme.getColor(themeColors.SIDE_BAR_BACKGROUND, true).toString();
let moreactionsEl = <HTMLElement>this.moreactionsElement.nativeElement;
moreactionsEl.style.borderRightColor = theme.getColor(themeColors.SIDE_BAR_BACKGROUND, true).toString();
}
}

View File

@@ -0,0 +1,57 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
code-component {
height: 100%;
width: 100%;
display: block;
}
code-component .toolbar {
border-right-width: 1px;
border-right-style: solid;
}
code-component .toolbarIconRun {
height: 20px;
background-image: url('../media/light/execute_cell.svg');
padding-bottom: 10px;
}
.vs-dark code-component .toolbarIconRun,
.hc-black code-component .toolbarIconRun {
background-image: url('../media/dark/execute_cell_inverse.svg');
}
code-component .toolbarIconStop {
height: 20px;
background-image: url('../media/light/stop_cell.svg');
padding-bottom: 10px;
}
.vs-dark code-component .toolbarIconStop,
.hc-black code-component .toolbarIconStop {
background-image: url('../media/dark/stop_cell_inverse.svg');
}
/* overview ruler */
code-component .monaco-editor .decorationsOverviewRuler {
visibility: hidden;
}
code-component .carbon-taskbar .icon {
background-size: 20px;
width: 40px;
}
code-component .action-label.icon.toggle-more {
height: 20px;
width: 20px;
}
code-component .carbon-taskbar.monaco-toolbar .monaco-action-bar.animated .actions-container
{
padding-left: 10px
}

View File

@@ -0,0 +1,187 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { nb } from 'sqlops';
import { Action } from 'vs/base/common/actions';
import { TPromise } from 'vs/base/common/winjs.base';
import { localize } from 'vs/nls';
import { CellType } from 'sql/parts/notebook/models/contracts';
import { NotebookModel } from 'sql/parts/notebook/models/notebookModel';
import { getErrorMessage } from 'sql/parts/notebook/notebookUtils';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { ICellModel, FutureInternal } from 'sql/parts/notebook/models/modelInterfaces';
import { ToggleableAction } from 'sql/parts/notebook/notebookActions';
let notebookMoreActionMsg = localize('notebook.failed', "Please select active cell and try again");
function hasModelAndCell(context: CellContext, notificationService: INotificationService): boolean {
if (!context || !context.model) {
return false;
}
if (context.cell === undefined) {
notificationService.notify({
severity: Severity.Error,
message: notebookMoreActionMsg
});
return false;
}
return true;
}
export class CellContext {
constructor(public model: NotebookModel, private _cell?: ICellModel) {
}
public get cell(): ICellModel {
return this._cell ? this._cell : this.model.activeCell;
}
}
abstract class CellActionBase extends Action {
constructor(id: string, label: string, icon: string, protected notificationService: INotificationService) {
super(id, label, icon);
}
public run(context: CellContext): TPromise<boolean> {
if (hasModelAndCell(context, this.notificationService)) {
return TPromise.wrap(this.runCellAction(context).then(() => true));
}
return TPromise.as(true);
}
abstract runCellAction(context: CellContext): Promise<void>;
}
export class RunCellAction extends ToggleableAction {
public static ID = 'notebook.runCell';
public static LABEL = 'Run cell';
constructor(@INotificationService private notificationService: INotificationService) {
super(RunCellAction.ID, {
shouldToggleTooltip: true,
toggleOnLabel: localize('runCell', 'Run cell'),
toggleOnClass: 'toolbarIconRun',
toggleOffLabel: localize('stopCell', 'Cancel execution'),
toggleOffClass: 'toolbarIconStop',
isOn: true
});
}
public run(context: CellContext): TPromise<boolean> {
if (hasModelAndCell(context, this.notificationService)) {
return TPromise.wrap(this.runCellAction(context).then(() => true));
}
return TPromise.as(true);
}
public async runCellAction(context: CellContext): Promise<void> {
try {
let model = context.model;
let cell = context.cell;
let kernel = await this.getOrStartKernel(model);
if (!kernel) {
return;
}
// If cell is currently running and user clicks the stop/cancel button, call kernel.interrupt()
// This matches the same behavior as JupyterLab
if (cell.future && cell.future.inProgress) {
cell.future.inProgress = false;
await kernel.interrupt();
} else {
// TODO update source based on editor component contents
let content = cell.source;
if (content) {
this.toggle(false);
let future = await kernel.requestExecute({
code: content,
stop_on_error: true
}, false);
cell.setFuture(future as FutureInternal);
// For now, await future completion. Later we should just track and handle cancellation based on model notifications
let reply = await future.done;
}
}
} catch (error) {
let message = getErrorMessage(error);
this.notificationService.error(message);
} finally {
this.toggle(true);
}
}
private async getOrStartKernel(model: NotebookModel): Promise<nb.IKernel> {
let clientSession = model && model.clientSession;
if (!clientSession) {
this.notificationService.error(localize('notebookNotReady', 'The session for this notebook is not yet ready'));
return undefined;
} else if (!clientSession.isReady || clientSession.status === 'dead') {
this.notificationService.info(localize('sessionNotReady', 'The session for this notebook will start momentarily'));
await clientSession.kernelChangeCompleted;
}
if (!clientSession.kernel) {
let defaultKernel = model && model.defaultKernel && model.defaultKernel.name;
if (!defaultKernel) {
this.notificationService.error(localize('noDefaultKernel', 'No kernel is available for this notebook'));
return undefined;
}
await clientSession.changeKernel({
name: defaultKernel
});
}
return clientSession.kernel;
}
}
export class AddCellAction extends CellActionBase {
constructor(
id: string, label: string, private cellType: CellType, private isAfter: boolean,
@INotificationService notificationService: INotificationService
) {
super(id, label, undefined, notificationService);
}
runCellAction(context: CellContext): Promise<void> {
try {
let model = context.model;
let index = model.cells.findIndex((cell) => cell.id === context.cell.id);
if (index !== undefined && this.isAfter) {
index += 1;
}
model.addCell(this.cellType, index);
} catch (error) {
let message = getErrorMessage(error);
this.notificationService.notify({
severity: Severity.Error,
message: message
});
}
return Promise.resolve();
}
}
export class DeleteCellAction extends CellActionBase {
constructor(id: string, label: string,
@INotificationService notificationService: INotificationService
) {
super(id, label, undefined, notificationService);
}
runCellAction(context: CellContext): Promise<void> {
try {
context.model.deleteCell(context.cell);
} catch (error) {
let message = getErrorMessage(error);
this.notificationService.notify({
severity: Severity.Error,
message: message
});
}
return Promise.resolve();
}
}

View File

@@ -0,0 +1,15 @@
<!--
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-->
<div style="overflow: hidden; width: 100%; height: 100%; display: flex; flex-flow: column">
<div class="notebook-code" style="flex: 0 0 auto;">
<code-component [cellModel]="cellModel" [model]="model"></code-component>
</div>
<div #codeCellOutput class="notebook-output" style="flex: 0 0 auto;">
<output-area-component *ngIf="cellModel.outputs && cellModel.outputs.length > 0" [cellModel]="cellModel">
</output-area-component>
</div>
</div>

View File

@@ -0,0 +1,63 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./codeCell';
import { OnInit, Component, Input, Inject, forwardRef, ElementRef, ChangeDetectorRef, OnDestroy, ViewChild } from '@angular/core';
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
import { CellView } from 'sql/parts/notebook/cellViews/interfaces';
import { IColorTheme, IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
import * as themeColors from 'vs/workbench/common/theme';
import { ICellModel } from 'sql/parts/notebook/models/modelInterfaces';
import { NotebookModel } from 'sql/parts/notebook/models/notebookModel';
export const CODE_SELECTOR: string = 'code-cell-component';
@Component({
selector: CODE_SELECTOR,
templateUrl: decodeURI(require.toUrl('./codeCell.component.html'))
})
export class CodeCellComponent extends CellView implements OnInit {
@ViewChild('codeCellOutput', { read: ElementRef }) private outputPreview: ElementRef;
private _model: NotebookModel;
@Input() cellModel: ICellModel;
@Input() set model(value: NotebookModel) {
this._model = value;
}
constructor(
@Inject(forwardRef(() => ChangeDetectorRef)) private _changeRef: ChangeDetectorRef,
@Inject(IWorkbenchThemeService) private themeService: IWorkbenchThemeService
) {
super();
}
ngOnInit() {
this._register(this.themeService.onDidColorThemeChange(this.updateTheme, this));
this.updateTheme(this.themeService.getColorTheme());
if (this.cellModel) {
this.cellModel.onOutputsChanged(() => {
this._changeRef.detectChanges();
});
}
}
// Todo: implement layout
public layout() {
}
private updateTheme(theme: IColorTheme): void {
let outputElement = <HTMLElement>this.outputPreview.nativeElement;
outputElement.style.borderTopColor = theme.getColor(themeColors.SIDE_BAR_BACKGROUND, true).toString();
}
get model(): NotebookModel {
return this._model;
}
}

View File

@@ -0,0 +1,14 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
code-cell-component {
display: block;
}
code-cell-component .notebook-output {
border-top-width: 1px;
border-top-style: solid;
user-select: initial;
}

View File

@@ -0,0 +1,17 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { OnDestroy } from '@angular/core';
import { AngularDisposable } from 'sql/base/common/lifecycle';
export abstract class CellView extends AngularDisposable implements OnDestroy {
constructor() {
super();
}
public abstract layout(): void;
}

View File

@@ -0,0 +1,11 @@
<!--
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-->
<div style="overflow: hidden; width: 100%; height: 100%; display: flex; flex-flow: column">
<div style="flex: 0 0 auto; user-select: initial;">
<div #output ></div>
</div>
</div>

View File

@@ -0,0 +1,95 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./code';
import { OnInit, Component, Input, Inject, forwardRef, ElementRef, ChangeDetectorRef, OnDestroy, ViewChild, Output, EventEmitter } from '@angular/core';
import { AngularDisposable } from 'sql/base/common/lifecycle';
import { nb } from 'sqlops';
import { INotebookService } from 'sql/services/notebook/notebookService';
import { MimeModel } from 'sql/parts/notebook/outputs/common/mimemodel';
import * as outputProcessor from '../outputs/common/outputProcessor';
import { RenderMimeRegistry } from 'sql/parts/notebook/outputs/registry';
import 'vs/css!sql/parts/notebook/outputs/style/index';
export const OUTPUT_SELECTOR: string = 'output-component';
@Component({
selector: OUTPUT_SELECTOR,
templateUrl: decodeURI(require.toUrl('./output.component.html'))
})
export class OutputComponent extends AngularDisposable implements OnInit {
@ViewChild('output', { read: ElementRef }) private outputElement: ElementRef;
@Input() cellOutput: nb.ICellOutput;
private _trusted: boolean;
private _initialized: boolean = false;
private readonly _minimumHeight = 30;
registry: RenderMimeRegistry;
constructor(
@Inject(INotebookService) private _notebookService: INotebookService
) {
super();
this.registry = _notebookService.getMimeRegistry();
}
ngOnInit() {
this.renderOutput();
this._initialized = true;
}
private renderOutput() {
let node = this.outputElement.nativeElement;
let output = this.cellOutput;
let options = outputProcessor.getBundleOptions({ value: output, trusted: this.trustedMode });
// TODO handle safe/unsafe mapping
this.createRenderedMimetype(options, node);
}
public layout(): void {
}
get trustedMode(): boolean {
return this._trusted;
}
@Input()
set trustedMode(value: boolean) {
this._trusted = value;
if (this._initialized) {
this.renderOutput();
}
}
protected createRenderedMimetype(options: MimeModel.IOptions, node: HTMLElement): void {
let mimeType = this.registry.preferredMimeType(
options.data,
options.trusted ? 'any' : 'ensure'
);
if (mimeType) {
let output = this.registry.createRenderer(mimeType);
output.node = node;
let model = new MimeModel(options);
output.renderModel(model).catch(error => {
// Manually append error message to output
output.node.innerHTML = `<pre>Javascript Error: ${error.message}</pre>`;
// Remove mime-type-specific CSS classes
output.node.className = 'p-Widget jp-RenderedText';
output.node.setAttribute(
'data-mime-type',
'application/vnd.jupyter.stderr'
);
});
//this.setState({ node: node });
} else {
// TODO Localize
node.innerHTML =
`No ${options.trusted ? '' : '(safe) '}renderer could be ` +
'found for output. It has the following MIME types: ' +
Object.keys(options.data).join(', ');
//this.setState({ node: node });
}
}
}

View File

@@ -0,0 +1,12 @@
<!--
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-->
<div style="overflow: hidden; width: 100%; height: 100%; display: flex; flex-flow: column">
<div style="flex: 0 0 auto;">
<output-component *ngFor="let output of cellModel.outputs" [cellOutput]="output" [trustedMode] = "cellModel.trustedMode" >
</output-component>
</div>
</div>

View File

@@ -0,0 +1,33 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./code';
import { OnInit, Component, Input, Inject, forwardRef, ChangeDetectorRef } from '@angular/core';
import { AngularDisposable } from 'sql/base/common/lifecycle';
import { ICellModel } from 'sql/parts/notebook/models/modelInterfaces';
export const OUTPUT_AREA_SELECTOR: string = 'output-area-component';
@Component({
selector: OUTPUT_AREA_SELECTOR,
templateUrl: decodeURI(require.toUrl('./outputArea.component.html'))
})
export class OutputAreaComponent extends AngularDisposable implements OnInit {
@Input() cellModel: ICellModel;
private readonly _minimumHeight = 30;
constructor(
@Inject(forwardRef(() => ChangeDetectorRef)) private _changeRef: ChangeDetectorRef,
) {
super();
}
ngOnInit(): void {
if (this.cellModel) {
this.cellModel.onOutputsChanged(() => {
this._changeRef.detectChanges();
});
}
}
}

View File

@@ -0,0 +1,13 @@
<!--
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-->
<div style="overflow: hidden; width: 100%; height: 100%; display: flex; flex-flow: column">
<div class="notebook-text" style="flex: 0 0 auto;">
<code-component *ngIf="isEditMode" [cellModel]="cellModel" (onContentChanged)="handleContentChanged()"></code-component>
</div>
<div #preview class="notebook-preview" style="flex: 0 0 auto;" (dblclick)="toggleEditMode()">
</div>
</div>

View File

@@ -0,0 +1,109 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./textCell';
import { OnInit, Component, Input, Inject, forwardRef, ElementRef, ChangeDetectorRef, OnDestroy, ViewChild } from '@angular/core';
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
import { CellView } from 'sql/parts/notebook/cellViews/interfaces';
import { IColorTheme, IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
import * as themeColors from 'vs/workbench/common/theme';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { ICellModel } from 'sql/parts/notebook/models/modelInterfaces';
import { ISanitizer, defaultSanitizer } from 'sql/parts/notebook/outputs/sanitizer';
import { localize } from 'vs/nls';
export const TEXT_SELECTOR: string = 'text-cell-component';
@Component({
selector: TEXT_SELECTOR,
templateUrl: decodeURI(require.toUrl('./textCell.component.html'))
})
export class TextCellComponent extends CellView implements OnInit {
@ViewChild('preview', { read: ElementRef }) private output: ElementRef;
@Input() cellModel: ICellModel;
private _content: string;
private isEditMode: boolean;
private _sanitizer: ISanitizer;
constructor(
@Inject(forwardRef(() => CommonServiceInterface)) private _bootstrapService: CommonServiceInterface,
@Inject(forwardRef(() => ChangeDetectorRef)) private _changeRef: ChangeDetectorRef,
@Inject(IWorkbenchThemeService) private themeService: IWorkbenchThemeService,
@Inject(ICommandService) private _commandService: ICommandService
) {
super();
this.isEditMode = false;
}
ngOnChanges() {
this.updatePreview();
}
//Gets sanitizer from ISanitizer interface
private get sanitizer(): ISanitizer {
if (this._sanitizer) {
return this._sanitizer;
}
return this._sanitizer = defaultSanitizer;
}
/**
* Updates the preview of markdown component with latest changes
* If content is empty and in non-edit mode, default it to 'Double-click to edit'
* Sanitizes the data to be shown in markdown cell
*/
private updatePreview() {
if (this._content !== this.cellModel.source) {
if (!this.cellModel.source && !this.isEditMode) {
(<HTMLElement>this.output.nativeElement).innerHTML = localize('doubleClickEdit', 'Double-click to edit');
} else {
this._content = this.sanitizeContent(this.cellModel.source);
// todo: pass in the notebook filename instead of undefined value
this._commandService.executeCommand<string>('notebook.showPreview', undefined, this._content).then((htmlcontent) => {
let outputElement = <HTMLElement>this.output.nativeElement;
outputElement.innerHTML = htmlcontent;
});
}
}
}
//Sanitizes the content based on trusted mode of Cell Model
private sanitizeContent(content: string): string {
if (this.cellModel && !this.cellModel.trustedMode) {
content = this.sanitizer.sanitize(content);
}
return content;
}
ngOnInit() {
this.updatePreview();
this._register(this.themeService.onDidColorThemeChange(this.updateTheme, this));
this.updateTheme(this.themeService.getColorTheme());
this.cellModel.onOutputsChanged(e => {
this.updatePreview();
});
}
// Todo: implement layout
public layout() {
}
private updateTheme(theme: IColorTheme): void {
let outputElement = <HTMLElement>this.output.nativeElement;
outputElement.style.borderTopColor = theme.getColor(themeColors.SIDE_BAR_BACKGROUND, true).toString();
}
public handleContentChanged(): void {
this.updatePreview();
}
public toggleEditMode(): void {
this.isEditMode = !this.isEditMode;
this.updatePreview();
this._changeRef.detectChanges();
}
}

View File

@@ -0,0 +1,14 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
text-cell-component {
display: block;
}
text-cell-component .notebook-preview {
border-top-width: 1px;
border-top-style: solid;
user-select: initial;
}

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12"><defs><style>.cls-1{fill:#fff;}</style></defs><title>add_12x12</title><path class="cls-1" d="M6,.88a5.17,5.17,0,0,1,1.4.19A5.26,5.26,0,0,1,11,4.73a5.3,5.3,0,0,1,0,2.79,5.26,5.26,0,0,1-3.67,3.67,5.3,5.3,0,0,1-2.79,0A5.26,5.26,0,0,1,.9,7.53a5.3,5.3,0,0,1,0-2.79A5.26,5.26,0,0,1,4.57,1.07,5.17,5.17,0,0,1,6,.88Zm0,9.75a4.4,4.4,0,0,0,1.2-.16A4.56,4.56,0,0,0,8.24,10,4.5,4.5,0,0,0,9.85,8.4a4.56,4.56,0,0,0,.45-1.08,4.51,4.51,0,0,0,0-2.39,4.56,4.56,0,0,0-.45-1.08A4.5,4.5,0,0,0,8.24,2.24a4.56,4.56,0,0,0-1.08-.45,4.51,4.51,0,0,0-2.39,0,4.56,4.56,0,0,0-1.08.45A4.5,4.5,0,0,0,2.08,3.86a4.56,4.56,0,0,0-.45,1.08,4.51,4.51,0,0,0,0,2.39A4.56,4.56,0,0,0,2.08,8.4,4.5,4.5,0,0,0,3.7,10a4.56,4.56,0,0,0,1.08.45A4.4,4.4,0,0,0,6,10.63Zm.38-4.88H8.22V6.5H6.34V8.38H5.59V6.5H3.72V5.75H5.59V3.88h.75Z"/></svg>

After

Width:  |  Height:  |  Size: 882 B

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#fff;}</style></defs><title>execute_cell_inverse </title><circle class="cls-1" cx="8" cy="7.92" r="7.76"/><polygon points="10.7 8 6.67 11 6.67 5 10.7 8 10.7 8"/></svg>

After

Width:  |  Height:  |  Size: 285 B

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#fff;}.cls-2{fill:#d02e00;}</style></defs><title>nontrust-inverse</title><path class="cls-1" d="M13.74,2a9.49,9.49,0,0,1-1.19-.17,7.1,7.1,0,0,1-1.14-.35A6.72,6.72,0,0,1,10.29.85,7.43,7.43,0,0,0,9.62.48,5.31,5.31,0,0,0,9,.22a4.18,4.18,0,0,0-.7-.16A6.07,6.07,0,0,0,7.5,0,4.81,4.81,0,0,0,6,.22,5.34,5.34,0,0,0,4.71.85a6.72,6.72,0,0,1-1.12.59,7.09,7.09,0,0,1-1.14.35A9.48,9.48,0,0,1,1.26,2C.85,2,.43,2,0,2V6A7.62,7.62,0,0,0,.29,8.17a9.15,9.15,0,0,0,.78,1.94,10.78,10.78,0,0,0,1.2,1.74,13.35,13.35,0,0,0,1.49,1.52,15.81,15.81,0,0,0,1.7,1.33c.59.42,1.19.8,1.8,1.15L7.5,16l.24-.14c.61-.35,1.21-.73,1.8-1.15a12.71,12.71,0,0,0,1.24-1l-.7-.7c-.32.27-.65.52-1,.76q-.8.55-1.59,1-.79-.46-1.59-1a15.89,15.89,0,0,1-1.51-1.2A13.66,13.66,0,0,1,3,11.22,9.59,9.59,0,0,1,2,9.66a8.52,8.52,0,0,1-.72-1.74A7.1,7.1,0,0,1,1,6V3a9.54,9.54,0,0,0,2.23-.37,8.08,8.08,0,0,0,2-.95,4.4,4.4,0,0,1,1.06-.5A3.87,3.87,0,0,1,7.5,1a3.87,3.87,0,0,1,1.16.16,4.4,4.4,0,0,1,1.06.5,8.08,8.08,0,0,0,2,.95A9.54,9.54,0,0,0,14,3V6a7.1,7.1,0,0,1-.26,1.91A8.51,8.51,0,0,1,13,9.66l-.1.18.73.73a3.14,3.14,0,0,0,.28-.46,9.15,9.15,0,0,0,.78-1.94A7.62,7.62,0,0,0,15,6V2C14.57,2,14.15,2,13.74,2Z"/><polygon class="cls-2" points="13.82 13.25 16 15.42 15.43 15.99 13.26 13.81 11.07 15.99 10.5 15.42 12.69 13.25 10.46 11.02 11.03 10.45 13.26 12.68 15.43 10.5 16 11.06 13.82 13.25"/></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#fff;}</style></defs><title>stop_cell_inverse</title><circle class="cls-1" cx="8" cy="7.92" r="7.76"/><rect x="5" y="4.92" width="6" height="6"/></svg>

After

Width:  |  Height:  |  Size: 269 B

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#fff;}.cls-2{fill:#061c08;}.cls-3{fill:#3bb44a;}</style></defs><title>trust_inverse </title><path class="cls-1" d="M13.74,2a9.46,9.46,0,0,1-1.19-.17,8.28,8.28,0,0,1-1.14-.35A6.72,6.72,0,0,1,10.29.87,7.43,7.43,0,0,0,9.62.5,4,4,0,0,0,8.26.08,4.62,4.62,0,0,0,7.5,0,5.15,5.15,0,0,0,6,.23,5.39,5.39,0,0,0,4.71.87a6.72,6.72,0,0,1-1.12.59,8.28,8.28,0,0,1-1.14.35A9.46,9.46,0,0,1,1.26,2C.85,2,.43,2,0,2V6A7.62,7.62,0,0,0,.29,8.19a9.37,9.37,0,0,0,.78,1.94,10.69,10.69,0,0,0,1.2,1.73,14.32,14.32,0,0,0,1.49,1.53,15.82,15.82,0,0,0,1.7,1.33q.88.62,1.8,1.14L7.5,16l.24-.14c.28-.16.57-.33.85-.52l-.71-.72-.38.23q-.79-.46-1.59-1a15.89,15.89,0,0,1-1.51-1.2A14.78,14.78,0,0,1,3,11.24,9.26,9.26,0,0,1,2,9.67a8.29,8.29,0,0,1-.72-1.74A7,7,0,0,1,1,6V3a10.09,10.09,0,0,0,2.23-.37,8.08,8.08,0,0,0,2-1,4.05,4.05,0,0,1,1.06-.5A3.87,3.87,0,0,1,7.5,1a3.87,3.87,0,0,1,1.16.16,4.06,4.06,0,0,1,1.06.5,8.08,8.08,0,0,0,2,1A10.09,10.09,0,0,0,14,3V6a7,7,0,0,1-.26,1.9A8.29,8.29,0,0,1,13,9.67,9.26,9.26,0,0,1,12,11.24a10.85,10.85,0,0,1-.8.86l.71.71a12.86,12.86,0,0,0,.87-1,10.69,10.69,0,0,0,1.2-1.73,9.37,9.37,0,0,0,.78-1.94A7.62,7.62,0,0,0,15,6V2C14.57,2,14.15,2,13.74,2ZM10,13.1a9.54,9.54,0,0,1-.89.68l.54.55.16.16c.31-.21.61-.44.9-.68Z"/><polygon class="cls-2" points="16 10.94 11.15 15.79 10.49 15.13 9.85 14.49 9.69 14.33 9.15 13.78 8.81 13.44 9.59 12.66 10.04 13.1 10.75 13.81 10.85 13.91 11.15 14.21 15.22 10.15 15.33 10.26 16 10.94"/><polygon class="cls-3" points="16 10.94 11.15 15.79 10.49 15.13 9.85 14.49 9.69 14.33 9.15 13.78 8.81 13.44 9.59 12.66 10.04 13.1 10.75 13.81 10.85 13.91 11.15 14.21 15.22 10.15 15.33 10.26 16 10.94"/></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12"><title>add_16x16</title><path d="M6,1a5.17,5.17,0,0,1,1.4.19,5.26,5.26,0,0,1,3.67,3.67,5.3,5.3,0,0,1,0,2.79A5.26,5.26,0,0,1,7.4,11.32a5.3,5.3,0,0,1-2.79,0A5.26,5.26,0,0,1,.94,7.65a5.3,5.3,0,0,1,0-2.79A5.26,5.26,0,0,1,4.6,1.19,5.17,5.17,0,0,1,6,1Zm0,9.75a4.4,4.4,0,0,0,1.2-.16,4.56,4.56,0,0,0,1.08-.45A4.5,4.5,0,0,0,9.88,8.53a4.56,4.56,0,0,0,.45-1.08,4.51,4.51,0,0,0,0-2.39A4.56,4.56,0,0,0,9.88,4,4.5,4.5,0,0,0,8.27,2.37a4.56,4.56,0,0,0-1.08-.45,4.51,4.51,0,0,0-2.39,0,4.56,4.56,0,0,0-1.08.45A4.5,4.5,0,0,0,2.11,4a4.56,4.56,0,0,0-.45,1.08,4.51,4.51,0,0,0,0,2.39,4.56,4.56,0,0,0,.45,1.08,4.5,4.5,0,0,0,1.61,1.61,4.56,4.56,0,0,0,1.08.45A4.4,4.4,0,0,0,6,10.76Zm.38-4.88H8.25v.75H6.37V8.51H5.62V6.63H3.75V5.88H5.62V4h.75Z"/></svg>

After

Width:  |  Height:  |  Size: 818 B

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#fff;}</style></defs><title>execute_cell</title><circle cx="8" cy="7.92" r="7.76"/><polygon class="cls-1" points="10.7 8 6.67 11 6.67 5 10.7 8 10.7 8"/></svg>

After

Width:  |  Height:  |  Size: 276 B

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#d02e00;}</style></defs><title>nontrust</title><path d="M13.74,2a9.49,9.49,0,0,1-1.19-.17,7.1,7.1,0,0,1-1.14-.35A6.72,6.72,0,0,1,10.29.84,7.43,7.43,0,0,0,9.62.47,5.31,5.31,0,0,0,9,.21,4.18,4.18,0,0,0,8.26,0,6.07,6.07,0,0,0,7.5,0,4.81,4.81,0,0,0,6,.21,5.34,5.34,0,0,0,4.71.84a6.72,6.72,0,0,1-1.12.59,7.09,7.09,0,0,1-1.14.35A9.48,9.48,0,0,1,1.26,2C.85,2,.43,2,0,2V6A7.62,7.62,0,0,0,.29,8.16a9.15,9.15,0,0,0,.78,1.94,10.78,10.78,0,0,0,1.2,1.74,13.35,13.35,0,0,0,1.49,1.52,15.81,15.81,0,0,0,1.7,1.33c.59.42,1.19.8,1.8,1.15L7.5,16l.24-.14c.61-.35,1.21-.73,1.8-1.15a12.71,12.71,0,0,0,1.24-1l-.7-.7c-.32.27-.65.52-1,.76q-.8.55-1.59,1-.79-.46-1.59-1A15.89,15.89,0,0,1,4.4,12.6,13.66,13.66,0,0,1,3,11.21,9.59,9.59,0,0,1,2,9.65a8.52,8.52,0,0,1-.72-1.74A7.1,7.1,0,0,1,1,6V3a9.54,9.54,0,0,0,2.23-.37,8.08,8.08,0,0,0,2-.95,4.4,4.4,0,0,1,1.06-.5A3.87,3.87,0,0,1,7.5,1a3.87,3.87,0,0,1,1.16.16,4.4,4.4,0,0,1,1.06.5,8.08,8.08,0,0,0,2,.95A9.54,9.54,0,0,0,14,3V6a7.1,7.1,0,0,1-.26,1.91A8.51,8.51,0,0,1,13,9.65l-.1.18.73.73a3.14,3.14,0,0,0,.28-.46,9.15,9.15,0,0,0,.78-1.94A7.62,7.62,0,0,0,15,6V2C14.57,2,14.15,2,13.74,2Z"/><polygon class="cls-1" points="13.82 13.24 16 15.41 15.43 15.98 13.26 13.8 11.07 15.98 10.5 15.41 12.69 13.24 10.46 11.01 11.03 10.44 13.26 12.67 15.43 10.49 16 11.05 13.82 13.24"/></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#fff;}</style></defs><title>stop_cell</title><circle cx="8" cy="7.92" r="7.76"/><rect class="cls-1" x="5" y="4.92" width="6" height="6"/></svg>

After

Width:  |  Height:  |  Size: 261 B

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#061c08;}.cls-2{fill:#3bb44a;}</style></defs><title>trust</title><path d="M13.74,2a9.46,9.46,0,0,1-1.19-.17,8.28,8.28,0,0,1-1.14-.35A6.72,6.72,0,0,1,10.29.85,7.43,7.43,0,0,0,9.62.48,4,4,0,0,0,8.26.07,4.62,4.62,0,0,0,7.5,0,5.15,5.15,0,0,0,6,.21,5.39,5.39,0,0,0,4.71.85a6.72,6.72,0,0,1-1.12.59,8.28,8.28,0,0,1-1.14.35A9.46,9.46,0,0,1,1.26,2C.85,2,.43,2,0,2V6A7.62,7.62,0,0,0,.29,8.17a9.37,9.37,0,0,0,.78,1.94,10.69,10.69,0,0,0,1.2,1.73,14.32,14.32,0,0,0,1.49,1.53,15.82,15.82,0,0,0,1.7,1.33q.88.62,1.8,1.14L7.5,16l.24-.14c.28-.16.57-.33.85-.52l-.71-.72-.38.23q-.79-.46-1.59-1a15.89,15.89,0,0,1-1.51-1.2A14.78,14.78,0,0,1,3,11.22,9.26,9.26,0,0,1,2,9.65a8.29,8.29,0,0,1-.72-1.74A7,7,0,0,1,1,6V3a10.09,10.09,0,0,0,2.23-.37,8.08,8.08,0,0,0,2-1,4.05,4.05,0,0,1,1.06-.5A3.87,3.87,0,0,1,7.5,1a3.87,3.87,0,0,1,1.16.16,4.06,4.06,0,0,1,1.06.5,8.08,8.08,0,0,0,2,1A10.09,10.09,0,0,0,14,3V6a7,7,0,0,1-.26,1.9A8.29,8.29,0,0,1,13,9.65,9.26,9.26,0,0,1,12,11.22a10.85,10.85,0,0,1-.8.86l.71.71a12.86,12.86,0,0,0,.87-1,10.69,10.69,0,0,0,1.2-1.73,9.37,9.37,0,0,0,.78-1.94A7.62,7.62,0,0,0,15,6V2C14.57,2,14.15,2,13.74,2ZM10,13.08a9.54,9.54,0,0,1-.89.68l.54.55.16.16c.31-.21.61-.44.9-.68Z"/><polygon class="cls-1" points="16 10.93 11.15 15.78 10.49 15.12 9.85 14.47 9.69 14.31 9.15 13.77 8.81 13.43 9.59 12.64 10.04 13.09 10.75 13.79 10.85 13.89 11.15 14.2 15.22 10.13 15.33 10.24 16 10.93"/><polygon class="cls-2" points="16 10.93 11.15 15.78 10.49 15.12 9.85 14.47 9.69 14.31 9.15 13.77 8.81 13.43 9.59 12.64 10.04 13.09 10.75 13.79 10.85 13.89 11.15 14.2 15.22 10.13 15.33 10.24 16 10.93"/></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1,322 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { Event, Emitter } from 'vs/base/common/event';
import URI from 'vs/base/common/uri';
import { nb } from 'sqlops';
import { ICellModelOptions, IModelFactory, FutureInternal } from './modelInterfaces';
import * as notebookUtils from '../notebookUtils';
import { CellTypes, CellType, NotebookChangeType } from 'sql/parts/notebook/models/contracts';
import { ICellModel } from 'sql/parts/notebook/models/modelInterfaces';
let modelId = 0;
export class CellModel implements ICellModel {
private static LanguageMapping: Map<string, string>;
private _cellType: nb.CellType;
private _source: string;
private _language: string;
private _future: FutureInternal;
private _outputs: nb.ICellOutput[] = [];
private _isEditMode: boolean;
private _onOutputsChanged = new Emitter<ReadonlyArray<nb.ICellOutput>>();
private _onCellModeChanged = new Emitter<boolean>();
public id: string;
private _isTrusted: boolean;
private _active: boolean;
private _cellUri: URI;
constructor(private factory: IModelFactory, cellData?: nb.ICell, private _options?: ICellModelOptions) {
this.id = `${modelId++}`;
CellModel.CreateLanguageMappings();
// Do nothing for now
if (cellData) {
this.fromJSON(cellData);
} else {
this._cellType = CellTypes.Code;
this._source = '';
}
this._isEditMode = this._cellType !== CellTypes.Markdown;
this.setDefaultLanguage();
if (_options && _options.isTrusted) {
this._isTrusted = true;
} else {
this._isTrusted = false;
}
}
public equals(other: ICellModel) {
return other && other.id === this.id;
}
public get onOutputsChanged(): Event<ReadonlyArray<nb.ICellOutput>> {
return this._onOutputsChanged.event;
}
public get onCellModeChanged(): Event<boolean> {
return this._onCellModeChanged.event;
}
public get isEditMode(): boolean {
return this._isEditMode;
}
public get future(): FutureInternal {
return this._future;
}
public set isEditMode(isEditMode: boolean) {
this._isEditMode = isEditMode;
this._onCellModeChanged.fire(this._isEditMode);
// Note: this does not require a notebook update as it does not change overall state
}
public get trustedMode(): boolean {
return this._isTrusted;
}
public set trustedMode(isTrusted: boolean) {
if (this._isTrusted !== isTrusted) {
this._isTrusted = isTrusted;
this._onOutputsChanged.fire(this._outputs);
}
}
public get active(): boolean {
return this._active;
}
public set active(value: boolean) {
this._active = value;
}
public get cellUri(): URI {
return this._cellUri;
}
public set cellUri(value: URI) {
this._cellUri = value;
}
public get options(): ICellModelOptions {
return this._options;
}
public get cellType(): CellType {
return this._cellType;
}
public get source(): string {
return this._source;
}
public set source(newSource: string) {
if (this._source !== newSource) {
this._source = newSource;
this.sendChangeToNotebook(NotebookChangeType.CellSourceUpdated);
}
}
public get language(): string {
return this._language;
}
public set language(newLanguage: string) {
this._language = newLanguage;
}
/**
* Sets the future which will be used to update the output
* area for this cell
*/
setFuture(future: FutureInternal): void {
if (this._future === future) {
// Nothing to do
return;
}
// Setting the future indicates the cell is running which enables trusted mode.
// See https://jupyter-notebook.readthedocs.io/en/stable/security.html
this._isTrusted = true;
if (this._future) {
this._future.dispose();
}
this.clearOutputs();
this._future = future;
future.setReplyHandler({ handle: (msg) => this.handleReply(msg) });
future.setIOPubHandler({ handle: (msg) => this.handleIOPub(msg) });
}
public clearOutputs(): void {
this._outputs = [];
this.fireOutputsChanged();
}
private fireOutputsChanged(): void {
this._onOutputsChanged.fire(this.outputs);
this.sendChangeToNotebook(NotebookChangeType.CellOutputUpdated);
}
private sendChangeToNotebook(change: NotebookChangeType): void {
if (this._options && this._options.notebook) {
this._options.notebook.onCellChange(this, change);
}
}
public get outputs(): Array<nb.ICellOutput> {
return this._outputs;
}
private handleReply(msg: nb.IShellMessage): void {
// TODO #931 we should process this. There can be a payload attached which should be added to outputs.
// In all other cases, it is a no-op
let output: nb.ICellOutput = msg.content as nb.ICellOutput;
}
private handleIOPub(msg: nb.IIOPubMessage): void {
let msgType = msg.header.msg_type;
let displayId = this.getDisplayId(msg);
let output: nb.ICellOutput;
switch (msgType) {
case 'execute_result':
case 'display_data':
case 'stream':
case 'error':
output = msg.content as nb.ICellOutput;
output.output_type = msgType;
break;
case 'clear_output':
// TODO wait until next message before clearing
// let wait = (msg as KernelMessage.IClearOutputMsg).content.wait;
this.clearOutputs();
break;
case 'update_display_data':
output = msg.content as nb.ICellOutput;
output.output_type = 'display_data';
// TODO #930 handle in-place update of displayed data
// targets = this._displayIdMap.get(displayId);
// if (targets) {
// for (let index of targets) {
// model.set(index, output);
// }
// }
break;
default:
break;
}
// TODO handle in-place update of displayed data
// if (displayId && msgType === 'display_data') {
// targets = this._displayIdMap.get(displayId) || [];
// targets.push(model.length - 1);
// this._displayIdMap.set(displayId, targets);
// }
if (output) {
// deletes transient node in the serialized JSON
delete output['transient'];
this._outputs.push(output);
this.fireOutputsChanged();
}
}
private getDisplayId(msg: nb.IIOPubMessage): string | undefined {
let transient = (msg.content.transient || {});
return transient['display_id'] as string;
}
public toJSON(): nb.ICell {
let cellJson: Partial<nb.ICell> = {
cell_type: this._cellType,
source: this._source,
metadata: {
}
};
if (this._cellType === CellTypes.Code) {
cellJson.metadata.language = this._language,
cellJson.outputs = this._outputs;
cellJson.execution_count = 1; // TODO: keep track of actual execution count
}
return cellJson as nb.ICell;
}
public fromJSON(cell: nb.ICell): void {
if (!cell) {
return;
}
this._cellType = cell.cell_type;
this._source = Array.isArray(cell.source) ? cell.source.join('') : cell.source;
this._language = (cell.metadata && cell.metadata.language) ? cell.metadata.language : 'python';
if (cell.outputs) {
for (let output of cell.outputs) {
// For now, we're assuming it's OK to save these as-is with no modification
this.addOutput(output);
}
}
}
private addOutput(output: nb.ICellOutput) {
this._normalize(output);
this._outputs.push(output);
}
/**
* Normalize an output.
*/
private _normalize(value: nb.ICellOutput): void {
if (notebookUtils.isStream(value)) {
if (Array.isArray(value.text)) {
value.text = (value.text as string[]).join('\n');
}
}
}
private static CreateLanguageMappings(): void {
if (CellModel.LanguageMapping) {
return;
}
CellModel.LanguageMapping = new Map<string, string>();
CellModel.LanguageMapping['pyspark'] = 'python';
CellModel.LanguageMapping['pyspark3'] = 'python';
CellModel.LanguageMapping['python'] = 'python';
CellModel.LanguageMapping['scala'] = 'scala';
}
private get languageInfo(): nb.ILanguageInfo {
if (this._options && this._options.notebook && this._options.notebook.languageInfo) {
return this._options.notebook.languageInfo;
}
return undefined;
}
private setDefaultLanguage(): void {
this._language = 'python';
// In languageInfo, set the language to the "name" property
// If the "name" property isn't defined, check the "mimeType" property
// Otherwise, default to python as the language
let languageInfo = this.languageInfo;
if (languageInfo) {
if (languageInfo.name) {
// check the LanguageMapping to determine if a mapping is necessary (example 'pyspark' -> 'python')
if (CellModel.LanguageMapping[languageInfo.name]) {
this._language = CellModel.LanguageMapping[languageInfo.name];
} else {
this._language = languageInfo.name;
}
} else if (languageInfo.mimetype) {
this._language = languageInfo.mimetype;
}
}
let mimeTypePrefix = 'x-';
if (this._language.includes(mimeTypePrefix)) {
this._language = this._language.replace(mimeTypePrefix, '');
}
}
}

View File

@@ -0,0 +1,361 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// This code is based on @jupyterlab/packages/apputils/src/clientsession.tsx
'use strict';
import { nb } from 'sqlops';
import * as nls from 'vs/nls';
import URI from 'vs/base/common/uri';
import { Event, Emitter } from 'vs/base/common/event';
import { IClientSession, IKernelPreference, IClientSessionOptions } from './modelInterfaces';
import { Deferred } from 'sql/base/common/promise';
import * as notebookUtils from '../notebookUtils';
import * as sparkUtils from '../spark/sparkUtils';
import { INotebookManager } from 'sql/services/notebook/notebookService';
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
import { NotebookConnection } from 'sql/parts/notebook/models/notebookConnection';
/**
* Implementation of a client session. This is a model over session operations,
* which may come from the session manager or a specific session.
*/
export class ClientSession implements IClientSession {
//#region private fields with public accessors
private _terminatedEmitter = new Emitter<void>();
private _kernelChangedEmitter = new Emitter<nb.IKernelChangedArgs>();
private _statusChangedEmitter = new Emitter<nb.ISession>();
private _iopubMessageEmitter = new Emitter<nb.IMessage>();
private _unhandledMessageEmitter = new Emitter<nb.IMessage>();
private _propertyChangedEmitter = new Emitter<'path' | 'name' | 'type'>();
private _notebookUri: URI;
private _type: string;
private _name: string;
private _isReady: boolean;
private _ready: Deferred<void>;
private _kernelChangeCompleted: Deferred<void>;
private _kernelPreference: IKernelPreference;
private _kernelDisplayName: string;
private _errorMessage: string;
//#endregion
private _serverLoadFinished: Promise<void>;
private _session: nb.ISession;
private isServerStarted: boolean;
private notebookManager: INotebookManager;
private _connection: NotebookConnection;
private _kernelConfigActions: ((kernelName: string) => Promise<any>)[] = [];
constructor(private options: IClientSessionOptions) {
this._notebookUri = options.notebookUri;
this.notebookManager = options.notebookManager;
this._isReady = false;
this._ready = new Deferred<void>();
this._kernelChangeCompleted = new Deferred<void>();
}
public async initialize(connection?: NotebookConnection): Promise<void> {
try {
this._kernelConfigActions.push((kernelName: string) => { return this.runTasksBeforeSessionStart(kernelName); });
this._connection = connection;
this._serverLoadFinished = this.startServer();
await this._serverLoadFinished;
await this.initializeSession();
} catch (err) {
this._errorMessage = notebookUtils.getErrorMessage(err);
}
// Always resolving for now. It's up to callers to check for error case
this._isReady = true;
this._ready.resolve();
this._kernelChangeCompleted.resolve();
}
private async startServer(): Promise<void> {
let serverManager = this.notebookManager.serverManager;
if (serverManager && !serverManager.isStarted) {
await serverManager.startServer();
if (!serverManager.isStarted) {
throw new Error(nls.localize('ServerNotStarted', 'Server did not start for unknown reason'));
}
this.isServerStarted = serverManager.isStarted;
} else {
this.isServerStarted = true;
}
}
private async initializeSession(): Promise<void> {
await this._serverLoadFinished;
if (this.isServerStarted) {
if (!this.notebookManager.sessionManager.isReady) {
await this.notebookManager.sessionManager.ready;
}
if (this._kernelPreference && this._kernelPreference.shouldStart) {
await this.startSessionInstance(this._kernelPreference.name);
}
}
}
private async startSessionInstance(kernelName: string): Promise<void> {
let session: nb.ISession;
try {
// TODO #3164 should use URI instead of path for startNew
session = await this.notebookManager.sessionManager.startNew({
path: this.notebookUri.fsPath,
kernelName: kernelName
// TODO add kernel name if saved in the document
});
session.defaultKernelLoaded = true;
} catch (err) {
// TODO move registration
if (err && err.response && err.response.status === 501) {
this.options.notificationService.warn(nls.localize('sparkKernelRequiresConnection', 'Kernel {0} was not found. The default kernel will be used instead.', kernelName));
session = await this.notebookManager.sessionManager.startNew({
path: this.notebookUri.fsPath,
kernelName: undefined
});
session.defaultKernelLoaded = false;
} else {
throw err;
}
}
this._session = session;
await this.runKernelConfigActions(kernelName);
this._statusChangedEmitter.fire(session);
}
private async runKernelConfigActions(kernelName: string): Promise<void> {
for (let startAction of this._kernelConfigActions) {
await startAction(kernelName);
}
}
public dispose(): void {
// No-op for now
}
/**
* Indicates the server has finished loading. It may have failed to load in
* which case the view will be in an error state.
*/
public get serverLoadFinished(): Promise<void> {
return this._serverLoadFinished;
}
//#region IClientSession Properties
public get terminated(): Event<void> {
return this._terminatedEmitter.event;
}
public get kernelChanged(): Event<nb.IKernelChangedArgs> {
return this._kernelChangedEmitter.event;
}
public get statusChanged(): Event<nb.ISession> {
return this._statusChangedEmitter.event;
}
public get iopubMessage(): Event<nb.IMessage> {
return this._iopubMessageEmitter.event;
}
public get unhandledMessage(): Event<nb.IMessage> {
return this._unhandledMessageEmitter.event;
}
public get propertyChanged(): Event<'path' | 'name' | 'type'> {
return this._propertyChangedEmitter.event;
}
public get kernel(): nb.IKernel | null {
return this._session ? this._session.kernel : undefined;
}
public get notebookUri(): URI {
return this._notebookUri;
}
public get name(): string {
return this._name;
}
public get type(): string {
return this._type;
}
public get status(): nb.KernelStatus {
if (!this.isReady) {
return 'starting';
}
return this._session ? this._session.status : 'dead';
}
public get isReady(): boolean {
return this._isReady;
}
public get ready(): Promise<void> {
return this._ready.promise;
}
public get kernelChangeCompleted(): Promise<void> {
return this._kernelChangeCompleted.promise;
}
public get kernelPreference(): IKernelPreference {
return this._kernelPreference;
}
public set kernelPreference(value: IKernelPreference) {
this._kernelPreference = value;
}
public get kernelDisplayName(): string {
return this._kernelDisplayName;
}
public get errorMessage(): string {
return this._errorMessage;
}
public get isInErrorState(): boolean {
return !!this._errorMessage;
}
//#endregion
//#region Not Yet Implemented
/**
* Change the current kernel associated with the document.
*/
async changeKernel(options: nb.IKernelSpec): Promise<nb.IKernel> {
this._kernelChangeCompleted = new Deferred<void>();
this._isReady = false;
let oldKernel = this.kernel;
let newKernel = this.kernel;
let kernel = await this.doChangeKernel(options);
try {
await kernel.ready;
} catch (error) {
// Cleanup some state before re-throwing
this._isReady = kernel.isReady;
this._kernelChangeCompleted.resolve();
throw error;
}
newKernel = this._session ? kernel : this._session.kernel;
this._isReady = kernel.isReady;
// Send resolution events to listeners
this._kernelChangeCompleted.resolve();
this._kernelChangedEmitter.fire({
oldValue: oldKernel,
newValue: newKernel
});
return kernel;
}
/**
* Helper method to either call ChangeKernel on current session, or start a new session
* @param options
*/
private async doChangeKernel(options: nb.IKernelSpec): Promise<nb.IKernel> {
let kernel: nb.IKernel;
if (this._session) {
kernel = await this._session.changeKernel(options);
await this.runKernelConfigActions(kernel.name);
} else {
kernel = await this.startSessionInstance(options.name).then(() => this.kernel);
}
return kernel;
}
public async runTasksBeforeSessionStart(kernelName: string): Promise<void> {
// TODO we should move all Spark-related code to SparkMagicContext
if (this._session && this._connection && this.isSparkKernel(kernelName)) {
// TODO may need to reenable a way to get the credential
// await this._connection.getCredential();
// %_do_not_call_change_endpoint is a SparkMagic command that lets users change endpoint options,
// such as user/profile/host name/auth type
let server = URI.parse(sparkUtils.getLivyUrl(this._connection.host, this._connection.knoxport)).toString();
let doNotCallChangeEndpointParams =
`%_do_not_call_change_endpoint --username=${this._connection.user} --password=${this._connection.password} --server=${server} --auth=Basic_Access`;
let future = this._session.kernel.requestExecute({
code: doNotCallChangeEndpointParams
}, true);
await future.done;
}
}
public async updateConnection(connection: NotebookConnection): Promise<void> {
if (!this.kernel) {
// TODO is there any case where skipping causes errors? So far it seems like it gets called twice
return;
}
this._connection = (connection.connectionProfile.id !== '-1') ? connection : this._connection;
// if kernel is not set, don't run kernel config actions
// this should only occur when a cell is cancelled, which interrupts the kernel
if (this.kernel && this.kernel.name) {
await this.runKernelConfigActions(this.kernel.name);
}
}
isSparkKernel(kernelName: string): any {
return kernelName && kernelName.toLowerCase().indexOf('spark') > -1;
}
/**
* Kill the kernel and shutdown the session.
*
* @returns A promise that resolves when the session is shut down.
*/
public async shutdown(): Promise<void> {
// Always try to shut down session
if (this._session && this._session.id) {
await this.notebookManager.sessionManager.shutdown(this._session.id);
}
let serverManager = this.notebookManager.serverManager;
if (serverManager) {
await serverManager.stopServer();
}
}
/**
* Select a kernel for the session.
*/
selectKernel(): Promise<void> {
throw new Error('Not implemented');
}
/**
* Restart the session.
*
* @returns A promise that resolves with whether the kernel has restarted.
*
* #### Notes
* If there is a running kernel, present a dialog.
* If there is no kernel, we start a kernel with the last run
* kernel name and resolves with `true`. If no kernel has been started,
* this is a no-op, and resolves with `false`.
*/
restart(): Promise<boolean> {
throw new Error('Not implemented');
}
/**
* Change the session path.
*
* @param path - The new session path.
*
* @returns A promise that resolves when the session has renamed.
*
* #### Notes
* This uses the Jupyter REST API, and the response is validated.
* The promise is fulfilled on a valid response and rejected otherwise.
*/
setPath(path: string): Promise<void> {
throw new Error('Not implemented');
}
/**
* Change the session name.
*/
setName(name: string): Promise<void> {
throw new Error('Not implemented');
}
/**
* Change the session type.
*/
setType(type: string): Promise<void> {
throw new Error('Not implemented');
}
//#endregion
}

View File

@@ -0,0 +1,47 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
export type CellType = 'code' | 'markdown' | 'raw';
export class CellTypes {
public static readonly Code = 'code';
public static readonly Markdown = 'markdown';
public static readonly Raw = 'raw';
}
// to do: add all mime types
export type MimeType = 'text/plain' | 'text/html';
// to do: add all mime types
export class MimeTypes {
public static readonly PlainText = 'text/plain';
public static readonly HTML = 'text/html';
}
export type OutputType =
| 'execute_result'
| 'display_data'
| 'stream'
| 'error'
| 'update_display_data';
export class OutputTypes {
public static readonly ExecuteResult = 'execute_result';
public static readonly DisplayData = 'display_data';
public static readonly Stream = 'stream';
public static readonly Error = 'error';
public static readonly UpdateDisplayData = 'update_display_data';
}
export enum NotebookChangeType {
CellsAdded,
CellDeleted,
CellSourceUpdated,
CellOutputUpdated,
DirtyStateChanged
}

View File

@@ -0,0 +1,23 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { nb } from 'sqlops';
import { CellModel } from './cell';
import { IClientSession, IClientSessionOptions, ICellModelOptions, ICellModel, IModelFactory } from './modelInterfaces';
import { ClientSession } from './clientSession';
export class ModelFactory implements IModelFactory {
public createCell(cell: nb.ICell, options: ICellModelOptions): ICellModel {
return new CellModel(this, cell, options);
}
public createClientSession(options: IClientSessionOptions): IClientSession {
return new ClientSession(options);
}
}

View File

@@ -0,0 +1,389 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// This code is based on @jupyterlab/packages/apputils/src/clientsession.tsx
'use strict';
import { nb } from 'sqlops';
import { Event } from 'vs/base/common/event';
import { IDisposable } from 'vs/base/common/lifecycle';
import URI from 'vs/base/common/uri';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { CellType, NotebookChangeType } from 'sql/parts/notebook/models/contracts';
import { INotebookManager } from 'sql/services/notebook/notebookService';
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
import { NotebookConnection } from 'sql/parts/notebook/models/notebookConnection';
import { IConnectionManagementService } from 'sql/parts/connection/common/connectionManagement';
export interface IClientSessionOptions {
notebookUri: URI;
notebookManager: INotebookManager;
notificationService: INotificationService;
}
/**
* The interface of client session object.
*
* The client session represents the link between
* a path and its kernel for the duration of the lifetime
* of the session object. The session can have no current
* kernel, and can start a new kernel at any time.
*/
export interface IClientSession extends IDisposable {
/**
* A signal emitted when the session is shut down.
*/
readonly terminated: Event<void>;
/**
* A signal emitted when the kernel changes.
*/
readonly kernelChanged: Event<nb.IKernelChangedArgs>;
/**
* A signal emitted when the kernel status changes.
*/
readonly statusChanged: Event<nb.ISession>;
/**
* A signal emitted for a kernel messages.
*/
readonly iopubMessage: Event<nb.IMessage>;
/**
* A signal emitted for an unhandled kernel message.
*/
readonly unhandledMessage: Event<nb.IMessage>;
/**
* A signal emitted when a session property changes.
*/
readonly propertyChanged: Event<'path' | 'name' | 'type'>;
/**
* The current kernel associated with the document.
*/
readonly kernel: nb.IKernel | null;
/**
* The current path associated with the client session.
*/
readonly notebookUri: URI;
/**
* The current name associated with the client session.
*/
readonly name: string;
/**
* The type of the client session.
*/
readonly type: string;
/**
* The current status of the client session.
*/
readonly status: nb.KernelStatus;
/**
* Whether the session is ready.
*/
readonly isReady: boolean;
/**
* Whether the session is in an unusable state
*/
readonly isInErrorState: boolean;
/**
* The error information, if this session is in an error state
*/
readonly errorMessage: string;
/**
* A promise that is fulfilled when the session is ready.
*/
readonly ready: Promise<void>;
/**
* A promise that is fulfilled when the session completes a kernel change.
*/
readonly kernelChangeCompleted: Promise<void>;
/**
* The kernel preference.
*/
kernelPreference: IKernelPreference;
/**
* The display name of the kernel.
*/
readonly kernelDisplayName: string;
/**
* Initializes the ClientSession, by starting the server and
* connecting to the SessionManager.
* This will optionally start a session if the kernel preferences
* indicate this is desired
*/
initialize(connection?: NotebookConnection): Promise<void>;
/**
* Change the current kernel associated with the document.
*/
changeKernel(
options: nb.IKernelSpec
): Promise<nb.IKernel>;
/**
* Kill the kernel and shutdown the session.
*
* @returns A promise that resolves when the session is shut down.
*/
shutdown(): Promise<void>;
/**
* Select a kernel for the session.
*/
selectKernel(): Promise<void>;
/**
* Restart the session.
*
* @returns A promise that resolves with whether the kernel has restarted.
*
* #### Notes
* If there is a running kernel, present a dialog.
* If there is no kernel, we start a kernel with the last run
* kernel name and resolves with `true`. If no kernel has been started,
* this is a no-op, and resolves with `false`.
*/
restart(): Promise<boolean>;
/**
* Change the session path.
*
* @param path - The new session path.
*
* @returns A promise that resolves when the session has renamed.
*
* #### Notes
* This uses the Jupyter REST API, and the response is validated.
* The promise is fulfilled on a valid response and rejected otherwise.
*/
setPath(path: string): Promise<void>;
/**
* Change the session name.
*/
setName(name: string): Promise<void>;
/**
* Change the session type.
*/
setType(type: string): Promise<void>;
/**
* Updates the connection
*/
updateConnection(connection: NotebookConnection): void;
}
export interface IDefaultConnection {
defaultConnection: IConnectionProfile;
otherConnections: IConnectionProfile[];
}
/**
* A kernel preference.
*/
export interface IKernelPreference {
/**
* The name of the kernel.
*/
readonly name?: string;
/**
* The preferred kernel language.
*/
readonly language?: string;
/**
* The id of an existing kernel.
*/
readonly id?: string;
/**
* Whether to prefer starting a kernel.
*/
readonly shouldStart?: boolean;
/**
* Whether a kernel can be started.
*/
readonly canStart?: boolean;
/**
* Whether to auto-start the default kernel if no matching kernel is found.
*/
readonly autoStartDefault?: boolean;
}
export interface INotebookModel {
/**
* Cell List for this model
*/
readonly cells: ReadonlyArray<ICellModel>;
/**
* Client Session in the notebook, used for sending requests to the notebook service
*/
readonly clientSession: IClientSession;
/**
* LanguageInfo saved in the query book
*/
readonly languageInfo: nb.ILanguageInfo;
/**
* The notebook service used to call backend APIs
*/
readonly notebookManager: INotebookManager;
/**
* Event fired on first initialization of the kernel and
* on subsequent change events
*/
readonly kernelChanged: Event<nb.IKernelChangedArgs>;
/**
* Event fired on first initialization of the kernels and
* on subsequent change events
*/
readonly kernelsChanged: Event<nb.IKernelSpec>;
/**
* Default kernel
*/
defaultKernel?: nb.IKernelSpec;
/**
* Event fired on first initialization of the contexts and
* on subsequent change events
*/
readonly contextsChanged: Event<void>;
/**
* The specs for available kernels, or undefined if these have
* not been loaded yet
*/
readonly specs: nb.IAllKernels | undefined;
/**
* The specs for available contexts, or undefined if these have
* not been loaded yet
*/
readonly contexts: IDefaultConnection | undefined;
/**
* The trusted mode of the Notebook
*/
trustedMode: boolean;
/**
* Change the current kernel from the Kernel dropdown
* @param displayName kernel name (as displayed in Kernel dropdown)
*/
changeKernel(displayName: string): void;
/**
* Change the current context (if applicable)
*/
changeContext(host: string, connection?: IConnectionProfile): void;
/**
* Find a cell's index given its model
*/
findCellIndex(cellModel: ICellModel): number;
/**
* Adds a cell to the index of the model
*/
addCell(cellType: CellType, index?: number): void;
/**
* Deletes a cell
*/
deleteCell(cellModel: ICellModel): void;
/**
* Save the model to its backing content manager.
* Serializes the model and then calls through to save it
*/
saveModel(): Promise<boolean>;
/**
* Notifies the notebook of a change in the cell
*/
onCellChange(cell: ICellModel, change: NotebookChangeType): void;
}
export interface ICellModelOptions {
notebook: INotebookModel;
isTrusted: boolean;
}
export interface ICellModel {
cellUri: URI;
id: string;
language: string;
source: string;
cellType: CellType;
trustedMode: boolean;
active: boolean;
readonly future: FutureInternal;
readonly outputs: ReadonlyArray<nb.ICellOutput>;
readonly onOutputsChanged: Event<ReadonlyArray<nb.ICellOutput>>;
setFuture(future: FutureInternal): void;
equals(cellModel: ICellModel): boolean;
toJSON(): nb.ICell;
}
export interface FutureInternal extends nb.IFuture {
inProgress: boolean;
}
export interface IModelFactory {
createCell(cell: nb.ICell, options: ICellModelOptions): ICellModel;
createClientSession(options: IClientSessionOptions): IClientSession;
}
export interface INotebookModelOptions {
/**
* Path to the local or remote notebook
*/
notebookUri: URI;
/**
* Factory for creating cells and client sessions
*/
factory: IModelFactory;
notebookManager: INotebookManager;
notificationService: INotificationService;
connectionService: IConnectionManagementService;
}
// TODO would like to move most of these constants to an extension
export namespace notebookConstants {
export const hadoopKnoxProviderName = 'HADOOP_KNOX';
export const python3 = 'python3';
export const python3DisplayName = 'Python 3';
export const defaultSparkKernel = 'pyspark3kernel';
export const hostPropName = 'host';
}

View File

@@ -0,0 +1,94 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { localize } from 'vs/nls';
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
export namespace constants {
export const hostPropName = 'host';
export const userPropName = 'user';
export const knoxPortPropName = 'knoxport';
export const clusterPropName = 'clustername';
export const passwordPropName = 'password';
export const defaultKnoxPort = '30443';
}
/**
* This is a temporary connection definition, with known properties for Knox gateway connections.
* Long term this should be refactored to an extension contribution
*
* @export
* @class NotebookConnection
*/
export class NotebookConnection {
private _host: string;
private _knoxPort: string;
constructor(private _connectionProfile: IConnectionProfile) {
if (!this._connectionProfile) {
throw new Error(localize('connectionInfoMissing', 'connectionInfo is required'));
}
}
public get connectionProfile(): IConnectionProfile {
return this._connectionProfile;
}
public get host(): string {
if (!this._host) {
this.ensureHostAndPort();
}
return this._host;
}
/**
* Sets host and port values, using any ',' or ':' delimited port in the hostname in
* preference to the built in port.
*/
private ensureHostAndPort(): void {
this._host = this.connectionProfile.options[constants.hostPropName];
this._knoxPort = NotebookConnection.getKnoxPortOrDefault(this.connectionProfile);
// determine whether the host has either a ',' or ':' in it
this.setHostAndPort(',');
this.setHostAndPort(':');
}
// set port and host correctly after we've identified that a delimiter exists in the host name
private setHostAndPort(delimeter: string): void {
let originalHost = this._host;
let index = originalHost.indexOf(delimeter);
if (index > -1) {
this._host = originalHost.slice(0, index);
this._knoxPort = originalHost.slice(index + 1);
}
}
public get user(): string {
return this._connectionProfile.options[constants.userPropName];
}
public get password(): string {
return this._connectionProfile.options[constants.passwordPropName];
}
public get knoxport(): string {
if (!this._knoxPort) {
this.ensureHostAndPort();
}
return this._knoxPort;
}
private static getKnoxPortOrDefault(connectionProfile: IConnectionProfile): string {
let port = connectionProfile.options[constants.knoxPortPropName];
if (!port) {
port = constants.defaultKnoxPort;
}
return port;
}
}

View File

@@ -0,0 +1,524 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { nb, connection } from 'sqlops';
import { localize } from 'vs/nls';
import { Event, Emitter } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import { CellModel } from './cell';
import { IClientSession, INotebookModel, IDefaultConnection, INotebookModelOptions, ICellModel, notebookConstants } from './modelInterfaces';
import { NotebookChangeType, CellTypes, CellType } from 'sql/parts/notebook/models/contracts';
import { nbversion } from '../notebookConstants';
import * as notebookUtils from '../notebookUtils';
import { INotebookManager } from 'sql/services/notebook/notebookService';
import { SparkMagicContexts } from 'sql/parts/notebook/models/sparkMagicContexts';
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
import { NotebookConnection } from 'sql/parts/notebook/models/notebookConnection';
import { INotification, Severity } from 'vs/platform/notification/common/notification';
import { Schemas } from 'vs/base/common/network';
/*
* Used to control whether a message in a dialog/wizard is displayed as an error,
* warning, or informational message. Default is error.
*/
export enum MessageLevel {
Error = 0,
Warning = 1,
Information = 2
}
export class ErrorInfo {
constructor(public readonly message: string, public readonly severity: MessageLevel) {
}
}
export interface NotebookContentChange {
/**
* What was the change that occurred?
*/
changeType: NotebookChangeType;
/**
* Optional cells that were changed
*/
cells?: ICellModel | ICellModel[];
/**
* Optional index of the change, indicating the cell at which an insert or
* delete occurred
*/
cellIndex?: number;
/**
* Optional value indicating if the notebook is in a dirty or clean state after this change
*
* @type {boolean}
* @memberof NotebookContentChange
*/
isDirty?: boolean;
}
export class NotebookModel extends Disposable implements INotebookModel {
private _contextsChangedEmitter = new Emitter<void>();
private _contentChangedEmitter = new Emitter<NotebookContentChange>();
private _kernelsChangedEmitter = new Emitter<nb.IKernelSpec>();
private _inErrorState: boolean = false;
private _clientSession: IClientSession;
private _sessionLoadFinished: Promise<void>;
private _onClientSessionReady = new Emitter<IClientSession>();
private _activeContexts: IDefaultConnection;
private _trustedMode: boolean;
private _cells: ICellModel[];
private _defaultLanguageInfo: nb.ILanguageInfo;
private onErrorEmitter = new Emitter<INotification>();
private _savedKernelInfo: nb.IKernelInfo;
private readonly _nbformat: number = nbversion.MAJOR_VERSION;
private readonly _nbformatMinor: number = nbversion.MINOR_VERSION;
private _hadoopConnection: NotebookConnection;
private _defaultKernel: nb.IKernelSpec;
private _activeCell: ICellModel;
constructor(private notebookOptions: INotebookModelOptions, startSessionImmediately?: boolean, private connectionProfile?: IConnectionProfile) {
super();
if (!notebookOptions || !notebookOptions.notebookUri || !notebookOptions.notebookManager) {
throw new Error('path or notebook service not defined');
}
if (startSessionImmediately) {
this.backgroundStartSession();
}
this._trustedMode = false;
}
public get notebookManager(): INotebookManager {
return this.notebookOptions.notebookManager;
}
public get hasServerManager(): boolean {
// If the service has a server manager, then we can show the start button
return !!this.notebookManager.serverManager;
}
public get contentChanged(): Event<NotebookContentChange> {
return this._contentChangedEmitter.event;
}
public get isSessionReady(): boolean {
return !!this._clientSession;
}
/**
* ClientSession object which handles management of a session instance,
* plus startup of the session manager which can return key metadata about the
* notebook environment
*/
public get clientSession(): IClientSession {
return this._clientSession;
}
public get kernelChanged(): Event<nb.IKernelChangedArgs> {
return this.clientSession.kernelChanged;
}
public get kernelsChanged(): Event<nb.IKernelSpec> {
return this._kernelsChangedEmitter.event;
}
public get defaultKernel(): nb.IKernelSpec {
return this._defaultKernel;
}
public get contextsChanged(): Event<void> {
return this._contextsChangedEmitter.event;
}
public get cells(): ICellModel[] {
return this._cells;
}
public get contexts(): IDefaultConnection {
return this._activeContexts;
}
public get specs(): nb.IAllKernels | undefined {
return this.notebookManager.sessionManager.specs;
}
public get inErrorState(): boolean {
return this._inErrorState;
}
public get onError(): Event<INotification> {
return this.onErrorEmitter.event;
}
public get trustedMode(): boolean {
return this._trustedMode;
}
public set trustedMode(isTrusted: boolean) {
this._trustedMode = isTrusted;
if (this._cells) {
this._cells.forEach(c => {
c.trustedMode = this._trustedMode;
});
}
}
public get hadoopConnection(): NotebookConnection {
return this._hadoopConnection;
}
/**
* Indicates the server has finished loading. It may have failed to load in
* which case the view will be in an error state.
*/
public get sessionLoadFinished(): Promise<void> {
return this._sessionLoadFinished;
}
/**
* Notifies when the client session is ready for use
*/
public get onClientSessionReady(): Event<IClientSession> {
return this._onClientSessionReady.event;
}
public async requestModelLoad(isTrusted: boolean = false): Promise<void> {
try {
this._trustedMode = isTrusted;
let contents = null;
if (this.notebookOptions.notebookUri.scheme !== Schemas.untitled) {
contents = await this.notebookManager.contentManager.getNotebookContents(this.notebookOptions.notebookUri);
}
let factory = this.notebookOptions.factory;
// if cells already exist, create them with language info (if it is saved)
this._cells = undefined;
if (contents) {
this._defaultLanguageInfo = this.getDefaultLanguageInfo(contents);
this._savedKernelInfo = this.getSavedKernelInfo(contents);
if (contents.cells && contents.cells.length > 0) {
this._cells = contents.cells.map(c => factory.createCell(c, { notebook: this, isTrusted: isTrusted }));
}
}
if (!this._cells) {
this._cells = [this.createCell(CellTypes.Code)];
}
} catch (error) {
this._inErrorState = true;
throw error;
}
}
public findCellIndex(cellModel: CellModel): number {
return this._cells.findIndex((cell) => cell.equals(cellModel));
}
public addCell(cellType: CellType, index?: number): void {
if (this.inErrorState || !this._cells) {
return;
}
let cell = this.createCell(cellType);
if (index !== undefined && index !== null && index >= 0 && index < this._cells.length) {
this._cells.splice(index, 0, cell);
} else {
this._cells.push(cell);
index = undefined;
}
this._contentChangedEmitter.fire({
changeType: NotebookChangeType.CellsAdded,
cells: [cell],
cellIndex: index
});
}
private createCell(cellType: CellType): ICellModel {
let singleCell: nb.ICell = {
cell_type: cellType,
source: '',
metadata: {},
execution_count: 1
};
return this.notebookOptions.factory.createCell(singleCell, { notebook: this, isTrusted: true });
}
deleteCell(cellModel: ICellModel): void {
if (this.inErrorState || !this._cells) {
return;
}
let index = this._cells.findIndex((cell) => cell.equals(cellModel));
if (index > -1) {
this._cells.splice(index, 1);
this._contentChangedEmitter.fire({
changeType: NotebookChangeType.CellDeleted,
cells: [cellModel],
cellIndex: index
});
} else {
this.notifyError(localize('deleteCellFailed', 'Failed to delete cell.'));
}
}
public get activeCell(): ICellModel {
return this._activeCell;
}
public set activeCell(value: ICellModel) {
this._activeCell = value;
}
private notifyError(error: string): void {
this.onErrorEmitter.fire({ message: error, severity: Severity.Error });
}
public backgroundStartSession(): void {
this._clientSession = this.notebookOptions.factory.createClientSession({
notebookUri: this.notebookOptions.notebookUri,
notebookManager: this.notebookManager,
notificationService: this.notebookOptions.notificationService
});
let id: string = this.connectionProfile ? this.connectionProfile.id : undefined;
this._hadoopConnection = this.connectionProfile ? new NotebookConnection(this.connectionProfile) : undefined;
this._clientSession.initialize(this._hadoopConnection);
this._sessionLoadFinished = this._clientSession.ready.then(async () => {
if (this._clientSession.isInErrorState) {
this.setErrorState(this._clientSession.errorMessage);
} else {
this._onClientSessionReady.fire(this._clientSession);
// Once session is loaded, can use the session manager to retrieve useful info
this.loadKernelInfo();
await this.loadActiveContexts(undefined);
}
});
}
private isValidKnoxConnection(profile: IConnectionProfile | connection.Connection) {
return profile && profile.providerName === notebookConstants.hadoopKnoxProviderName && profile.options[notebookConstants.hostPropName] !== undefined;
}
public get languageInfo(): nb.ILanguageInfo {
return this._defaultLanguageInfo;
}
private updateLanguageInfo(info: nb.ILanguageInfo) {
if (info) {
this._defaultLanguageInfo = info;
}
}
public changeKernel(displayName: string): void {
let spec = this.getSpecNameFromDisplayName(displayName);
this.doChangeKernel(spec);
}
public doChangeKernel(kernelSpec: nb.IKernelSpec): Promise<void> {
return this._clientSession.changeKernel(kernelSpec)
.then((kernel) => {
kernel.ready.then(() => {
if (kernel.info) {
this.updateLanguageInfo(kernel.info.language_info);
}
}, err => undefined);
return this.updateKernelInfo(kernel);
}).catch((err) => {
this.notifyError(localize('changeKernelFailed', 'Failed to change kernel: {0}', notebookUtils.getErrorMessage(err)));
// TODO should revert kernels dropdown
});
}
public changeContext(host: string, newConnection?: IConnectionProfile): void {
try {
if (!newConnection) {
newConnection = this._activeContexts.otherConnections.find((connection) => connection.options['host'] === host);
}
if (!newConnection && this._activeContexts.defaultConnection.options['host'] === host) {
newConnection = this._activeContexts.defaultConnection;
}
SparkMagicContexts.configureContext(this.notebookOptions);
this._hadoopConnection = new NotebookConnection(newConnection);
this.refreshConnections(newConnection);
this._clientSession.updateConnection(this._hadoopConnection);
} catch (err) {
let msg = notebookUtils.getErrorMessage(err);
this.notifyError(localize('changeContextFailed', 'Changing context failed: {0}', msg));
}
}
private refreshConnections(newConnection: IConnectionProfile) {
if (this.isValidKnoxConnection(newConnection) &&
this._hadoopConnection.connectionProfile.id !== '-1' &&
this._hadoopConnection.connectionProfile.id !== this._activeContexts.defaultConnection.id) {
// Put the defaultConnection to the head of otherConnections
if (this.isValidKnoxConnection(this._activeContexts.defaultConnection)) {
this._activeContexts.otherConnections = this._activeContexts.otherConnections.filter(conn => conn.id !== this._activeContexts.defaultConnection.id);
this._activeContexts.otherConnections.unshift(this._activeContexts.defaultConnection);
}
// Change the defaultConnection to newConnection
this._activeContexts.defaultConnection = newConnection;
}
}
private loadKernelInfo(): void {
this.clientSession.kernelChanged(async (e) => {
await this.loadActiveContexts(e);
});
try {
let sessionManager = this.notebookManager.sessionManager;
if (sessionManager) {
let defaultKernel = SparkMagicContexts.getDefaultKernel(sessionManager.specs, this.connectionProfile, this._savedKernelInfo, this.notebookOptions.notificationService);
this._defaultKernel = defaultKernel;
this._clientSession.statusChanged(async (session) => {
if (session && session.defaultKernelLoaded === true) {
this._kernelsChangedEmitter.fire(defaultKernel);
} else if (session && !session.defaultKernelLoaded) {
this._kernelsChangedEmitter.fire({ name: notebookConstants.python3, display_name: notebookConstants.python3DisplayName });
}
});
this.doChangeKernel(defaultKernel);
}
} catch (err) {
let msg = notebookUtils.getErrorMessage(err);
this.notifyError(localize('loadKernelFailed', 'Loading kernel info failed: {0}', msg));
}
}
// Get default language if saved in notebook file
// Otherwise, default to python
private getDefaultLanguageInfo(notebook: nb.INotebook): nb.ILanguageInfo {
return notebook!.metadata!.language_info || {
name: 'python',
version: '',
mimetype: 'x-python'
};
}
// Get default kernel info if saved in notebook file
private getSavedKernelInfo(notebook: nb.INotebook): nb.IKernelInfo {
return notebook!.metadata!.kernelspec;
}
private getSpecNameFromDisplayName(displayName: string): nb.IKernelSpec {
displayName = this.sanitizeDisplayName(displayName);
let kernel: nb.IKernelSpec = this.specs.kernels.find(k => k.display_name.toLowerCase() === displayName.toLowerCase());
if (!kernel) {
return undefined; // undefined is handled gracefully in the session to default to the default kernel
} else if (!kernel.name) {
kernel.name = this.specs.defaultKernel;
}
return kernel;
}
private setErrorState(errMsg: string): void {
this._inErrorState = true;
let msg = localize('startSessionFailed', 'Could not start session: {0}', errMsg);
this.notifyError(msg);
}
public dispose(): void {
super.dispose();
this.handleClosed();
}
public async handleClosed(): Promise<void> {
try {
if (this._clientSession) {
await this._clientSession.shutdown();
this._clientSession = undefined;
}
} catch (err) {
this.notifyError(localize('shutdownError', 'An error occurred when closing the notebook: {0}', err));
}
}
private async loadActiveContexts(kernelChangedArgs: nb.IKernelChangedArgs): Promise<void> {
this._activeContexts = await SparkMagicContexts.getContextsForKernel(this.notebookOptions.connectionService, kernelChangedArgs, this.connectionProfile);
this._contextsChangedEmitter.fire();
if (this.contexts.defaultConnection !== undefined && this.contexts.defaultConnection.options !== undefined) {
let defaultHadoopConnection = new NotebookConnection(this.contexts.defaultConnection);
this.changeContext(defaultHadoopConnection.host);
}
}
/**
* Sanitizes display name to remove IP address in order to fairly compare kernels
* In some notebooks, display name is in the format <kernel> (<ip address>)
* example: PySpark (25.23.32.4)
* @param displayName Display Name for the kernel
*/
public sanitizeDisplayName(displayName: string): string {
let name = displayName;
if (name) {
let index = name.indexOf('(');
name = (index > -1) ? name.substr(0, index - 1).trim() : name;
}
return name;
}
public async saveModel(): Promise<boolean> {
let notebook = this.toJSON();
if (!notebook) {
return false;
}
await this.notebookManager.contentManager.save(this.notebookOptions.notebookUri, notebook);
this._contentChangedEmitter.fire({
changeType: NotebookChangeType.DirtyStateChanged,
isDirty: false
});
return true;
}
private async updateKernelInfo(kernel: nb.IKernel): Promise<void> {
if (kernel) {
try {
let spec = await kernel.getSpec();
this._savedKernelInfo = {
name: kernel.name,
display_name: spec.display_name,
language: spec.language
};
} catch (err) {
// Don't worry about this for now. Just use saved values
}
}
}
/**
* Serialize the model to JSON.
*/
toJSON(): nb.INotebook {
let cells: nb.ICell[] = this.cells.map(c => c.toJSON());
let metadata = Object.create(null) as nb.INotebookMetadata;
// TODO update language and kernel when these change
metadata.kernelspec = this._savedKernelInfo;
metadata.language_info = this.languageInfo;
return {
metadata,
nbformat_minor: this._nbformatMinor,
nbformat: this._nbformat,
cells
};
}
onCellChange(cell: CellModel, change: NotebookChangeType): void {
let changeInfo: NotebookContentChange = {
changeType: change,
cells: [cell]
};
switch (change) {
case NotebookChangeType.CellOutputUpdated:
case NotebookChangeType.CellSourceUpdated:
changeInfo.changeType = NotebookChangeType.DirtyStateChanged;
changeInfo.isDirty = true;
break;
default:
// Do nothing for now
}
this._contentChangedEmitter.fire(changeInfo);
}
}

View File

@@ -0,0 +1,195 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as path from 'path';
import { nb } from 'sqlops';
import * as json from 'vs/base/common/json';
import * as pfs from 'vs/base/node/pfs';
import { localize } from 'vs/nls';
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
import { IDefaultConnection, notebookConstants, INotebookModelOptions } from 'sql/parts/notebook/models/modelInterfaces';
import * as notebookUtils from '../notebookUtils';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IConnectionManagementService } from 'sql/parts/connection/common/connectionManagement';
export class SparkMagicContexts {
public static get DefaultContext(): IDefaultConnection {
// TODO NOTEBOOK REFACTOR fix default connection handling
let defaultConnection: IConnectionProfile = <any> {
providerName: notebookConstants.hadoopKnoxProviderName,
id: '-1',
options:
{
host: localize('selectConnection', 'Select connection')
}
};
return {
// default context if no other contexts are applicable
defaultConnection: defaultConnection,
otherConnections: [defaultConnection]
};
}
/**
* Get all of the applicable contexts for a given kernel
* @param apiWrapper ApiWrapper
* @param kernelChangedArgs kernel changed args (both old and new kernel info)
* @param profile current connection profile
*/
public static async getContextsForKernel(connectionService: IConnectionManagementService, kernelChangedArgs?: nb.IKernelChangedArgs, profile?: IConnectionProfile): Promise<IDefaultConnection> {
let connections: IDefaultConnection = this.DefaultContext;
if (!profile) {
if (!kernelChangedArgs || !kernelChangedArgs.newValue ||
(kernelChangedArgs.oldValue && kernelChangedArgs.newValue.id === kernelChangedArgs.oldValue.id)) {
// nothing to do, kernels are the same or new kernel is undefined
return connections;
}
}
if (kernelChangedArgs && kernelChangedArgs.newValue && kernelChangedArgs.newValue.name) {
switch (kernelChangedArgs.newValue.name) {
case (notebookConstants.python3):
// python3 case, use this.DefaultContext for the only connection
break;
//TO DO: Handle server connections based on kernel type. Right now, we call the same method for all kernel types.
default:
connections = await this.getActiveContexts(connectionService, profile);
}
} else {
connections = await this.getActiveContexts(connectionService, profile);
}
return connections;
}
/**
* Get all active contexts and sort them
* @param apiWrapper ApiWrapper
* @param profile current connection profile
*/
public static async getActiveContexts(connectionService: IConnectionManagementService, profile: IConnectionProfile): Promise<IDefaultConnection> {
let defaultConnection: IConnectionProfile = SparkMagicContexts.DefaultContext.defaultConnection;
let activeConnections: IConnectionProfile[] = await connectionService.getActiveConnections();
// If no connections exist, only show 'n/a'
if (activeConnections && activeConnections.length > 0) {
// Remove all non-Spark connections
activeConnections = activeConnections.filter(conn => conn.providerName === notebookConstants.hadoopKnoxProviderName);
}
if (activeConnections.length === 0) {
return SparkMagicContexts.DefaultContext;
}
// If launched from the right click or server dashboard, connection profile data exists, so use that as default
if (profile && profile.options) {
let profileConnection = activeConnections.filter(conn => conn.options['host'] === profile.options['host']);
if (profileConnection) {
defaultConnection = profileConnection[0];
}
} else {
if (activeConnections.length > 0) {
defaultConnection = activeConnections[0];
} else {
// TODO NOTEBOOK REFACTOR change this so it's no longer incompatible with IConnectionProfile
defaultConnection = <IConnectionProfile> <any>{
providerName: notebookConstants.hadoopKnoxProviderName,
id: '-1',
options:
{
host: localize('addConnection', 'Add new connection')
}
};
activeConnections.push(defaultConnection);
}
}
return {
otherConnections: activeConnections,
defaultConnection: defaultConnection
};
}
public static async configureContext(options: INotebookModelOptions): Promise<object> {
let sparkmagicConfDir = path.join(notebookUtils.getUserHome(), '.sparkmagic');
// TODO NOTEBOOK REFACTOR re-enable this or move to extension. Requires config files to be available in order to work
// await notebookUtils.mkDir(sparkmagicConfDir);
// // Default to localhost in config file.
// let creds: ICredentials = {
// 'url': 'http://localhost:8088'
// };
// let configPath = notebookUtils.getTemplatePath(options.extensionContext.extensionPath, path.join('jupyter_config', 'sparkmagic_config.json'));
// let fileBuffer: Buffer = await pfs.readFile(configPath);
// let fileContents: string = fileBuffer.toString();
// let config: ISparkMagicConfig = json.parse(fileContents);
// SparkMagicContexts.updateConfig(config, creds, sparkmagicConfDir);
// let configFilePath = path.join(sparkmagicConfDir, 'config.json');
// await pfs.writeFile(configFilePath, JSON.stringify(config));
return {'SPARKMAGIC_CONF_DIR': sparkmagicConfDir};
}
/**
*
* @param specs kernel specs (comes from session manager)
* @param connectionInfo connection profile
* @param savedKernelInfo kernel info loaded from
*/
public static getDefaultKernel(specs: nb.IAllKernels, connectionInfo: IConnectionProfile, savedKernelInfo: nb.IKernelInfo, notificationService: INotificationService): nb.IKernelSpec {
let defaultKernel = specs.kernels.find((kernel) => kernel.name === specs.defaultKernel);
let profile = connectionInfo as IConnectionProfile;
if (specs && connectionInfo && profile.providerName === notebookConstants.hadoopKnoxProviderName) {
// set default kernel to default spark kernel if profile exists
// otherwise, set default to kernel info loaded from existing file
defaultKernel = !savedKernelInfo ? specs.kernels.find((spec) => spec.name === notebookConstants.defaultSparkKernel) : savedKernelInfo;
} else {
// Handle kernels
if (savedKernelInfo && savedKernelInfo.name.toLowerCase().indexOf('spark') > -1) {
notificationService.warn(localize('sparkKernelRequiresConnection', 'Cannot use kernel {0} as no connection is active. The default kernel of {1} will be used instead.', savedKernelInfo.display_name, defaultKernel.display_name));
}
}
// If no default kernel specified (should never happen), default to python3
if (!defaultKernel) {
defaultKernel = {
name: notebookConstants.python3,
display_name: notebookConstants.python3DisplayName
};
}
return defaultKernel;
}
private static updateConfig(config: ISparkMagicConfig, creds: ICredentials, homePath: string): void {
config.kernel_python_credentials = creds;
config.kernel_scala_credentials = creds;
config.kernel_r_credentials = creds;
config.logging_config.handlers.magicsHandler.home_path = homePath;
}
}
interface ICredentials {
'url': string;
}
interface ISparkMagicConfig {
kernel_python_credentials: ICredentials;
kernel_scala_credentials: ICredentials;
kernel_r_credentials: ICredentials;
logging_config: {
handlers: {
magicsHandler: {
home_path: string;
}
}
};
}
export interface IKernelJupyterID {
id: string;
jupyterId: string;
}

View File

@@ -0,0 +1,19 @@
<!--
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-->
<div style="overflow: hidden; width: 100%; height: 100%; display: flex; flex-flow: column">
<div #toolbar class="editor-toolbar actionbar-container" style="flex: 0 0 auto; display: flex; flex-flow: row; width: 100%; align-items: center; height: 36px">
</div>
<div class="scrollable" style="flex: 1 1 auto; position: relative">
<loading-spinner [loading]="isLoading"></loading-spinner>
<div class="notebook-cell" *ngFor="let cell of cells" (click)="selectCell(cell)" [class.active]="cell.active" (keydown)="onKeyDown($event)">
<code-cell-component *ngIf="cell.cellType === 'code'" [cellModel]="cell" [model]="model">
</code-cell-component>
<text-cell-component *ngIf="cell.cellType === 'markdown'" [cellModel]="cell">
</text-cell-component>
</div>
</div>
</div>

View File

@@ -0,0 +1,279 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import './notebookStyles';
import { nb } from 'sqlops';
import { OnInit, Component, Inject, forwardRef, ElementRef, ChangeDetectorRef, ViewChild } from '@angular/core';
import URI from 'vs/base/common/uri';
import { IColorTheme, IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
import * as themeColors from 'vs/workbench/common/theme';
import { INotificationService, INotification } from 'vs/platform/notification/common/notification';
import { localize } from 'vs/nls';
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
import { AngularDisposable } from 'sql/base/common/lifecycle';
import { CellTypes, CellType, NotebookChangeType } from 'sql/parts/notebook/models/contracts';
import { ICellModel, IModelFactory } from 'sql/parts/notebook/models/modelInterfaces';
import { IConnectionManagementService, IConnectionDialogService } from 'sql/parts/connection/common/connectionManagement';
import { INotebookService, INotebookParams, INotebookManager } from 'sql/services/notebook/notebookService';
import { IBootstrapParams } from 'sql/services/bootstrap/bootstrapService';
import { NotebookModel, ErrorInfo, MessageLevel, NotebookContentChange } from 'sql/parts/notebook/models/notebookModel';
import { ModelFactory } from 'sql/parts/notebook/models/modelFactory';
import * as notebookUtils from './notebookUtils';
import { Deferred } from 'sql/base/common/promise';
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
import { Taskbar } from 'sql/base/browser/ui/taskbar/taskbar';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { KernelsDropdown, AttachToDropdown, AddCellAction, TrustedAction } from 'sql/parts/notebook/notebookActions';
import { attachSelectBoxStyler } from 'vs/platform/theme/common/styler';
export const NOTEBOOK_SELECTOR: string = 'notebook-component';
@Component({
selector: NOTEBOOK_SELECTOR,
templateUrl: decodeURI(require.toUrl('./notebook.component.html'))
})
export class NotebookComponent extends AngularDisposable implements OnInit {
@ViewChild('toolbar', { read: ElementRef }) private toolbar: ElementRef;
private _model: NotebookModel;
private _isInErrorState: boolean = false;
private _errorMessage: string;
protected _actionBar: Taskbar;
private _activeCell: ICellModel;
protected isLoading: boolean;
private notebookManager: INotebookManager;
private _modelReadyDeferred = new Deferred<NotebookModel>();
private _modelRegisteredDeferred = new Deferred<NotebookModel>();
private profile: IConnectionProfile;
private _trustedAction: TrustedAction;
constructor(
@Inject(forwardRef(() => CommonServiceInterface)) private _bootstrapService: CommonServiceInterface,
@Inject(forwardRef(() => ChangeDetectorRef)) private _changeRef: ChangeDetectorRef,
@Inject(IWorkbenchThemeService) private themeService: IWorkbenchThemeService,
@Inject(IConnectionManagementService) private connectionManagementService: IConnectionManagementService,
@Inject(INotificationService) private notificationService: INotificationService,
@Inject(INotebookService) private notebookService: INotebookService,
@Inject(IBootstrapParams) private notebookParams: INotebookParams,
@Inject(IInstantiationService) private instantiationService: IInstantiationService,
@Inject(IContextMenuService) private contextMenuService: IContextMenuService,
@Inject(IContextViewService) private contextViewService: IContextViewService,
@Inject(IConnectionDialogService) private connectionDialogService: IConnectionDialogService
) {
super();
this.profile = this.notebookParams!.profile;
this.isLoading = true;
}
ngOnInit() {
this._register(this.themeService.onDidColorThemeChange(this.updateTheme, this));
this.updateTheme(this.themeService.getColorTheme());
this.initActionBar();
this.doLoad();
}
public get model(): NotebookModel {
return this._model;
}
public get modelRegistered(): Promise<NotebookModel> {
return this._modelRegisteredDeferred.promise;
}
protected get cells(): ReadonlyArray<ICellModel> {
return this._model ? this._model.cells : [];
}
private updateTheme(theme: IColorTheme): void {
let toolbarEl = <HTMLElement>this.toolbar.nativeElement;
toolbarEl.style.borderBottomColor = theme.getColor(themeColors.SIDE_BAR_BACKGROUND, true).toString();
}
public selectCell(cell: ICellModel) {
if (cell !== this._activeCell) {
if (this._activeCell) {
this._activeCell.active = false;
}
this._activeCell = cell;
this._activeCell.active = true;
this._model.activeCell = this._activeCell;
this._changeRef.detectChanges();
}
}
// Add cell based on cell type
public addCell(cellType: CellType)
{
this._model.addCell(cellType);
}
// Updates Notebook model's trust details
public updateModelTrustDetails(isTrusted: boolean)
{
this._model.trustedMode = isTrusted;
this._model.cells.forEach(cell => {
cell.trustedMode = isTrusted;
});
this.setDirty(true);
this._changeRef.detectChanges();
}
public onKeyDown(event) {
switch (event.key) {
case 'ArrowDown':
case 'ArrowRight':
let nextIndex = (this.findCellIndex(this._activeCell) + 1) % this.cells.length;
this.selectCell(this.cells[nextIndex]);
break;
case 'ArrowUp':
case 'ArrowLeft':
let index = this.findCellIndex(this._activeCell);
if (index === 0) {
index = this.cells.length;
}
this.selectCell(this.cells[--index]);
break;
default:
break;
}
}
private async doLoad(): Promise<void> {
try {
await this.loadModel();
this.setLoading(false);
this._modelReadyDeferred.resolve(this._model);
} catch (error) {
this.setViewInErrorState(localize('displayFailed', 'Could not display contents: {0}', error));
this.setLoading(false);
this._modelReadyDeferred.reject(error);
}
}
private setLoading(isLoading: boolean): void {
this.isLoading = isLoading;
this._changeRef.detectChanges();
}
private async loadModel(): Promise<void> {
this.notebookManager = await this.notebookService.getOrCreateNotebookManager(this.notebookParams.providerId, this.notebookParams.notebookUri);
let model = new NotebookModel({
factory: this.modelFactory,
notebookUri: this.notebookParams.notebookUri,
connectionService: this.connectionManagementService,
notificationService: this.notificationService,
notebookManager: this.notebookManager
}, false, this.profile);
model.onError((errInfo: INotification) => this.handleModelError(errInfo));
await model.requestModelLoad(this.notebookParams.isTrusted);
model.contentChanged((change) => this.handleContentChanged(change));
this._model = model;
this.updateToolbarComponents(this._model.trustedMode);
this._register(model);
this._modelRegisteredDeferred.resolve(this._model);
model.backgroundStartSession();
this._changeRef.detectChanges();
}
// Updates toolbar components
private updateToolbarComponents(isTrusted: boolean)
{
if(this._trustedAction)
{
this._trustedAction.enabled = true;
this._trustedAction.trusted = isTrusted;
}
}
private get modelFactory(): IModelFactory {
if (!this.notebookParams.modelFactory) {
this.notebookParams.modelFactory = new ModelFactory();
}
return this.notebookParams.modelFactory;
}
private handleModelError(notification: INotification): void {
this.notificationService.notify(notification);
}
private handleContentChanged(change: NotebookContentChange) {
// Note: for now we just need to set dirty state and refresh the UI.
this.setDirty(true);
this._changeRef.detectChanges();
}
findCellIndex(cellModel: ICellModel): number {
return this._model.cells.findIndex((cell) => cell.id === cellModel.id);
}
private setViewInErrorState(error: any): any {
this._isInErrorState = true;
this._errorMessage = notebookUtils.getErrorMessage(error);
// For now, send message as error notification #870 covers having dedicated area for this
this.notificationService.error(error);
}
protected initActionBar() {
let kernelContainer = document.createElement('div');
let kernelDropdown = new KernelsDropdown(kernelContainer, this.contextViewService, this.modelRegistered);
kernelDropdown.render(kernelContainer);
attachSelectBoxStyler(kernelDropdown, this.themeService);
let attachToContainer = document.createElement('div');
let attachTodropdwon = new AttachToDropdown(attachToContainer, this.contextViewService, this.modelRegistered,
this.connectionManagementService, this.connectionDialogService, this.notificationService);
attachTodropdwon.render(attachToContainer);
attachSelectBoxStyler(attachTodropdwon, this.themeService);
let attachToInfoText = document.createElement('div');
attachToInfoText.className = 'notebook-info-label';
attachToInfoText.innerText = 'Attach To: ';
let addCodeCellButton = new AddCellAction('notebook.AddCodeCell', localize('code', 'Code'), 'notebook-button icon-add');
addCodeCellButton.cellType = CellTypes.Code;
let addTextCellButton = new AddCellAction('notebook.AddTextCell',localize('text', 'Text'), 'notebook-button icon-add');
addTextCellButton.cellType = CellTypes.Markdown;
this._trustedAction = this.instantiationService.createInstance(TrustedAction, 'notebook.Trusted');
this._trustedAction.enabled = false;
let taskbar = <HTMLElement>this.toolbar.nativeElement;
this._actionBar = new Taskbar(taskbar, this.contextMenuService);
this._actionBar.context = this;
this._actionBar.setContent([
{ element: kernelContainer },
{ element: attachToContainer },
{ action: addCodeCellButton},
{ action: addTextCellButton},
{ action: this._trustedAction}
]);
}
public async save(): Promise<boolean> {
try {
let saved = await this._model.saveModel();
return saved;
} catch (err) {
this.notificationService.error(localize('saveFailed', 'Failed to save notebook: {0}', notebookUtils.getErrorMessage(err)));
return false;
}
}
private setDirty(isDirty: boolean): void {
// TODO reenable handling of isDirty
// if (this.editor) {
// this.editor.isDirty = isDirty;
// }
}
}

View File

@@ -0,0 +1,88 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Registry } from 'vs/platform/registry/common/platform';
import { EditorDescriptor, IEditorRegistry, Extensions as EditorExtensions } from 'vs/workbench/browser/editor';
import { IConfigurationRegistry, Extensions as ConfigExtensions } from 'vs/platform/configuration/common/configurationRegistry';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
import { Action } from 'vs/base/common/actions';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { TPromise } from 'vs/base/common/winjs.base';
import { Schemas } from 'vs/base/common/network';
import URI from 'vs/base/common/uri';
import { localize } from 'vs/nls';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { NotebookInput, NotebookInputModel, notebooksEnabledCondition } from 'sql/parts/notebook/notebookInput';
import { NotebookEditor } from 'sql/parts/notebook/notebookEditor';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
let counter = 0;
/**
* todo: Will remove this code.
* This is the entry point to open the new Notebook
*/
export class NewNotebookAction extends Action {
public static ID = 'workbench.action.newnotebook';
public static LABEL = localize('workbench.action.newnotebook.description', 'New Notebook');
constructor(
id: string,
label: string,
@IEditorService private _editorService: IEditorService,
@IInstantiationService private _instantiationService: IInstantiationService
) {
super(id, label);
}
public run(): TPromise<void> {
let title = `Untitled-${counter++}`;
let untitledUri = URI.from({ scheme: Schemas.untitled, path: title });
let model = new NotebookInputModel(untitledUri, undefined, false, undefined);
let input = this._instantiationService.createInstance(NotebookInput, title, model);
return this._editorService.openEditor(input, { pinned: true }).then(() => undefined);
}
}
// Model View editor registration
const viewModelEditorDescriptor = new EditorDescriptor(
NotebookEditor,
NotebookEditor.ID,
'Notebook'
);
Registry.as<IEditorRegistry>(EditorExtensions.Editors)
.registerEditor(viewModelEditorDescriptor, [new SyncDescriptor(NotebookInput)]);
// Feature flag for built-in Notebooks. Will be removed in the future.
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigExtensions.Configuration);
configurationRegistry.registerConfiguration({
'id': 'notebook',
'title': 'Notebook',
'type': 'object',
'properties': {
'notebook.enabled': {
'type': 'boolean',
'default': false,
'description': localize('notebook.enabledDescription', 'Enable viewing notebook files using built-in notebook editor.')
}
}
});
// this is the entry point to open the new Notebook
CommandsRegistry.registerCommand(NewNotebookAction.ID, serviceAccessor => {
serviceAccessor.get(IInstantiationService).createInstance(NewNotebookAction, NewNotebookAction.ID, NewNotebookAction.LABEL).run();
});
MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
command: {
id: NewNotebookAction.ID,
title:NewNotebookAction.LABEL,
},
when: notebooksEnabledCondition
});

View File

@@ -0,0 +1,64 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.notebookEditor .editor-toolbar {
border-bottom-width: 1px;
border-bottom-style: solid;
}
.notebookEditor .notebook-cell {
margin: 10px 20px 10px;
border-width: 1px;
border-style: solid;
}
.notebookEditor .notebook-info-label {
padding-right: 5px;
text-align: center;
display: flex;
align-items: center;
}
.notebookEditor .actionbar-container .monaco-action-bar > ul.actions-container {
padding-top: 0px;
}
.notebookEditor .notebook-button {
display: inline-block;
width: 100%;
padding: 0px;
text-align: center;
cursor: pointer;
padding-left: 15px;
background-size: 11px;
margin-right: 0.3em;
font-size: 11px;
}
.notebookEditor .notebook-button.icon-add{
background-image: url("./media/light/add.svg");
}
.vs-dark .notebookEditor .notebook-button.icon-add,
.hc-black .notebookEditor .notebook-button.icon-add{
background-image: url("./media/dark/add_inverse.svg");
}
.notebookEditor .notebook-button.icon-trusted{
background-image: url("./media/light/trusted.svg");
}
.vs-dark .notebookEditor .notebook-button.icon-trusted,
.hc-black .notebookEditor .notebook-button.icon-trusted{
background-image: url("./media/dark/trusted_inverse.svg");
}
.notebookEditor .notebook-button.icon-notTrusted{
background-image: url("./media/light/nottrusted.svg");
}
.vs-dark .notebookEditor .notebook-button.icon-notTrusted,
.hc-black .notebookEditor .notebook-button.icon-notTrusted{
background-image: url("./media/dark/nottrusted_inverse.svg");
}

View File

@@ -0,0 +1,79 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { forwardRef, NgModule, ComponentFactoryResolver, Inject, ApplicationRef } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { CommonModule, APP_BASE_HREF } from '@angular/common';
import { BrowserModule } from '@angular/platform-browser';
import { Extensions, IComponentRegistry } from 'sql/platform/dashboard/common/modelComponentRegistry';
import { ComponentHostDirective } from 'sql/parts/dashboard/common/componentHost.directive';
import { IBootstrapParams, ISelector, providerIterator } from 'sql/services/bootstrap/bootstrapService';
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
import { Checkbox } from 'sql/base/browser/ui/checkbox/checkbox.component';
import { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox.component';
import { EditableDropDown } from 'sql/base/browser/ui/editableDropdown/editableDropdown.component';
import { InputBox } from 'sql/base/browser/ui/inputBox/inputBox.component';
import { NotebookComponent } from 'sql/parts/notebook/notebook.component';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { Registry } from 'vs/platform/registry/common/platform';
import { CodeComponent } from 'sql/parts/notebook/cellViews/code.component';
import { CodeCellComponent } from 'sql/parts/notebook/cellViews/codeCell.component';
import { TextCellComponent } from 'sql/parts/notebook/cellViews/textCell.component';
import { OutputAreaComponent } from 'sql/parts/notebook/cellViews/outputArea.component';
import { OutputComponent } from 'sql/parts/notebook/cellViews/output.component';
import LoadingSpinner from 'sql/parts/modelComponents/loadingSpinner.component';
export const NotebookModule = (params, selector: string, instantiationService: IInstantiationService): any => {
@NgModule({
declarations: [
Checkbox,
SelectBox,
EditableDropDown,
InputBox,
LoadingSpinner,
CodeComponent,
CodeCellComponent,
TextCellComponent,
NotebookComponent,
ComponentHostDirective,
OutputAreaComponent,
OutputComponent
],
entryComponents: [NotebookComponent],
imports: [
FormsModule,
CommonModule,
BrowserModule
],
providers: [
{ provide: APP_BASE_HREF, useValue: '/' },
CommonServiceInterface,
{ provide: IBootstrapParams, useValue: params },
{ provide: ISelector, useValue: selector },
...providerIterator(instantiationService)
]
})
class ModuleClass {
constructor(
@Inject(forwardRef(() => ComponentFactoryResolver)) private _resolver: ComponentFactoryResolver,
@Inject(ISelector) private selector: string
) {
}
ngDoBootstrap(appRef: ApplicationRef) {
const factoryWrapper: any = this._resolver.resolveComponentFactory(NotebookComponent);
factoryWrapper.factory.selector = this.selector;
appRef.bootstrap(factoryWrapper);
}
}
return ModuleClass;
};

View File

@@ -0,0 +1,310 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as sqlops from 'sqlops';
import { Action } from 'vs/base/common/actions';
import { TPromise } from 'vs/base/common/winjs.base';
import { localize } from 'vs/nls';
import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview';
import { INotificationService, Severity, INotificationActions } from 'vs/platform/notification/common/notification';
import { SelectBox, ISelectBoxOptionsWithLabel } from 'sql/base/browser/ui/selectBox/selectBox';
import { INotebookModel, notebookConstants } from 'sql/parts/notebook/models/modelInterfaces';
import { CellType } from 'sql/parts/notebook/models/contracts';
import { NotebookComponent } from 'sql/parts/notebook/notebook.component';
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
import { IConnectionManagementService, IConnectionDialogService } from 'sql/parts/connection/common/connectionManagement';
import { getErrorMessage } from 'sql/parts/notebook/notebookUtils';
const msgLoading = localize('loading', 'Loading kernels...');
const kernelLabel: string = localize('Kernel', 'Kernel: ');
const attachToLabel: string = localize('AttachTo', 'Attach to: ');
const msgLoadingContexts = localize('loadingContexts', 'Loading contexts...');
const msgAddNewConnection = localize('addNewConnection', 'Add new connection');
const msgSelectConnection = localize('selectConnection', 'Select connection');
const msgConnectionNotApplicable = localize('connectionNotSupported', 'n/a');
// Action to add a cell to notebook based on cell type(code/markdown).
export class AddCellAction extends Action {
public cellType: CellType;
constructor(
id: string, label: string, cssClass: string
) {
super(id, label, cssClass);
}
public run(context: NotebookComponent): TPromise<boolean> {
return new TPromise<boolean>((resolve, reject) => {
try {
context.addCell(this.cellType);
resolve(true);
} catch (e) {
reject(e);
}
});
}
}
export interface IToggleableState {
baseClass?: string;
shouldToggleTooltip?: boolean;
toggleOnClass: string;
toggleOnLabel: string;
toggleOffLabel: string;
toggleOffClass: string;
isOn: boolean;
}
export abstract class ToggleableAction extends Action {
constructor(id: string, protected state: IToggleableState) {
super(id, '');
this.updateLabelAndIcon();
}
private updateLabelAndIcon() {
if (this.state.shouldToggleTooltip) {
this.tooltip = this.state.isOn ? this.state.toggleOnLabel : this.state.toggleOffLabel;
} else {
this.label = this.state.isOn ? this.state.toggleOnLabel : this.state.toggleOffLabel;
}
let classes = this.state.baseClass ? `${this.state.baseClass} ` : '';
classes += this.state.isOn ? this.state.toggleOnClass : this.state.toggleOffClass;
this.class = classes;
}
protected toggle(isOn: boolean): void {
this.state.isOn = isOn;
this.updateLabelAndIcon();
}
}
export class TrustedAction extends ToggleableAction {
// Constants
private static readonly trustedLabel = localize('trustLabel', 'Trusted');
private static readonly notTrustedLabel = localize('untrustLabel', 'Not Trusted');
private static readonly alreadyTrustedMsg = localize('alreadyTrustedMsg', 'Notebook is already trusted.');
private static readonly baseClass = 'notebook-button';
private static readonly trustedCssClass = 'icon-trusted';
private static readonly notTrustedCssClass = 'icon-notTrusted';
// Properties
constructor(
id: string,
@INotificationService private _notificationService: INotificationService
) {
super(id, {
baseClass: TrustedAction.baseClass,
toggleOnLabel: TrustedAction.trustedLabel,
toggleOnClass: TrustedAction.trustedCssClass,
toggleOffLabel: TrustedAction.notTrustedLabel,
toggleOffClass: TrustedAction.notTrustedCssClass,
isOn: false
});
}
public get trusted(): boolean {
return this.state.isOn;
}
public set trusted(value: boolean) {
this.toggle(value);
}
public run(context: NotebookComponent): TPromise<boolean> {
let self = this;
return new TPromise<boolean>((resolve, reject) => {
try {
if (self.trusted) {
const actions: INotificationActions = { primary: [] };
self._notificationService.notify({ severity: Severity.Info, message: TrustedAction.alreadyTrustedMsg, actions });
}
else {
self.trusted = !self.trusted;
context.updateModelTrustDetails(self.trusted);
}
resolve(true);
} catch (e) {
reject(e);
}
});
}
}
export class KernelsDropdown extends SelectBox {
private model: INotebookModel;
constructor(container: HTMLElement, contextViewProvider: IContextViewProvider, modelRegistered: Promise<INotebookModel>
) {
let selectBoxOptionsWithLabel: ISelectBoxOptionsWithLabel = {
labelText: kernelLabel,
labelOnTop: false
};
super([msgLoading], msgLoading, contextViewProvider, container, selectBoxOptionsWithLabel);
if (modelRegistered) {
modelRegistered
.then((model) => this.updateModel(model))
.catch((err) => {
// No-op for now
});
}
this.onDidSelect(e => this.doChangeKernel(e.selected));
}
updateModel(model: INotebookModel): void {
this.model = model;
model.kernelsChanged((defaultKernel) => {
this.updateKernel(defaultKernel);
});
if (model.clientSession) {
model.clientSession.kernelChanged((changedArgs: sqlops.nb.IKernelChangedArgs) => {
if (changedArgs.newValue) {
this.updateKernel(changedArgs.newValue);
}
});
}
}
// Update SelectBox values
private updateKernel(defaultKernel: sqlops.nb.IKernelSpec) {
let specs = this.model.specs;
if (specs && specs.kernels) {
let index = specs.kernels.findIndex((kernel => kernel.name === defaultKernel.name));
this.setOptions(specs.kernels.map(kernel => kernel.display_name), index);
}
}
public doChangeKernel(displayName: string): void {
this.model.changeKernel(displayName);
}
}
export class AttachToDropdown extends SelectBox {
private model: INotebookModel;
constructor(container: HTMLElement, contextViewProvider: IContextViewProvider, modelRegistered: Promise<INotebookModel>,
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
@IConnectionDialogService private _connectionDialogService: IConnectionDialogService,
@INotificationService private _notificationService: INotificationService) {
super([msgLoadingContexts], msgLoadingContexts, contextViewProvider, container, { labelText: attachToLabel, labelOnTop: false } as ISelectBoxOptionsWithLabel);
if (modelRegistered) {
modelRegistered
.then((model) => this.updateModel(model))
.catch((err) => {
// No-op for now
});
}
this.onDidSelect(e => {
let connection = this.model.contexts.otherConnections.find((c) => c.options.host === e.selected);
this.doChangeContext(connection);
});
}
public updateModel(model: INotebookModel): void {
this.model = model;
model.contextsChanged(() => {
if (this.model.clientSession.kernel && this.model.clientSession.kernel.name) {
let currentKernel = this.model.clientSession.kernel.name;
this.loadAttachToDropdown(this.model, currentKernel);
}
});
}
// Load "Attach To" dropdown with the values corresponding to Kernel dropdown
public async loadAttachToDropdown(model: INotebookModel, currentKernel: string): Promise<void> {
if (currentKernel === notebookConstants.python3) {
this.setOptions([msgConnectionNotApplicable]);
this.disable();
}
else {
let hadoopConnections = this.getHadoopConnections(model);
this.enable();
if (hadoopConnections.length === 1 && hadoopConnections[0] === msgAddNewConnection) {
hadoopConnections.unshift(msgSelectConnection);
this.selectWithOptionName(msgSelectConnection);
}
else {
hadoopConnections.push(msgAddNewConnection);
}
this.setOptions(hadoopConnections);
}
}
//Get hadoop connections from context
public getHadoopConnections(model: INotebookModel): string[] {
let otherHadoopConnections: IConnectionProfile[] = [];
model.contexts.otherConnections.forEach((conn) => { otherHadoopConnections.push(conn); });
this.selectWithOptionName(model.contexts.defaultConnection.options.host);
otherHadoopConnections = this.setHadoopConnectionsList(model.contexts.defaultConnection, model.contexts.otherConnections);
let hadoopConnections = otherHadoopConnections.map((context) => context.options.host);
return hadoopConnections;
}
private setHadoopConnectionsList(defaultHadoopConnection: IConnectionProfile, otherHadoopConnections: IConnectionProfile[]) {
if (defaultHadoopConnection.options.host !== msgSelectConnection) {
otherHadoopConnections = otherHadoopConnections.filter(conn => conn.options.host !== defaultHadoopConnection.options.host);
otherHadoopConnections.unshift(defaultHadoopConnection);
if (otherHadoopConnections.length > 1) {
otherHadoopConnections = otherHadoopConnections.filter(val => val.options.host !== msgSelectConnection);
}
}
return otherHadoopConnections;
}
public doChangeContext(connection?: IConnectionProfile): void {
if (this.value === msgAddNewConnection) {
this.openConnectionDialog();
} else {
this.model.changeContext(this.value, connection);
}
}
/**
* Open connection dialog
* Enter server details and connect to a server from the dialog
* Bind the server value to 'Attach To' drop down
* Connected server is displayed at the top of drop down
**/
public async openConnectionDialog(): Promise<void> {
try {
//TODO: Figure out how to plumb through the correct provider here
await this._connectionDialogService.openDialogAndWait(this._connectionManagementService, { connectionType: 1, providers: [notebookConstants.hadoopKnoxProviderName] }).then(connection => {
let attachToConnections = this.values;
if (!connection) {
this.loadAttachToDropdown(this.model, this.model.clientSession.kernel.name);
return;
}
let connectedServer = connection.options[notebookConstants.hostPropName];
//Check to see if the same host is already there in dropdown. We only have host names in dropdown
if (attachToConnections.some(val => val === connectedServer)) {
this.loadAttachToDropdown(this.model, this.model.clientSession.kernel.name);
this.doChangeContext();
return;
}
else {
attachToConnections.unshift(connectedServer);
}
//To ignore n/a after we have at least one valid connection
attachToConnections = attachToConnections.filter(val => val !== msgSelectConnection);
let index = attachToConnections.findIndex((connection => connection === connectedServer));
this.setOptions(attachToConnections);
if (!index || index < 0 || index >= attachToConnections.length) {
index = 0;
}
this.select(index);
// Call doChangeContext to set the newly chosen connection in the model
this.doChangeContext(connection);
});
}
catch (error) {
const actions: INotificationActions = { primary: [] };
this._notificationService.notify({ severity: Severity.Error, message: getErrorMessage(error), actions });
}
}
}

View File

@@ -0,0 +1,19 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
export namespace nbversion {
/**
* The major version of the notebook format.
*/
export const MAJOR_VERSION: number = 4;
/**
* The minor version of the notebook format.
*/
export const MINOR_VERSION: number = 2;
}

View File

@@ -0,0 +1,101 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { TPromise } from 'vs/base/common/winjs.base';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
import { EditorOptions } from 'vs/workbench/common/editor';
import * as DOM from 'vs/base/browser/dom';
import { $ } from 'vs/base/browser/builder';
import { bootstrapAngular } from 'sql/services/bootstrap/bootstrapService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { CancellationToken } from 'vs/base/common/cancellation';
import { NotebookInput } from 'sql/parts/notebook/notebookInput';
import { NotebookModule } from 'sql/parts/notebook/notebook.module';
import { NOTEBOOK_SELECTOR } from 'sql/parts/notebook/notebook.component';
import { INotebookParams, DEFAULT_NOTEBOOK_PROVIDER } from 'sql/services/notebook/notebookService';
export class NotebookEditor extends BaseEditor {
public static ID: string = 'workbench.editor.notebookEditor';
private _notebookContainer: HTMLElement;
protected _input: NotebookInput;
constructor(
@ITelemetryService telemetryService: ITelemetryService,
@IThemeService themeService: IThemeService,
@IInstantiationService private instantiationService: IInstantiationService,
) {
super(NotebookEditor.ID, telemetryService, themeService);
}
public get input(): NotebookInput {
return this._input;
}
/**
* Called to create the editor in the parent element.
*/
public createEditor(parent: HTMLElement): void {
}
/**
* Sets focus on this editor. Specifically, it sets the focus on the hosted text editor.
*/
public focus(): void {
}
/**
* Updates the internal variable keeping track of the editor's size, and re-calculates the sash position.
* To be called when the container of this editor changes size.
*/
public layout(dimension: DOM.Dimension): void {
}
public setInput(input: NotebookInput, options: EditorOptions): TPromise<void> {
if (this.input && this.input.matches(input)) {
return TPromise.as(undefined);
}
const parentElement = this.getContainer();
super.setInput(input, options, CancellationToken.None);
$(parentElement).clearChildren();
if (!input.hasBootstrapped) {
let container = DOM.$<HTMLElement>('.notebookEditor');
container.style.height = '100%';
this._notebookContainer = DOM.append(parentElement, container);
this.input.container = this._notebookContainer;
return TPromise.wrap<void>(this.bootstrapAngular(input));
} else {
this._notebookContainer = DOM.append(parentElement, this.input.container);
return TPromise.wrap<void>(null);
}
}
/**
* Load the angular components and record for this input that we have done so
*/
private bootstrapAngular(input: NotebookInput): void {
// Get the bootstrap params and perform the bootstrap
input.hasBootstrapped = true;
let params: INotebookParams = {
notebookUri: input.notebookUri,
providerId: input.providerId ? input.providerId : DEFAULT_NOTEBOOK_PROVIDER,
isTrusted: input.isTrusted
};
bootstrapAngular(this.instantiationService,
NotebookModule,
this._notebookContainer,
NOTEBOOK_SELECTOR,
params,
input
);
}
}

View File

@@ -0,0 +1,176 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { TPromise } from 'vs/base/common/winjs.base';
import { IEditorModel } from 'vs/platform/editor/common/editor';
import { EditorInput, EditorModel, ConfirmResult } from 'vs/workbench/common/editor';
import { Emitter, Event } from 'vs/base/common/event';
import URI from 'vs/base/common/uri';
import { IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { INotebookService } from 'sql/services/notebook/notebookService';
export type ModeViewSaveHandler = (handle: number) => Thenable<boolean>;
export let notebooksEnabledCondition = ContextKeyExpr.equals('config.notebook.enabled', true);
export class NotebookInputModel extends EditorModel {
private dirty: boolean;
private readonly _onDidChangeDirty: Emitter<void> = this._register(new Emitter<void>());
private _providerId: string;
constructor(public readonly notebookUri: URI, private readonly handle: number, private _isTrusted: boolean = false, private saveHandler?: ModeViewSaveHandler) {
super();
this.dirty = false;
}
public get providerId(): string {
return this._providerId;
}
public set providerId(value: string) {
this._providerId = value;
}
get isTrusted(): boolean {
return this._isTrusted;
}
get onDidChangeDirty(): Event<void> {
return this._onDidChangeDirty.event;
}
get isDirty(): boolean {
return this.dirty;
}
public setDirty(dirty: boolean): void {
if (this.dirty === dirty) {
return;
}
this.dirty = dirty;
this._onDidChangeDirty.fire();
}
save(): TPromise<boolean> {
if (this.saveHandler) {
return TPromise.wrap(this.saveHandler(this.handle));
}
return TPromise.wrap(true);
}
}
export class NotebookInputValidator {
constructor(@IContextKeyService private readonly _contextKeyService: IContextKeyService) {}
public isNotebookEnabled(): boolean {
return this._contextKeyService.contextMatchesRules(notebooksEnabledCondition);
}
}
export class NotebookInput extends EditorInput {
public static ID: string = 'workbench.editorinputs.notebookInput';
public hasBootstrapped = false;
// Holds the HTML content for the editor when the editor discards this input and loads another
private _parentContainer: HTMLElement;
constructor(private _title: string,
private _model: NotebookInputModel,
@INotebookService private notebookService: INotebookService
) {
super();
this._model.onDidChangeDirty(() => this._onDidChangeDirty.fire());
this.onDispose(() => {
if (this.notebookService) {
this.notebookService.handleNotebookClosed(this.notebookUri);
}
});
}
public get title(): string {
return this._title;
}
public get notebookUri(): URI {
return this._model.notebookUri;
}
public get providerId(): string {
return this._model.providerId;
}
public getTypeId(): string {
return NotebookInput.ID;
}
public resolve(refresh?: boolean): TPromise<IEditorModel> {
return undefined;
}
public getName(): string {
return this._title;
}
public get isTrusted(): boolean {
return this._model.isTrusted;
}
public dispose(): void {
this._disposeContainer();
super.dispose();
}
private _disposeContainer() {
if (!this._parentContainer) {
return;
}
let parentNode = this._parentContainer.parentNode;
if (parentNode) {
parentNode.removeChild(this._parentContainer);
this._parentContainer = null;
}
}
set container(container: HTMLElement) {
this._disposeContainer();
this._parentContainer = container;
}
get container(): HTMLElement {
return this._parentContainer;
}
/**
* An editor that is dirty will be asked to be saved once it closes.
*/
isDirty(): boolean {
return this._model.isDirty;
}
/**
* Subclasses should bring up a proper dialog for the user if the editor is dirty and return the result.
*/
confirmSave(): TPromise<ConfirmResult> {
// TODO #2530 support save on close / confirm save. This is significantly more work
// as we need to either integrate with textFileService (seems like this isn't viable)
// or register our own complimentary service that handles the lifecycle operations such
// as close all, auto save etc.
return TPromise.wrap(ConfirmResult.DONT_SAVE);
}
/**
* Saves the editor if it is dirty. Subclasses return a promise with a boolean indicating the success of the operation.
*/
save(): TPromise<boolean> {
return this._model.save();
}
}

View File

@@ -0,0 +1,50 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./notebook';
import { registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService';
import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme';
import { activeContrastBorder, buttonBackground } from 'vs/platform/theme/common/colorRegistry';
registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
// Active border
const activeBorder = theme.getColor(buttonBackground);
if (activeBorder) {
collector.addRule(`
.notebookEditor .notebook-cell.active {
border-color: ${activeBorder};
border-width: 2px;
}
`);
}
// Inactive border
const inactiveBorder = theme.getColor(SIDE_BAR_BACKGROUND);
if (inactiveBorder) {
collector.addRule(`
.notebookEditor .notebook-cell {
border-color: ${inactiveBorder};
border-width: 1px;
}
`);
}
// Styling with Outline color (e.g. high contrast theme)
const outline = theme.getColor(activeContrastBorder);
if (outline) {
collector.addRule(`
.notebookEditor .notebook-cell.active {
outline-color: ${outline};
outline-width: 1px;
outline-style: solid;
}
.notebookEditor .notebook-cell:hover:not(.active) {
outline-style: dashed;
}
`);
}
});

View File

@@ -0,0 +1,38 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { nb } from 'sqlops';
import * as os from 'os';
import * as pfs from 'vs/base/node/pfs';
import { localize } from 'vs/nls';
import { IOutputChannel } from 'vs/workbench/parts/output/common/output';
/**
* Test whether an output is from a stream.
*/
export function isStream(output: nb.ICellOutput): output is nb.IStreamResult {
return output.output_type === 'stream';
}
export function getErrorMessage(error: Error | string): string {
return (error instanceof Error) ? error.message : error;
}
export function getUserHome(): string {
return process.env.HOME || process.env.USERPROFILE;
}
export async function mkDir(dirPath: string, outputChannel?: IOutputChannel): Promise<void> {
let exists = await pfs.dirExists(dirPath);
if (!exists) {
if (outputChannel) {
outputChannel.append(localize('mkdirOutputMsg', '... Creating {0}', dirPath) + os.EOL);
}
await pfs.mkdirp(dirPath);
}
}

View File

@@ -0,0 +1,54 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
/**
* A type alias for a JSON primitive.
*/
export declare type JSONPrimitive = boolean | number | string | null;
/**
* A type alias for a JSON value.
*/
export declare type JSONValue = JSONPrimitive | JSONObject | JSONArray;
/**
* A type definition for a JSON object.
*/
export interface JSONObject {
[key: string]: JSONValue;
}
/**
* A type definition for a JSON array.
*/
export interface JSONArray extends Array<JSONValue> {
}
/**
* A type definition for a readonly JSON object.
*/
export interface ReadonlyJSONObject {
readonly [key: string]: ReadonlyJSONValue;
}
/**
* A type definition for a readonly JSON array.
*/
export interface ReadonlyJSONArray extends ReadonlyArray<ReadonlyJSONValue> {
}
/**
* A type alias for a readonly JSON value.
*/
export declare type ReadonlyJSONValue = JSONPrimitive | ReadonlyJSONObject | ReadonlyJSONArray;
/**
* Test whether a JSON value is a primitive.
*
* @param value - The JSON value of interest.
*
* @returns `true` if the value is a primitive,`false` otherwise.
*/
export function isPrimitive(value: any): boolean {
return (
value === null ||
typeof value === 'boolean' ||
typeof value === 'number' ||
typeof value === 'string'
);
}

View File

@@ -0,0 +1,87 @@
/*-----------------------------------------------------------------------------
| Copyright (c) Jupyter Development Team.
| Distributed under the terms of the Modified BSD License.
|----------------------------------------------------------------------------*/
import { IRenderMime } from './renderMimeInterfaces';
import { ReadonlyJSONObject } from './jsonext';
/**
* The default mime model implementation.
*/
export class MimeModel implements IRenderMime.IMimeModel {
/**
* Construct a new mime model.
*/
constructor(options: MimeModel.IOptions = {}) {
this.trusted = !!options.trusted;
this._data = options.data || {};
this._metadata = options.metadata || {};
this._callback = options.callback;
}
/**
* Whether the model is trusted.
*/
readonly trusted: boolean;
/**
* The data associated with the model.
*/
get data(): ReadonlyJSONObject {
return this._data;
}
/**
* The metadata associated with the model.
*/
get metadata(): ReadonlyJSONObject {
return this._metadata;
}
/**
* Set the data associated with the model.
*
* #### Notes
* Depending on the implementation of the mime model,
* this call may or may not have deferred effects,
*/
setData(options: IRenderMime.ISetDataOptions): void {
this._data = options.data || this._data;
this._metadata = options.metadata || this._metadata;
this._callback(options);
}
private _callback: (options: IRenderMime.ISetDataOptions) => void;
private _data: ReadonlyJSONObject;
private _metadata: ReadonlyJSONObject;
}
/**
* The namespace for MimeModel class statics.
*/
export namespace MimeModel {
/**
* The options used to create a mime model.
*/
export interface IOptions {
/**
* Whether the model is trusted. Defaults to `false`.
*/
trusted?: boolean;
/**
* A callback function for when the data changes.
*/
callback?: (options: IRenderMime.ISetDataOptions) => void;
/**
* The initial mime data.
*/
data?: ReadonlyJSONObject;
/**
* The initial mime metadata.
*/
metadata?: ReadonlyJSONObject;
}
}

View File

@@ -0,0 +1,494 @@
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
// Notebook format interfaces
// https://nbformat.readthedocs.io/en/latest/format_description.html
// https://github.com/jupyter/nbformat/blob/master/nbformat/v4/nbformat.v4.schema.json
import { JSONObject } from './jsonext';
import { nb } from 'sqlops';
/**
* A namespace for nbformat interfaces.
*/
export namespace nbformat {
/**
* The major version of the notebook format.
*/
export const MAJOR_VERSION: number = 4;
/**
* The minor version of the notebook format.
*/
export const MINOR_VERSION: number = 2;
/**
* The kernelspec metadata.
*/
export interface IKernelspecMetadata extends JSONObject {
name: string;
display_name: string;
}
/**
* The language info metatda
*/
export interface ILanguageInfoMetadata extends JSONObject {
name: string;
codemirror_mode?: string | JSONObject;
file_extension?: string;
mimetype?: string;
pygments_lexer?: string;
}
/**
* The default metadata for the notebook.
*/
export interface INotebookMetadata extends JSONObject {
kernelspec?: IKernelspecMetadata;
language_info?: ILanguageInfoMetadata;
orig_nbformat: number;
}
/**
* The notebook content.
*/
export interface INotebookContent {
metadata: INotebookMetadata;
nbformat_minor: number;
nbformat: number;
cells: ICell[];
}
/**
* A multiline string.
*/
export type MultilineString = string | string[];
/**
* A mime-type keyed dictionary of data.
*/
export interface IMimeBundle extends JSONObject {
[key: string]: MultilineString | JSONObject;
}
/**
* Media attachments (e.g. inline images).
*/
export interface IAttachments {
[key: string]: IMimeBundle;
}
/**
* The code cell's prompt number. Will be null if the cell has not been run.
*/
export type ExecutionCount = number | null;
/**
* Cell output metadata.
*/
export type OutputMetadata = JSONObject;
/**
* Validate a mime type/value pair.
*
* @param type - The mimetype name.
*
* @param value - The value associated with the type.
*
* @returns Whether the type/value pair are valid.
*/
export function validateMimeValue(
type: string,
value: MultilineString | JSONObject
): boolean {
// Check if "application/json" or "application/foo+json"
const jsonTest = /^application\/(.*?)+\+json$/;
const isJSONType = type === 'application/json' || jsonTest.test(type);
let isString = (x: any) => {
return Object.prototype.toString.call(x) === '[object String]';
};
// If it is an array, make sure if is not a JSON type and it is an
// array of strings.
if (Array.isArray(value)) {
if (isJSONType) {
return false;
}
let valid = true;
(value as string[]).forEach(v => {
if (!isString(v)) {
valid = false;
}
});
return valid;
}
// If it is a string, make sure we are not a JSON type.
if (isString(value)) {
return !isJSONType;
}
// It is not a string, make sure it is a JSON type.
if (!isJSONType) {
return false;
}
// It is a JSON type, make sure it is a valid JSON object.
// return JSONExt.isObject(value);
return true;
}
/**
* Cell-level metadata.
*/
export interface IBaseCellMetadata extends JSONObject {
/**
* Whether the cell is trusted.
*
* #### Notes
* This is not strictly part of the nbformat spec, but it is added by
* the contents manager.
*
* See https://jupyter-notebook.readthedocs.io/en/latest/security.html.
*/
trusted: boolean;
/**
* The cell's name. If present, must be a non-empty string.
*/
name: string;
/**
* The cell's tags. Tags must be unique, and must not contain commas.
*/
tags: string[];
}
/**
* The base cell interface.
*/
export interface IBaseCell {
/**
* String identifying the type of cell.
*/
cell_type: string;
/**
* Contents of the cell, represented as an array of lines.
*/
source: MultilineString;
/**
* Cell-level metadata.
*/
metadata: Partial<ICellMetadata>;
}
/**
* Metadata for the raw cell.
*/
export interface IRawCellMetadata extends IBaseCellMetadata {
/**
* Raw cell metadata format for nbconvert.
*/
format: string;
}
/**
* A raw cell.
*/
export interface IRawCell extends IBaseCell {
/**
* String identifying the type of cell.
*/
cell_type: 'raw';
/**
* Cell-level metadata.
*/
metadata: Partial<IRawCellMetadata>;
/**
* Cell attachments.
*/
attachments?: IAttachments;
}
/**
* A markdown cell.
*/
export interface IMarkdownCell extends IBaseCell {
/**
* String identifying the type of cell.
*/
cell_type: 'markdown';
/**
* Cell attachments.
*/
attachments?: IAttachments;
}
/**
* Metadata for a code cell.
*/
export interface ICodeCellMetadata extends IBaseCellMetadata {
/**
* Whether the cell is collapsed/expanded.
*/
collapsed: boolean;
/**
* Whether the cell's output is scrolled, unscrolled, or autoscrolled.
*/
scrolled: boolean | 'auto';
}
/**
* A code cell.
*/
export interface ICodeCell extends IBaseCell {
/**
* String identifying the type of cell.
*/
cell_type: 'code';
/**
* Cell-level metadata.
*/
metadata: Partial<ICodeCellMetadata>;
/**
* Execution, display, or stream outputs.
*/
outputs: IOutput[];
/**
* The code cell's prompt number. Will be null if the cell has not been run.
*/
execution_count: ExecutionCount;
}
/**
* An unrecognized cell.
*/
export interface IUnrecognizedCell extends IBaseCell { }
/**
* A cell union type.
*/
export type ICell = IRawCell | IMarkdownCell | ICodeCell | IUnrecognizedCell;
/**
* Test whether a cell is a raw cell.
*/
export function isRaw(cell: ICell): cell is IRawCell {
return cell.cell_type === 'raw';
}
/**
* Test whether a cell is a markdown cell.
*/
export function isMarkdown(cell: ICell): cell is IMarkdownCell {
return cell.cell_type === 'markdown';
}
/**
* Test whether a cell is a code cell.
*/
export function isCode(cell: ICell): cell is ICodeCell {
return cell.cell_type === 'code';
}
/**
* A union metadata type.
*/
export type ICellMetadata =
| IBaseCellMetadata
| IRawCellMetadata
| ICodeCellMetadata;
/**
* The valid output types.
*/
export type OutputType =
| 'execute_result'
| 'display_data'
| 'stream'
| 'error'
| 'update_display_data';
/**
* Result of executing a code cell.
*/
export interface IExecuteResult extends nb.ICellOutput {
/**
* Type of cell output.
*/
output_type: 'execute_result';
/**
* A result's prompt number.
*/
execution_count: ExecutionCount;
/**
* A mime-type keyed dictionary of data.
*/
data: IMimeBundle;
/**
* Cell output metadata.
*/
metadata: OutputMetadata;
}
/**
* Data displayed as a result of code cell execution.
*/
export interface IDisplayData extends nb.ICellOutput {
/**
* Type of cell output.
*/
output_type: 'display_data';
/**
* A mime-type keyed dictionary of data.
*/
data: IMimeBundle;
/**
* Cell output metadata.
*/
metadata: OutputMetadata;
}
/**
* Data displayed as an update to existing display data.
*/
export interface IDisplayUpdate extends nb.ICellOutput {
/**
* Type of cell output.
*/
output_type: 'update_display_data';
/**
* A mime-type keyed dictionary of data.
*/
data: IMimeBundle;
/**
* Cell output metadata.
*/
metadata: OutputMetadata;
}
/**
* Stream output from a code cell.
*/
export interface IStream extends nb.ICellOutput {
/**
* Type of cell output.
*/
output_type: 'stream';
/**
* The name of the stream.
*/
name: StreamType;
/**
* The stream's text output.
*/
text: MultilineString;
}
/**
* An alias for a stream type.
*/
export type StreamType = 'stdout' | 'stderr';
/**
* Output of an error that occurred during code cell execution.
*/
export interface IError extends nb.ICellOutput {
/**
* Type of cell output.
*/
output_type: 'error';
/**
* The name of the error.
*/
ename: string;
/**
* The value, or message, of the error.
*/
evalue: string;
/**
* The error's traceback.
*/
traceback: string[];
}
/**
* Unrecognized output.
*/
export interface IUnrecognizedOutput extends nb.ICellOutput { }
/**
* Test whether an output is an execute result.
*/
export function isExecuteResult(output: IOutput): output is IExecuteResult {
return output.output_type === 'execute_result';
}
/**
* Test whether an output is from display data.
*/
export function isDisplayData(output: IOutput): output is IDisplayData {
return output.output_type === 'display_data';
}
/**
* Test whether an output is from updated display data.
*/
export function isDisplayUpdate(output: IOutput): output is IDisplayUpdate {
return output.output_type === 'update_display_data';
}
/**
* Test whether an output is from a stream.
*/
export function isStream(output: IOutput): output is IStream {
return output.output_type === 'stream';
}
/**
* Test whether an output is from a stream.
*/
export function isError(output: IOutput): output is IError {
return output.output_type === 'error';
}
/**
* An output union type.
*/
export type IOutput =
| IUnrecognizedOutput
| IExecuteResult
| IDisplayData
| IStream
| IError;
}
export interface ICellOutputWithIdAndTrust extends nb.ICellOutput {
id: number;
trusted: boolean;
}

View File

@@ -0,0 +1,110 @@
/*-----------------------------------------------------------------------------
| Copyright (c) Jupyter Development Team.
| Distributed under the terms of the Modified BSD License.
|----------------------------------------------------------------------------*/
import { JSONObject } from './jsonext';
import { MimeModel } from './mimemodel';
import * as JSONExt from './jsonext';
import { nbformat } from './nbformat';
import { nb } from 'sqlops';
/**
* A multiline string.
*/
export type MultilineString = string | string[];
/**
* A mime-type keyed dictionary of data.
*/
export interface IMimeBundle extends JSONObject {
[key: string]: MultilineString | JSONObject;
}
/**
* Get the data from a notebook output.
*/
export function getData(output: nb.ICellOutput): JSONObject {
let bundle: IMimeBundle = {};
if (
nbformat.isExecuteResult(output) ||
nbformat.isDisplayData(output) ||
nbformat.isDisplayUpdate(output)
) {
bundle = (output as nbformat.IExecuteResult).data;
} else if (nbformat.isStream(output)) {
if (output.name === 'stderr') {
bundle['application/vnd.jupyter.stderr'] = output.text;
} else {
bundle['application/vnd.jupyter.stdout'] = output.text;
}
} else if (nbformat.isError(output)) {
let traceback = output.traceback ? output.traceback.join('\n') : undefined;
bundle['application/vnd.jupyter.stderr'] =
traceback || `${output.ename}: ${output.evalue}`;
}
return convertBundle(bundle);
}
/**
* Get the metadata from an output message.
*/
export function getMetadata(output: nbformat.IOutput): JSONObject {
let value: JSONObject = Object.create(null);
if (nbformat.isExecuteResult(output) || nbformat.isDisplayData(output)) {
for (let key in output.metadata) {
value[key] = extract(output.metadata, key);
}
}
return value;
}
/**
* Get the bundle options given output model options.
*/
export function getBundleOptions(
options: IOutputModelOptions
): MimeModel.IOptions {
let data = getData(options.value);
let metadata = getMetadata(options.value);
let trusted = !!options.trusted;
return { data, metadata, trusted };
}
/**
* Extract a value from a JSONObject.
*/
export function extract(value: JSONObject, key: string): {} {
let item = value[key];
if (JSONExt.isPrimitive(item)) {
return item;
}
return JSON.parse(JSON.stringify(item));
}
/**
* Convert a mime bundle to mime data.
*/
function convertBundle(bundle: nbformat.IMimeBundle): JSONObject {
let map: JSONObject = Object.create(null);
for (let mimeType in bundle) {
map[mimeType] = extract(bundle, mimeType);
}
return map;
}
/**
* The options used to create a notebook output model.
*/
export interface IOutputModelOptions {
/**
* The raw output value.
*/
value: nbformat.IOutput;
/**
* Whether the output is trusted. The default is false.
*/
trusted?: boolean;
}

View File

@@ -0,0 +1,360 @@
/*-----------------------------------------------------------------------------
| Copyright (c) Jupyter Development Team.
| Distributed under the terms of the Modified BSD License.
|----------------------------------------------------------------------------*/
import { ReadonlyJSONObject } from './jsonext';
/**
* A namespace for rendermime associated interfaces.
*/
export namespace IRenderMime {
/**
* A model for mime data.
*/
export interface IMimeModel {
/**
* Whether the data in the model is trusted.
*/
readonly trusted: boolean;
/**
* The data associated with the model.
*/
readonly data: ReadonlyJSONObject;
/**
* The metadata associated with the model.
*/
readonly metadata: ReadonlyJSONObject;
/**
* Set the data associated with the model.
*
* #### Notes
* Calling this function may trigger an asynchronous operation
* that could cause the renderer to be rendered with a new model
* containing the new data.
*/
setData(options: ISetDataOptions): void;
}
/**
* The options used to update a mime model.
*/
export interface ISetDataOptions {
/**
* The new data object.
*/
data?: ReadonlyJSONObject;
/**
* The new metadata object.
*/
metadata?: ReadonlyJSONObject;
}
/**
* The options used to initialize a document widget factory.
*
* This interface is intended to be used by mime renderer extensions
* to define a document opener that uses its renderer factory.
*/
export interface IDocumentWidgetFactoryOptions {
/**
* The name of the widget to display in dialogs.
*/
readonly name: string;
/**
* The name of the document model type.
*/
readonly modelName?: string;
/**
* The primary file type of the widget.
*/
readonly primaryFileType: string;
/**
* The file types the widget can view.
*/
readonly fileTypes: ReadonlyArray<string>;
/**
* The file types for which the factory should be the default.
*/
readonly defaultFor?: ReadonlyArray<string>;
/**
* The file types for which the factory should be the default for rendering,
* if that is different than the default factory (which may be for editing)
* If undefined, then it will fall back on the default file type.
*/
readonly defaultRendered?: ReadonlyArray<string>;
}
/**
* A file type to associate with the renderer.
*/
export interface IFileType {
/**
* The name of the file type.
*/
readonly name: string;
/**
* The mime types associated the file type.
*/
readonly mimeTypes: ReadonlyArray<string>;
/**
* The extensions of the file type (e.g. `".txt"`). Can be a compound
* extension (e.g. `".table.json`).
*/
readonly extensions: ReadonlyArray<string>;
/**
* An optional display name for the file type.
*/
readonly displayName?: string;
/**
* An optional pattern for a file name (e.g. `^Dockerfile$`).
*/
readonly pattern?: string;
/**
* The icon class name for the file type.
*/
readonly iconClass?: string;
/**
* The icon label for the file type.
*/
readonly iconLabel?: string;
/**
* The file format for the file type ('text', 'base64', or 'json').
*/
readonly fileFormat?: string;
}
/**
* An interface for using a RenderMime.IRenderer for output and read-only documents.
*/
export interface IExtension {
/**
* The ID of the extension.
*
* #### Notes
* The convention for extension IDs in JupyterLab is the full NPM package
* name followed by a colon and a unique string token, e.g.
* `'@jupyterlab/apputils-extension:settings'` or `'foo-extension:bar'`.
*/
readonly id: string;
/**
* A renderer factory to be registered to render the MIME type.
*/
readonly rendererFactory: IRendererFactory;
/**
* The rank passed to `RenderMime.addFactory`. If not given,
* defaults to the `defaultRank` of the factory.
*/
readonly rank?: number;
/**
* The timeout after user activity to re-render the data.
*/
readonly renderTimeout?: number;
/**
* Preferred data type from the model. Defaults to `string`.
*/
readonly dataType?: 'string' | 'json';
/**
* The options used to open a document with the renderer factory.
*/
readonly documentWidgetFactoryOptions?:
| IDocumentWidgetFactoryOptions
| ReadonlyArray<IDocumentWidgetFactoryOptions>;
/**
* The optional file type associated with the extension.
*/
readonly fileTypes?: ReadonlyArray<IFileType>;
}
/**
* The interface for a module that exports an extension or extensions as
* the default value.
*/
export interface IExtensionModule {
/**
* The default export.
*/
readonly default: IExtension | ReadonlyArray<IExtension>;
}
/**
* A widget which displays the contents of a mime model.
*/
export interface IRenderer {
/**
* Render a mime model.
*
* @param model - The mime model to render.
*
* @returns A promise which resolves when rendering is complete.
*
* #### Notes
* This method may be called multiple times during the lifetime
* of the widget to update it if and when new data is available.
*/
renderModel(model: IRenderMime.IMimeModel): Promise<void>;
/**
* Node to be updated by the renderer
*/
node: HTMLElement;
}
/**
* The interface for a renderer factory.
*/
export interface IRendererFactory {
/**
* Whether the factory is a "safe" factory.
*
* #### Notes
* A "safe" factory produces renderer widgets which can render
* untrusted model data in a usable way. *All* renderers must
* handle untrusted data safely, but some may simply failover
* with a "Run cell to view output" message. A "safe" renderer
* is an indication that its sanitized output will be useful.
*/
readonly safe: boolean;
/**
* The mime types handled by this factory.
*/
readonly mimeTypes: ReadonlyArray<string>;
/**
* The default rank of the factory. If not given, defaults to 100.
*/
readonly defaultRank?: number;
/**
* Create a renderer which displays the mime data.
*
* @param options - The options used to render the data.
*/
createRenderer(options: IRendererOptions): IRenderer;
}
/**
* The options used to create a renderer.
*/
export interface IRendererOptions {
/**
* The preferred mimeType to render.
*/
mimeType: string;
/**
* The html sanitizer.
*/
sanitizer: ISanitizer;
/**
* An optional url resolver.
*/
resolver: IResolver | null;
/**
* An optional link handler.
*/
linkHandler: ILinkHandler | null;
/**
* The LaTeX typesetter.
*/
latexTypesetter: ILatexTypesetter | null;
}
/**
* An object that handles html sanitization.
*/
export interface ISanitizer {
/**
* Sanitize an HTML string.
*/
sanitize(dirty: string): string;
}
/**
* An object that handles links on a node.
*/
export interface ILinkHandler {
/**
* Add the link handler to the node.
*
* @param node: the node for which to handle the link.
*
* @param path: the path to open when the link is clicked.
*
* @param id: an optional element id to scroll to when the path is opened.
*/
handleLink(node: HTMLElement, path: string, id?: string): void;
}
/**
* An object that resolves relative URLs.
*/
export interface IResolver {
/**
* Resolve a relative url to a correct server path.
*/
resolveUrl(url: string): Promise<string>;
/**
* Get the download url of a given absolute server path.
*/
getDownloadUrl(path: string): Promise<string>;
/**
* Whether the URL should be handled by the resolver
* or not.
*
* #### Notes
* This is similar to the `isLocal` check in `URLExt`,
* but can also perform additional checks on whether the
* resolver should handle a given URL.
*/
isLocal?: (url: string) => boolean;
}
/**
* The interface for a LaTeX typesetter.
*/
export interface ILatexTypesetter {
/**
* Typeset a DOM element.
*
* @param element - the DOM element to typeset. The typesetting may
* happen synchronously or asynchronously.
*
* #### Notes
* The application-wide rendermime object has a settable
* `latexTypesetter` property which is used wherever LaTeX
* typesetting is required. Extensions wishing to provide their
* own typesetter may replace that on the global `lab.rendermime`.
*/
typeset(element: HTMLElement): void;
}
}

View File

@@ -0,0 +1,184 @@
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
import { JSONObject } from './jsonext';
import URI from 'vs/base/common/uri';
/**
* The namespace for URL-related functions.
*/
export namespace URLExt {
/**
* Normalize a url.
*/
export function normalize(url: string): string {
return URI.parse(url).toString();
}
/**
* Join a sequence of url components and normalizes as in node `path.join`.
*
* @param parts - The url components.
*
* @returns the joined url.
*/
export function join(...parts: string[]): string {
parts = parts || [];
// Isolate the top element.
const top = parts[0] || '';
// Check whether protocol shorthand is being used.
const shorthand = top.indexOf('//') === 0;
// Parse the top element into a header collection.
const header = top.match(/(\w+)(:)(\/\/)?/);
const protocol = header && header[1];
const colon = protocol && header[2];
const slashes = colon && header[3];
// Construct the URL prefix.
const prefix = shorthand
? '//'
: [protocol, colon, slashes].filter(str => str).join('');
// Construct the URL body omitting the prefix of the top value.
const body = [top.indexOf(prefix) === 0 ? top.replace(prefix, '') : top]
// Filter out top value if empty.
.filter(str => str)
// Remove leading slashes in all subsequent URL body elements.
.concat(parts.slice(1).map(str => str.replace(/^\//, '')))
.join('/')
// Replace multiple slashes with one.
.replace(/\/+/g, '/');
return prefix + body;
}
/**
* Encode the components of a multi-segment url.
*
* @param url - The url to encode.
*
* @returns the encoded url.
*
* #### Notes
* Preserves the `'/'` separators.
* Should not include the base url, since all parts are escaped.
*/
export function encodeParts(url: string): string {
return join(...url.split('/').map(encodeURIComponent));
}
/**
* Return a serialized object string suitable for a query.
*
* @param object - The source object.
*
* @returns an encoded url query.
*
* #### Notes
* Modified version of [stackoverflow](http://stackoverflow.com/a/30707423).
*/
export function objectToQueryString(value: JSONObject): string {
const keys = Object.keys(value);
if (!keys.length) {
return '';
}
return (
'?' +
keys
.map(key => {
const content = encodeURIComponent(String(value[key]));
return key + (content ? '=' + content : '');
})
.join('&')
);
}
/**
* Return a parsed object that represents the values in a query string.
*/
export function queryStringToObject(
value: string
): { [key: string]: string } {
return value
.replace(/^\?/, '')
.split('&')
.reduce(
(acc, val) => {
const [key, value] = val.split('=');
acc[key] = decodeURIComponent(value || '');
return acc;
},
{} as { [key: string]: string }
);
}
/**
* Test whether the url is a local url.
*
* #### Notes
* This function returns `false` for any fully qualified url, including
* `data:`, `file:`, and `//` protocol URLs.
*/
export function isLocal(url: string): boolean {
// If if doesn't have a scheme such as file: or http:// it's local
return !!URI.parse(url).scheme;
}
/**
* The interface for a URL object
*/
export interface IUrl {
/**
* The full URL string that was parsed with both the protocol and host
* components converted to lower-case.
*/
href?: string;
/**
* Identifies the URL's lower-cased protocol scheme.
*/
protocol?: string;
/**
* The full lower-cased host portion of the URL, including the port if
* specified.
*/
host?: string;
/**
* The lower-cased host name portion of the host component without the
* port included.
*/
hostname?: string;
/**
* The numeric port portion of the host component.
*/
port?: string;
/**
* The entire path section of the URL.
*/
pathname?: string;
/**
* The "fragment" portion of the URL including the leading ASCII hash
* `(#)` character
*/
hash?: string;
/**
* The search element, including leading question mark (`'?'`), if any,
* of the URL.
*/
search?: string;
}
}

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