Compare commits

...

51 Commits
1.3.4 ... 1.3.7

Author SHA1 Message Date
Karl Burtram
5a30878599 Bump agent, import, profiler extension versions 2018-12-06 18:11:11 -08:00
Karl Burtram
c8a8935db0 Bump SQL Tools Service to 1.5.0-alpha.63 2018-12-06 17:58:49 -08:00
Aditya Bist
ec196f57bb Agent feature - ability to start at step (#3483)
* added ability to start at certain step

* fixed width for scrollbar

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

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

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

* generalized feature telemetry via dialogs

* renamed eventName property to dialogName

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

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

* agent fixes

* fit step tree

* fix rendering issue

* fixed spinning wheel position

* added horizontal scrolling to steps tree

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

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

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

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

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

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

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

## Not covered in this PR
- Need to actually implement support for defining the provider and connection IDs for a notebook. this will be important to support New Notebook from a big data connection in Object Explorer
- Need to add APIs for adding cells, to support 
- Need to implement the metadata for getting full notebook contents. I've only implemented to key APIs needed to make this all work.
2018-12-03 18:50:44 -08:00
Raj
cb162b16f2 No kernel is shown when open a new notebook from command palette (#3374)
Fixes #3271. Ensure a provider is defined when opening through command palette
2018-12-03 18:12:25 -08:00
Karl Burtram
86e54ce145 Bump Azure Data Studio to 1.3.7 2018-12-03 17:43:12 -08:00
Anthony Dresser
efd809971f fix row select (#3390) 2018-12-03 14:10:50 -08:00
Karl Burtram
38ae14cc4d Use UTF8 for Azure token cache (#3391)
* Switch token cache encryption encoding to UTF8

* Try to parse as binary in fallback

* Code review feedback
2018-12-03 14:09:42 -08:00
Chris LaFreniere
c7e33a90fe Notebook toolbar extensibility (#3362)
* Notebook Toolbar Contribution

* Address CR comments, ensure CSS can be passed in for contributed items
2018-12-03 11:15:14 -08:00
Yurong He
5add835750 Fixed issue: Notebook: Can't use PySpark3 even though having big data cluster connected #3363 (#3380) 2018-12-03 10:53:04 -08:00
Karl Burtram
734c614cba Update installer to regkeys for per-user install (#3376) 2018-11-30 16:56:19 -08:00
Karl Burtram
f6b347fa62 Bump Azure Data Studio to 1.3.6 2018-11-30 16:51:27 -08:00
Alan Ren
08d2f3125e Fix for issue 3133 (#3375)
* Fix for issue 3133

* fix test error
2018-11-30 15:53:36 -08:00
Matt Irvine
385c48dcad Wait for account provider registration when using account service (#3221) 2018-11-30 11:52:01 -08:00
Yurong He
0926057bfe Fixed #3294 removed dead code for "attach to" and remove disable to make it respond theme change (#3349)
* Fixed #3294

* Fixed #3294
2018-11-29 19:28:08 -08:00
Karl Burtram
6912e3893e Bump Azure Data Studio to 1.3.5 2018-11-29 17:12:32 -08:00
Karl Burtram
d3052657df Bump SQL Tools to 1.5.0-alpha.60 2018-11-29 17:12:05 -08:00
Karl Burtram
a5ca4d8edf Add ALTER and PROC to the colorization list (#3353) 2018-11-29 17:04:45 -08:00
Anthony Dresser
afb1ebebd5 Result Streaming (#3319)
* 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

* fix duplicate result sets

* fix event stream versions

* yarn
2018-11-29 15:16:53 -08:00
Matt Irvine
a04a9eb5ad Hide connection account picker when there are no auth type options (#3350) 2018-11-29 14:54:46 -08:00
Cory Rivera
027badd21f Fix capitalization in dataTierApplicationWizard imports. (#3351) 2018-11-29 14:32:45 -08:00
Raj
1affc760e6 Notebook save functionality through button (#3340)
* 3268: Notebook save using button

* 3268: Misc change

* Handle promise while saving notebook

* Async await functions to run action
2018-11-29 14:10:16 -08:00
Yurong He
3ca72b7398 Fixed Spark UI links within Notebooks are not working notebooks sql2019Preview (#3344)
#3277
2018-11-29 12:52:18 -08:00
Yurong He
702dbddd78 Fixed some issues for Markdown and CSS (#3336)
* Fixed few markdown cell issues
* Change the boolean value from 1 to true
2018-11-29 12:50:53 -08:00
Aditya Bist
8fbecc0227 Fixed agent step update (#3308)
* fixed agent step update

* enabled reordering of steps in edit job
2018-11-29 10:48:12 -08:00
Alan Ren
421271acfa hide the profiler commands meant for internal use (#3339) 2018-11-28 15:03:53 -08:00
Raj
98af76b3ac Build error (#3335) 2018-11-28 10:45:21 -08:00
Raj
3952fdbe2d 3240: Empty cells show thick border (#3242)
* 3240: Empty cells show thick border

* 3240: Refactoring code and markdown css selection +output UI improvement
2018-11-28 09:45:34 -08:00
Yurong He
bc13beaa85 Revert "Hide run button in markdown editor and pull toggleMoreAction to a seperate class (#3321)" (#3328)
This reverts commit 9ea8baca05.
2018-11-27 23:43:16 -08:00
133 changed files with 4319 additions and 726 deletions

View File

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

View File

@@ -91,17 +91,17 @@ Filename: "{app}\{#ExeBasename}.exe"; Description: "{cm:LaunchProgram,{#NameLong
#else #else
#define SoftwareClassesRootKey "HKLM" #define SoftwareClassesRootKey "HKLM"
#endif #endif
Root: HKCR; Subkey: "{#RegValueName}SourceFile"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,{#NameLong}}"; Flags: uninsdeletekey Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}SourceFile"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,{#NameLong}}"; Flags: uninsdeletekey
Root: HKCR; Subkey: "{#RegValueName}SourceFile\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\code_file.ico" Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}SourceFile\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\code_file.ico"
Root: HKCR; Subkey: "{#RegValueName}SourceFile\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1""" Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}SourceFile\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""
Root: HKCU; Subkey: "Environment"; ValueType: expandsz; ValueName: "Path"; ValueData: "{olddata};{app}\bin"; Tasks: addtopath; Check: NeedsAddPath(ExpandConstant('{app}\bin')) Root: HKCU; Subkey: "Environment"; ValueType: expandsz; ValueName: "Path"; ValueData: "{olddata};{app}\bin"; Tasks: addtopath; Check: NeedsAddPath(ExpandConstant('{app}\bin'))
Root: HKCU; Subkey: "Software\Classes\.sql\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.sql\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles
Root: HKCU; Subkey: "Software\Classes\.sql\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.sql"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.sql\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.sql"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles
Root: HKCU; Subkey: "Software\Classes\{#RegValueName}.sql"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,SQL}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.sql"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,SQL}"; Flags: uninsdeletekey; Tasks: associatewithfiles
Root: HKCU; Subkey: "Software\Classes\{#RegValueName}.sql"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.sql"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles
Root: HKCU; Subkey: "Software\Classes\{#RegValueName}.sql\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\code_file.ico"; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.sql\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\code_file.ico"; Tasks: associatewithfiles
Root: HKCU; Subkey: "Software\Classes\{#RegValueName}.sql\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.sql\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles
; Environment ; Environment
#if "user" == InstallTarget #if "user" == InstallTarget
#define EnvironmentRootKey "HKCU" #define EnvironmentRootKey "HKCU"

View File

@@ -571,6 +571,18 @@ deep-assign@^1.0.0:
dependencies: dependencies:
is-obj "^1.0.0" 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: delayed-stream@~1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
@@ -942,6 +954,29 @@ glob@^5.0.3:
once "^1.3.0" once "^1.3.0"
path-is-absolute "^1.0.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: glogg@^1.0.0:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/glogg/-/glogg-1.0.1.tgz#dcf758e44789cc3f3d32c1f3562a3676e6a34810" resolved "https://registry.yarnpkg.com/glogg/-/glogg-1.0.1.tgz#dcf758e44789cc3f3d32c1f3562a3676e6a34810"
@@ -1313,6 +1348,25 @@ is-obj@^1.0.0:
resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f"
integrity sha1-PkcprB9f3gJc19g6iW2rn09n2w8= 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: is-posix-bracket@^0.1.0:
version "0.1.1" version "0.1.1"
resolved "https://registry.yarnpkg.com/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4" resolved "https://registry.yarnpkg.com/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4"
@@ -1778,6 +1832,11 @@ os-tmpdir@~1.0.2:
resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= 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: parse-glob@^3.0.4:
version "3.0.4" version "3.0.4"
resolved "https://registry.yarnpkg.com/parse-glob/-/parse-glob-3.0.4.tgz#b2c376cfb11f35513badd173ef0bb6e3a388391c" resolved "https://registry.yarnpkg.com/parse-glob/-/parse-glob-3.0.4.tgz#b2c376cfb11f35513badd173ef0bb6e3a388391c"
@@ -1798,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" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= 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: pause-stream@0.0.11:
version "0.0.11" version "0.0.11"
resolved "https://registry.yarnpkg.com/pause-stream/-/pause-stream-0.0.11.tgz#fe5a34b0cbce12b5aa6a2b403ee2e73b602f1445" resolved "https://registry.yarnpkg.com/pause-stream/-/pause-stream-0.0.11.tgz#fe5a34b0cbce12b5aa6a2b403ee2e73b602f1445"
@@ -1820,7 +1884,7 @@ performance-now@^2.1.0:
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=
pify@^2.3.0: pify@^2.0.0, pify@^2.3.0:
version "2.3.0" version "2.3.0"
resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw= integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw=
@@ -2118,7 +2182,7 @@ requires-port@~1.0.0:
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=
rimraf@2: rimraf@2, rimraf@^2.2.8:
version "2.6.2" version "2.6.2"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36"
integrity sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w== integrity sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==

View File

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

View File

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

View File

@@ -123,6 +123,7 @@ export class JobStepData implements IAgentDialogData {
stepData.retryInterval = jobStepInfo.retryInterval, stepData.retryInterval = jobStepInfo.retryInterval,
stepData.proxyName = jobStepInfo.proxyName; stepData.proxyName = jobStepInfo.proxyName;
stepData.dialogMode = AgentDialogMode.EDIT; stepData.dialogMode = AgentDialogMode.EDIT;
stepData.viaJobDialog = true;
return stepData; return stepData;
} }

View File

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

View File

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

View File

@@ -42,11 +42,12 @@ export class JobDialog extends AgentDialog<JobData> {
private readonly StepsTable_TypeColumnString: string = localize('jobDialog.type', 'Type'); private readonly StepsTable_TypeColumnString: string = localize('jobDialog.type', 'Type');
private readonly StepsTable_SuccessColumnString: string = localize('jobDialog.onSuccess', 'On Success'); private readonly StepsTable_SuccessColumnString: string = localize('jobDialog.onSuccess', 'On Success');
private readonly StepsTable_FailureColumnString: string = localize('jobDialog.onFailure', 'On Failure'); private readonly StepsTable_FailureColumnString: string = localize('jobDialog.onFailure', 'On Failure');
private readonly NewStepButtonString: string = localize('jobDialog.new', 'New...'); private readonly NewStepButtonString: string = localize('jobDialog.new', 'New Step');
private readonly EditStepButtonString: string = localize('jobDialog.edit', 'Edit'); private readonly EditStepButtonString: string = localize('jobDialog.edit', 'Edit Step');
private readonly DeleteStepButtonString: string = localize('jobDialog.delete', 'Delete'); private readonly DeleteStepButtonString: string = localize('jobDialog.delete', 'Delete Step');
private readonly MoveStepUpButtonString: string = localize('jobDialog.moveUp', 'Move Step Up'); private readonly MoveStepUpButtonString: string = localize('jobDialog.moveUp', 'Move Step Up');
private readonly MoveStepDownButtonString: string = localize('jobDialog.moveDown', 'Move Step Up'); private readonly MoveStepDownButtonString: string = localize('jobDialog.moveDown', 'Move Step Down');
private readonly StartStepDropdownString: string = localize('jobDialog.startStepAt', 'Start step');
// Notifications tab strings // Notifications tab strings
private readonly NotificationsTabTopLabelString: string = localize('jobDialog.notificationsTabTop', 'Actions to perform when the job completes'); private readonly NotificationsTabTopLabelString: string = localize('jobDialog.notificationsTabTop', 'Actions to perform when the job completes');
@@ -67,6 +68,10 @@ export class JobDialog extends AgentDialog<JobData> {
private readonly AlertEnabledLabelString: string = localize('jobDialog.alertEnabledLabel', 'Enabled'); private readonly AlertEnabledLabelString: string = localize('jobDialog.alertEnabledLabel', 'Enabled');
private readonly AlertTypeLabelString: string = localize('jobDialog.alertTypeLabel', 'Type'); private readonly AlertTypeLabelString: string = localize('jobDialog.alertTypeLabel', 'Type');
// Event Name strings
private readonly NewJobDialogEvent: string = 'NewJobDialogOpened';
private readonly EditJobDialogEvent: string = 'EditJobDialogOpened';
// UI Components // UI Components
private generalTab: sqlops.window.modelviewdialog.DialogTab; private generalTab: sqlops.window.modelviewdialog.DialogTab;
private stepsTab: sqlops.window.modelviewdialog.DialogTab; private stepsTab: sqlops.window.modelviewdialog.DialogTab;
@@ -101,6 +106,7 @@ export class JobDialog extends AgentDialog<JobData> {
private eventLogConditionDropdown: sqlops.DropDownComponent; private eventLogConditionDropdown: sqlops.DropDownComponent;
private deleteJobCheckBox: sqlops.CheckBoxComponent; private deleteJobCheckBox: sqlops.CheckBoxComponent;
private deleteJobConditionDropdown: sqlops.DropDownComponent; private deleteJobConditionDropdown: sqlops.DropDownComponent;
private startStepDropdown: sqlops.DropDownComponent;
// Schedule tab controls // Schedule tab controls
private schedulesTable: sqlops.TableComponent; private schedulesTable: sqlops.TableComponent;
@@ -115,6 +121,7 @@ export class JobDialog extends AgentDialog<JobData> {
private steps: sqlops.AgentJobStepInfo[]; private steps: sqlops.AgentJobStepInfo[];
private schedules: sqlops.AgentJobScheduleInfo[]; private schedules: sqlops.AgentJobScheduleInfo[];
private alerts: sqlops.AgentAlertInfo[] = []; private alerts: sqlops.AgentAlertInfo[] = [];
private startStepDropdownValues: sqlops.CategoryValue[] = [];
constructor(ownerUri: string, jobInfo: sqlops.AgentJobInfo = undefined) { constructor(ownerUri: string, jobInfo: sqlops.AgentJobInfo = undefined) {
super( super(
@@ -125,6 +132,7 @@ export class JobDialog extends AgentDialog<JobData> {
this.schedules = this.model.jobSchedules ? this.model.jobSchedules : []; this.schedules = this.model.jobSchedules ? this.model.jobSchedules : [];
this.alerts = this.model.alerts ? this.model.alerts : []; this.alerts = this.model.alerts ? this.model.alerts : [];
this.isEdit = jobInfo ? true : false; this.isEdit = jobInfo ? true : false;
this.dialogName = this.isEdit ? this.EditJobDialogEvent : this.NewJobDialogEvent;
} }
protected async initializeDialog() { protected async initializeDialog() {
@@ -218,19 +226,26 @@ export class JobDialog extends AgentDialog<JobData> {
this.StepsTable_FailureColumnString this.StepsTable_FailureColumnString
], ],
data: data, data: data,
height: 750 height: 650
}).component(); }).component();
this.startStepDropdown = view.modelBuilder.dropDown().withProperties({ width: 180 }).component();
this.startStepDropdown.enabled = this.steps.length > 1 ? true : false;
this.steps.forEach((step) => {
this.startStepDropdownValues.push({ displayName: step.id + ': ' + step.stepName, name: step.id.toString() });
});
this.startStepDropdown.values = this.startStepDropdownValues;
this.moveStepUpButton = view.modelBuilder.button() this.moveStepUpButton = view.modelBuilder.button()
.withProperties({ .withProperties({
label: this.MoveStepUpButtonString, label: this.MoveStepUpButtonString,
width: 80 width: 120
}).component(); }).component();
this.moveStepDownButton = view.modelBuilder.button() this.moveStepDownButton = view.modelBuilder.button()
.withProperties({ .withProperties({
label: this.MoveStepDownButtonString, label: this.MoveStepDownButtonString,
width: 80 width: 120
}).component(); }).component();
this.moveStepUpButton.enabled = false; this.moveStepUpButton.enabled = false;
@@ -238,7 +253,7 @@ export class JobDialog extends AgentDialog<JobData> {
this.newStepButton = view.modelBuilder.button().withProperties({ this.newStepButton = view.modelBuilder.button().withProperties({
label: this.NewStepButtonString, label: this.NewStepButtonString,
width: 80 width: 140
}).component(); }).component();
let stepDialog = new JobStepDialog(this.model.ownerUri, '' , this.model, null, true); let stepDialog = new JobStepDialog(this.model.ownerUri, '' , this.model, null, true);
@@ -246,6 +261,11 @@ export class JobDialog extends AgentDialog<JobData> {
let stepInfo = JobStepData.convertToAgentJobStepInfo(step); let stepInfo = JobStepData.convertToAgentJobStepInfo(step);
this.steps.push(stepInfo); this.steps.push(stepInfo);
this.stepsTable.data = this.convertStepsToData(this.steps); this.stepsTable.data = this.convertStepsToData(this.steps);
this.startStepDropdownValues = [];
this.steps.forEach((step) => {
this.startStepDropdownValues.push({ displayName: step.id + ': ' + step.stepName, name: step.id.toString() });
});
this.startStepDropdown.values = this.startStepDropdownValues;
}); });
this.newStepButton.onDidClick((e)=>{ this.newStepButton.onDidClick((e)=>{
if (this.nameTextBox.value && this.nameTextBox.value.length > 0) { if (this.nameTextBox.value && this.nameTextBox.value.length > 0) {
@@ -258,53 +278,127 @@ export class JobDialog extends AgentDialog<JobData> {
this.editStepButton = view.modelBuilder.button().withProperties({ this.editStepButton = view.modelBuilder.button().withProperties({
label: this.EditStepButtonString, label: this.EditStepButtonString,
width: 80 width: 140
}).component(); }).component();
this.deleteStepButton = view.modelBuilder.button().withProperties({ this.deleteStepButton = view.modelBuilder.button().withProperties({
label: this.DeleteStepButtonString, label: this.DeleteStepButtonString,
width: 80 width: 140
}).component(); }).component();
this.stepsTable.enabled = false; this.stepsTable.enabled = false;
this.editStepButton.enabled = false; this.editStepButton.enabled = false;
this.deleteStepButton.enabled = false; this.deleteStepButton.enabled = false;
this.stepsTable.onRowSelected(() => { this.moveStepUpButton.onDidClick(() => {
// only let edit or delete steps if there's let rowNumber = this.stepsTable.selectedRows[0];
// one step selection let previousRow = rowNumber - 1;
let previousStep = this.steps[previousRow];
let previousStepId = this.steps[previousRow].id;
let currentStep = this.steps[rowNumber];
let currentStepId = this.steps[rowNumber].id;
this.steps[previousRow] = currentStep;
this.steps[rowNumber] = previousStep;
this.stepsTable.data = this.convertStepsToData(this.steps);
this.steps[previousRow].id = previousStepId;
this.steps[rowNumber].id = currentStepId;
});
this.moveStepDownButton.onDidClick(() => {
let rowNumber = this.stepsTable.selectedRows[0];
let nextRow = rowNumber + 1;
let nextStep = this.steps[nextRow];
let nextStepId = this.steps[nextRow].id;
let currentStep = this.steps[rowNumber];
let currentStepId = this.steps[rowNumber].id;
this.steps[nextRow] = currentStep;
this.steps[rowNumber] = nextStep;
this.stepsTable.data = this.convertStepsToData(this.steps);
this.steps[nextRow].id = nextStepId;
this.steps[rowNumber].id = currentStepId;
});
this.editStepButton.onDidClick(() => {
if (this.stepsTable.selectedRows.length === 1) { if (this.stepsTable.selectedRows.length === 1) {
let rowNumber = this.stepsTable.selectedRows[0]; let rowNumber = this.stepsTable.selectedRows[0];
let stepData = this.model.jobSteps[rowNumber]; let stepData = this.model.jobSteps[rowNumber];
this.deleteStepButton.enabled = true; let editStepDialog = new JobStepDialog(this.model.ownerUri, '' , this.model, stepData, true);
this.editStepButton.enabled = true; editStepDialog.onSuccess((step) => {
this.editStepButton.onDidClick(() => { let stepInfo = JobStepData.convertToAgentJobStepInfo(step);
let stepDialog = new JobStepDialog(this.model.ownerUri, '' , this.model, stepData, true); for (let i = 0; i < this.steps.length; i++) {
stepDialog.openDialog(); if (this.steps[i].id === stepInfo.id) {
}); this.steps[i] = stepInfo;
}
}
this.stepsTable.data = this.convertStepsToData(this.steps);
this.startStepDropdownValues = [];
this.steps.forEach((step) => {
this.startStepDropdownValues.push({ displayName: step.id + ': ' + step.stepName, name: step.id.toString() });
});
this.startStepDropdown.values = this.startStepDropdownValues;
this.deleteStepButton.onDidClick(() => { });
AgentUtils.getAgentService().then((agentService) => { editStepDialog.openDialog();
let steps = this.model.jobSteps ? this.model.jobSteps : []; }
agentService.deleteJobStep(this.ownerUri, stepData).then((result) => { });
if (result && result.success) {
delete steps[rowNumber]; this.deleteStepButton.onDidClick(() => {
let data = this.convertStepsToData(steps); if (this.stepsTable.selectedRows.length === 1) {
this.stepsTable.data = data; let rowNumber = this.stepsTable.selectedRows[0];
} AgentUtils.getAgentService().then((agentService) => {
}); let steps = this.model.jobSteps ? this.model.jobSteps : [];
let stepData = this.model.jobSteps[rowNumber];
agentService.deleteJobStep(this.ownerUri, stepData).then((result) => {
if (result && result.success) {
delete steps[rowNumber];
let data = this.convertStepsToData(steps);
this.stepsTable.data = data;
this.startStepDropdownValues = [];
this.steps.forEach((step) => {
this.startStepDropdownValues.push({ displayName: step.id + ': ' + step.stepName, name: step.id.toString() });
});
this.startStepDropdown.values = this.startStepDropdownValues;
}
}); });
}); });
} }
}); });
let formModel = view.modelBuilder.formContainer() this.stepsTable.onRowSelected((row) => {
.withFormItems([{ // only let edit or delete steps if there's
// one step selection
if (this.stepsTable.selectedRows.length === 1) {
let rowNumber = this.stepsTable.selectedRows[0];
// if it's not the last step
if (this.steps.length !== rowNumber + 1) {
this.moveStepDownButton.enabled = true;
}
// if it's not the first step
if (rowNumber !== 0) {
this.moveStepUpButton.enabled = true;
}
this.deleteStepButton.enabled = true;
this.editStepButton.enabled = true;
}
});
let stepMoveContainer = this.createRowContainer(view).withItems([this.startStepDropdown, this.moveStepUpButton, this.moveStepDownButton]).component();
let stepsDialogContainer = this.createRowContainer(view).withItems([this.newStepButton, this.editStepButton, this.deleteStepButton]).component();
let formModel = view.modelBuilder.formContainer().withFormItems([
{
component: this.stepsTable, component: this.stepsTable,
title: this.JobStepsTopLabelString, title: this.JobStepsTopLabelString
actions: [this.moveStepUpButton, this.moveStepDownButton, this.newStepButton, this.editStepButton, this.deleteStepButton] },
}]).withLayout({ width: '100%' }).component(); {
component: stepMoveContainer,
title: this.StartStepDropdownString
},
{
component: stepsDialogContainer,
title: ''
}
]).withLayout({ width: '100%' }).component();
await view.initializeModel(formModel); await view.initializeModel(formModel);
this.setConditionDropdownSelectedValue(this.startStepDropdown, this.model.startStepId);
}); });
} }
@@ -567,6 +661,7 @@ export class JobDialog extends AgentDialog<JobData> {
this.model.pageLevel = this.getActualConditionValue(this.pagerCheckBox, this.pagerConditionDropdown); this.model.pageLevel = this.getActualConditionValue(this.pagerCheckBox, this.pagerConditionDropdown);
this.model.eventLogLevel = this.getActualConditionValue(this.eventLogCheckBox, this.eventLogConditionDropdown); this.model.eventLogLevel = this.getActualConditionValue(this.eventLogCheckBox, this.eventLogConditionDropdown);
this.model.deleteLevel = this.getActualConditionValue(this.deleteJobCheckBox, this.deleteJobConditionDropdown); this.model.deleteLevel = this.getActualConditionValue(this.deleteJobCheckBox, this.deleteJobConditionDropdown);
this.model.startStepId = +this.getDropdownValue(this.startStepDropdown);
if (!this.model.jobSteps) { if (!this.model.jobSteps) {
this.model.jobSteps = []; this.model.jobSteps = [];
} }

View File

@@ -67,6 +67,9 @@ export class JobStepDialog extends AgentDialog<JobStepData> {
private readonly QuitJobReportingSuccess: string = localize('jobStepDialog.quitJobSuccess', 'Quit the job reporting success'); private readonly QuitJobReportingSuccess: string = localize('jobStepDialog.quitJobSuccess', 'Quit the job reporting success');
private readonly QuitJobReportingFailure: string = localize('jobStepDialog.quitJobFailure', 'Quit the job reporting failure'); private readonly QuitJobReportingFailure: string = localize('jobStepDialog.quitJobFailure', 'Quit the job reporting failure');
// Event Name strings
private readonly NewStepDialog = 'NewStepDialogOpened';
private readonly EditStepDialog = 'EditStepDialogOpened';
// UI Components // UI Components
// Dialogs // Dialogs
@@ -131,6 +134,7 @@ export class JobStepDialog extends AgentDialog<JobStepData> {
this.jobModel = jobModel; this.jobModel = jobModel;
this.jobName = this.jobName ? this.jobName : this.jobModel.name; this.jobName = this.jobName ? this.jobName : this.jobModel.name;
this.server = server; this.server = server;
this.dialogName = this.isEdit ? this.EditStepDialog : this.NewStepDialog;
} }
private initializeUIComponents() { private initializeUIComponents() {
@@ -519,6 +523,7 @@ export class JobStepDialog extends AgentDialog<JobStepData> {
this.model.failureAction = this.failureActionDropdown.value as string; this.model.failureAction = this.failureActionDropdown.value as string;
this.model.outputFileName = this.outputFileNameBox.value; this.model.outputFileName = this.outputFileNameBox.value;
this.model.appendToLogFile = this.appendToExistingFileCheckbox.checked; this.model.appendToLogFile = this.appendToExistingFileCheckbox.checked;
this.model.command = this.commandTextBox.value ? this.commandTextBox.value : '';
} }
public async initializeDialog() { public async initializeDialog() {

View File

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

View File

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

View File

@@ -40,13 +40,13 @@ export class MainController {
public activate(): void { public activate(): void {
vscode.commands.registerCommand('agent.openJobDialog', (ownerUri: string, jobInfo: sqlops.AgentJobInfo) => { vscode.commands.registerCommand('agent.openJobDialog', (ownerUri: string, jobInfo: sqlops.AgentJobInfo) => {
let dialog = new JobDialog(ownerUri, jobInfo); let dialog = new JobDialog(ownerUri, jobInfo);
dialog.openDialog(); dialog.dialogName ? dialog.openDialog(dialog.dialogName) : dialog.openDialog();
}); });
vscode.commands.registerCommand('agent.openNewStepDialog', (ownerUri: string, server: string, jobInfo: sqlops.AgentJobInfo, jobStepInfo: sqlops.AgentJobStepInfo) => { vscode.commands.registerCommand('agent.openNewStepDialog', (ownerUri: string, server: string, jobInfo: sqlops.AgentJobInfo, jobStepInfo: sqlops.AgentJobStepInfo) => {
AgentUtils.getAgentService().then((agentService) => { AgentUtils.getAgentService().then((agentService) => {
let jobData: JobData = new JobData(ownerUri, jobInfo, agentService); let jobData: JobData = new JobData(ownerUri, jobInfo, agentService);
let dialog = new JobStepDialog(ownerUri, server, jobData, jobStepInfo, false); let dialog = new JobStepDialog(ownerUri, server, jobData, jobStepInfo, false);
dialog.openDialog(); dialog.dialogName ? dialog.openDialog(dialog.dialogName) : dialog.openDialog();
}); });
}); });
vscode.commands.registerCommand('agent.openPickScheduleDialog', (ownerUri: string, jobName: string) => { vscode.commands.registerCommand('agent.openPickScheduleDialog', (ownerUri: string, jobName: string) => {
@@ -57,17 +57,16 @@ export class MainController {
AgentUtils.getAgentService().then((agentService) => { AgentUtils.getAgentService().then((agentService) => {
let jobData: JobData = new JobData(ownerUri, jobInfo, agentService); let jobData: JobData = new JobData(ownerUri, jobInfo, agentService);
let dialog = new AlertDialog(ownerUri, jobData, alertInfo, false); let dialog = new AlertDialog(ownerUri, jobData, alertInfo, false);
dialog.openDialog(); dialog.dialogName ? dialog.openDialog(dialog.dialogName) : dialog.openDialog();
}); });
}); });
vscode.commands.registerCommand('agent.openOperatorDialog', (ownerUri: string, operatorInfo: sqlops.AgentOperatorInfo) => { vscode.commands.registerCommand('agent.openOperatorDialog', (ownerUri: string, operatorInfo: sqlops.AgentOperatorInfo) => {
let dialog = new OperatorDialog(ownerUri, operatorInfo); let dialog = new OperatorDialog(ownerUri, operatorInfo);
dialog.openDialog(); dialog.dialogName ? dialog.openDialog(dialog.dialogName) : dialog.openDialog();
}); });
vscode.commands.registerCommand('agent.openProxyDialog', (ownerUri: string, proxyInfo: sqlops.AgentProxyInfo, credentials: sqlops.CredentialInfo[]) => { vscode.commands.registerCommand('agent.openProxyDialog', (ownerUri: string, proxyInfo: sqlops.AgentProxyInfo, credentials: sqlops.CredentialInfo[]) => {
let dialog = new ProxyDialog(ownerUri, proxyInfo, credentials); let dialog = new ProxyDialog(ownerUri, proxyInfo, credentials);
dialog.openDialog(); dialog.dialogName ? dialog.openDialog(dialog.dialogName) : dialog.openDialog();
MainController.showNotYetImplemented();
}); });
} }

View File

@@ -223,22 +223,16 @@ export default class TokenCache implements adal.TokenCache {
return this.getOrCreateEncryptionParams() return this.getOrCreateEncryptionParams()
.then(encryptionParams => { .then(encryptionParams => {
try { try {
let cacheCipher = fs.readFileSync(self._cacheSerializationPath, TokenCache.FsOptions); return self.decryptCache('utf8', encryptionParams);
let decipher = crypto.createDecipheriv(TokenCache.CipherAlgorithm, encryptionParams.key, encryptionParams.initializationVector);
let cacheJson = decipher.update(cacheCipher, 'hex', 'binary');
cacheJson += decipher.final('binary');
// Deserialize the JSON into the array of tokens
let cacheObj = <adal.TokenResponse[]>JSON.parse(cacheJson);
for (let objIndex in cacheObj) {
// Rehydrate Date objects since they will always serialize as a string
cacheObj[objIndex].expiresOn = new Date(<string>cacheObj[objIndex].expiresOn);
}
return cacheObj;
} catch (e) { } catch (e) {
throw e; try {
// try to parse using 'binary' encoding and rewrite cache as UTF8
let response = self.decryptCache('binary', encryptionParams);
self.writeCache(response);
return response;
} catch (e) {
throw e;
}
} }
}) })
.then(null, err => { .then(null, err => {
@@ -248,6 +242,22 @@ export default class TokenCache implements adal.TokenCache {
}); });
} }
private decryptCache(encoding: crypto.Utf8AsciiBinaryEncoding, encryptionParams: EncryptionParams): adal.TokenResponse[] {
let cacheCipher = fs.readFileSync(this._cacheSerializationPath, TokenCache.FsOptions);
let decipher = crypto.createDecipheriv(TokenCache.CipherAlgorithm, encryptionParams.key, encryptionParams.initializationVector);
let cacheJson = decipher.update(cacheCipher, 'hex', encoding);
cacheJson += decipher.final(encoding);
// Deserialize the JSON into the array of tokens
let cacheObj = <adal.TokenResponse[]>JSON.parse(cacheJson);
for (let objIndex in cacheObj) {
// Rehydrate Date objects since they will always serialize as a string
cacheObj[objIndex].expiresOn = new Date(<string>cacheObj[objIndex].expiresOn);
}
return cacheObj;
}
private removeFromCache(cache: adal.TokenResponse[], entries: adal.TokenResponse[]): adal.TokenResponse[] { private removeFromCache(cache: adal.TokenResponse[], entries: adal.TokenResponse[]): adal.TokenResponse[] {
entries.forEach((entry: adal.TokenResponse) => { entries.forEach((entry: adal.TokenResponse) => {
// Check to see if the entry exists // Check to see if the entry exists
@@ -274,7 +284,7 @@ export default class TokenCache implements adal.TokenCache {
let cacheJson = JSON.stringify(cache); let cacheJson = JSON.stringify(cache);
let cipher = crypto.createCipheriv(TokenCache.CipherAlgorithm, encryptionParams.key, encryptionParams.initializationVector); let cipher = crypto.createCipheriv(TokenCache.CipherAlgorithm, encryptionParams.key, encryptionParams.initializationVector);
let cacheCipher = cipher.update(cacheJson, 'binary', 'hex'); let cacheCipher = cipher.update(cacheJson, 'utf8', 'hex');
cacheCipher += cipher.final('hex'); cacheCipher += cipher.final('hex');
fs.writeFileSync(self._cacheSerializationPath, cacheCipher, TokenCache.FsOptions); fs.writeFileSync(self._cacheSerializationPath, cacheCipher, TokenCache.FsOptions);

View File

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

View File

@@ -7,7 +7,7 @@
import * as sqlops from 'sqlops'; import * as sqlops from 'sqlops';
import * as nls from 'vscode-nls'; import * as nls from 'vscode-nls';
import { DacFxDataModel } from '../api/models'; import { DacFxDataModel } from '../api/models';
import { DataTierApplicationWizard, Operation } from '../DataTierApplicationWizard'; import { DataTierApplicationWizard, Operation } from '../dataTierApplicationWizard';
import { BasePage } from '../api/basePage'; import { BasePage } from '../api/basePage';
const localize = nls.loadMessageBundle(); const localize = nls.loadMessageBundle();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,8 @@
{
"displayName": "Notebook Core Extensions",
"description": "Defines the Data-procotol based Notebook contribution and many Notebook commands and contributions.",
"notebook.configuration.title": "Notebook configuration",
"notebook.enabled.description": "Enable viewing notebook files using built-in notebook editor.",
"notebook.command.new": "New Notebook",
"notebook.command.open": "Open Notebook"
}

View File

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

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

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

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

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

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

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

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,50 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as vscode from 'vscode';
import * as sqlops from 'sqlops';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
let counter = 0;
export function activate(extensionContext: vscode.ExtensionContext) {
extensionContext.subscriptions.push(vscode.commands.registerCommand('notebook.command.new', () => {
let title = `Untitled-${counter++}`;
let untitledUri = vscode.Uri.parse(`untitled:${title}`);
sqlops.nb.showNotebookDocument(untitledUri).then(success => {
}, (err: Error) => {
vscode.window.showErrorMessage(err.message);
});
}));
extensionContext.subscriptions.push(vscode.commands.registerCommand('notebook.command.open', () => {
openNotebook();
}));
}
async function openNotebook(): Promise<void> {
try {
let filter = {};
// TODO support querying valid notebook file types
filter[localize('notebookFiles', 'Notebooks')] = ['ipynb'];
let file = await vscode.window.showOpenDialog({
filters: filter
});
if (file) {
let doc = await vscode.workspace.openTextDocument(file[0]);
vscode.window.showTextDocument(doc);
}
} catch (err) {
vscode.window.showErrorMessage(err);
}
}
// this method is called when your extension is deactivated
export function deactivate() {
}

View File

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

View File

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

View File

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

View File

@@ -2,7 +2,7 @@
"name": "profiler", "name": "profiler",
"displayName": "SQL Server Profiler", "displayName": "SQL Server Profiler",
"description": "SQL Server Profiler for Azure Data Studio", "description": "SQL Server Profiler for Azure Data Studio",
"version": "0.5.1", "version": "0.6.0",
"publisher": "Microsoft", "publisher": "Microsoft",
"preview": true, "preview": true,
"license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/master/LICENSE.txt", "license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/master/LICENSE.txt",
@@ -26,8 +26,7 @@
"Microsoft.mssql" "Microsoft.mssql"
], ],
"contributes": { "contributes": {
"commands": [ "commands": [{
{
"command": "profiler.newProfiler", "command": "profiler.newProfiler",
"title": "Launch Profiler", "title": "Launch Profiler",
"category": "Profiler" "category": "Profiler"
@@ -49,13 +48,23 @@
} }
], ],
"menus": { "menus": {
"objectExplorer/item/context": [ "commandPalette": [{
"command": "profiler.start",
"when": "False"
},
{ {
"command": "profiler.newProfiler", "command": "profiler.stop",
"when": "connectionProvider == MSSQL && nodeType && nodeType == Server", "when": "False"
"group": "profiler" }, {
"command": "profiler.openCreateSessionDialog",
"when": "False"
} }
] ],
"objectExplorer/item/context": [{
"command": "profiler.newProfiler",
"when": "connectionProvider == MSSQL && nodeType && nodeType == Server",
"group": "profiler"
}]
}, },
"outputChannels": [ "outputChannels": [
"sqlprofiler" "sqlprofiler"

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +1,6 @@
{ {
"name": "azuredatastudio", "name": "azuredatastudio",
"version": "1.3.4", "version": "1.3.7",
"distro": "8c3e97e3425cc9814496472ab73e076de2ba99ee", "distro": "8c3e97e3425cc9814496472ab73e076de2ba99ee",
"author": { "author": {
"name": "Microsoft Corporation" "name": "Microsoft Corporation"
@@ -68,7 +68,7 @@
"rxjs": "5.4.0", "rxjs": "5.4.0",
"sanitize-html": "^1.19.1", "sanitize-html": "^1.19.1",
"semver": "^5.5.0", "semver": "^5.5.0",
"slickgrid": "github:anthonydresser/SlickGrid#2.3.28", "slickgrid": "github:anthonydresser/SlickGrid#2.3.29",
"spdlog": "0.7.1", "spdlog": "0.7.1",
"sudo-prompt": "8.2.0", "sudo-prompt": "8.2.0",
"svg.js": "^2.2.5", "svg.js": "^2.2.5",
@@ -90,6 +90,7 @@
"@types/mocha": "2.2.39", "@types/mocha": "2.2.39",
"@types/sanitize-html": "^1.18.2", "@types/sanitize-html": "^1.18.2",
"@types/semver": "5.3.30", "@types/semver": "5.3.30",
"@types/should": "^13.0.0",
"@types/sinon": "1.16.34", "@types/sinon": "1.16.34",
"@types/winreg": "^1.2.30", "@types/winreg": "^1.2.30",
"asar": "^0.14.0", "asar": "^0.14.0",
@@ -148,8 +149,10 @@
"queue": "3.0.6", "queue": "3.0.6",
"remap-istanbul": "^0.6.4", "remap-istanbul": "^0.6.4",
"rimraf": "^2.2.8", "rimraf": "^2.2.8",
"should": "^13.2.3",
"sinon": "^1.17.2", "sinon": "^1.17.2",
"source-map": "^0.4.4", "source-map": "^0.4.4",
"temp-write": "^3.4.0",
"tslint": "^5.9.1", "tslint": "^5.9.1",
"typemoq": "^0.3.2", "typemoq": "^0.3.2",
"typescript": "2.9.2", "typescript": "2.9.2",
@@ -174,6 +177,7 @@
"windows-process-tree": "0.2.2" "windows-process-tree": "0.2.2"
}, },
"resolutions": { "resolutions": {
"rc": "1.2.8" "rc": "1.2.8",
"event-stream": "3.3.4"
} }
} }

View File

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

View File

@@ -5,8 +5,6 @@
'use strict'; 'use strict';
import { range } from 'vs/base/common/arrays';
export interface IRowNumberColumnOptions { export interface IRowNumberColumnOptions {
numberOfRows: number; numberOfRows: number;
cssClass?: string; cssClass?: string;
@@ -17,7 +15,7 @@ const sizePerDigit = 15;
export class RowNumberColumn<T> implements Slick.Plugin<T> { export class RowNumberColumn<T> implements Slick.Plugin<T> {
private handler = new Slick.EventHandler(); private handler = new Slick.EventHandler();
private grid: Slick.Grid<T>; private grid: Slick.Grid<T>;
private currentColumnWidth: number;
constructor(private options: IRowNumberColumnOptions) { constructor(private options: IRowNumberColumnOptions) {
} }
@@ -52,16 +50,22 @@ 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> { public getColumnDefinition(): Slick.Column<T> {
// that smallest we can make it is 22 due to padding and margins in the cells // 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 { return {
id: 'rowNumber', id: 'rowNumber',
name: '', name: '',
field: 'rowNumber', field: 'rowNumber',
width: columnWidth, width: this.currentColumnWidth,
minWidth: columnWidth,
maxWidth: columnWidth,
resizable: false, resizable: false,
cssClass: this.options.cssClass, cssClass: this.options.cssClass,
focusable: false, focusable: false,

View File

@@ -25,7 +25,6 @@ export const NewQuery = 'NewQuery';
export const FirewallRuleRequested = 'FirewallRuleCreated'; export const FirewallRuleRequested = 'FirewallRuleCreated';
export const DashboardNavigated = 'DashboardNavigated'; export const DashboardNavigated = 'DashboardNavigated';
// Telemetry Properties // Telemetry Properties
// Modal Dialogs: // Modal Dialogs:
@@ -42,3 +41,21 @@ export const Accounts = 'Accounts';
export const FireWallRule = 'FirewallRule'; export const FireWallRule = 'FirewallRule';
export const AutoOAuth = 'AutoOAuth'; export const AutoOAuth = 'AutoOAuth';
export const AddNewDashboardTab = 'AddNewDashboardTab'; export const AddNewDashboardTab = 'AddNewDashboardTab';
// SQL Agent Events:
// Views
export const JobsView = 'JobsViewOpened';
export const JobHistoryView = 'JobHistoryViewOpened';
export const JobStepsView = 'JobStepsViewOpened';
// Actions
export const RunAgentJob = 'RunAgentJob';
export const StopAgentJob = 'StopAgentJob';
export const DeleteAgentJob = 'DeleteAgentJob';
export const DeleteAgentJobStep = 'DeleteAgentJobStep';
export const DeleteAgentAlert = 'DeleteAgentAlert';
export const DeleteAgentOperator = 'DeleteAgentOperator';
export const DeleteAgentProxy = 'DeleteAgentProxy';

View File

@@ -4,31 +4,10 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
'use strict'; 'use strict';
import * as crypto from 'crypto';
import * as os from 'os';
import { ITelemetryService, ITelemetryData } from 'vs/platform/telemetry/common/telemetry'; import { ITelemetryService, ITelemetryData } from 'vs/platform/telemetry/common/telemetry';
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces'; import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
import { warn } from 'sql/base/common/log'; import { warn } from 'sql/base/common/log';
import { generateUuid } from 'vs/base/common/uuid';
// Generate a unique, deterministic ID for the current user of the extension
export function generateUserId(): Promise<string> {
return new Promise<string>(resolve => {
try {
getmac.getMac((error, macAddress) => {
if (!error) {
resolve(crypto.createHash('sha256').update(macAddress + os.homedir(), 'utf8').digest('hex'));
} else {
resolve(generateUuid()); // fallback
}
});
} catch (err) {
resolve(generateUuid()); // fallback
}
});
}
export interface IConnectionTelemetryData extends ITelemetryData { export interface IConnectionTelemetryData extends ITelemetryData {
provider?: string; provider?: string;
} }

View File

@@ -63,7 +63,7 @@ export class CommandLineService implements ICommandLineProcessing {
// prompt the user for a new connection on startup if no profiles are registered // prompt the user for a new connection on startup if no profiles are registered
this._connectionManagementService.showConnectionDialog(); this._connectionManagementService.showConnectionDialog();
} else if (this._connectionProfile) { } else if (this._connectionProfile) {
this._connectionManagementService.connectIfNotConnected(this._connectionProfile, 'connection') this._connectionManagementService.connectIfNotConnected(this._connectionProfile, 'connection', true)
.then(result => TaskUtilities.newQuery(this._connectionProfile, .then(result => TaskUtilities.newQuery(this._connectionProfile,
this._connectionManagementService, this._connectionManagementService,
this._queryEditorService, this._queryEditorService,

View File

@@ -19,6 +19,7 @@ import { QueryPlanInput } from 'sql/parts/queryPlan/queryPlanInput';
import { NotebookInput, NotebookInputModel, NotebookInputValidator } from 'sql/parts/notebook/notebookInput'; import { NotebookInput, NotebookInputModel, NotebookInputValidator } from 'sql/parts/notebook/notebookInput';
import { Extensions, INotebookProviderRegistry } from 'sql/services/notebook/notebookRegistry'; import { Extensions, INotebookProviderRegistry } from 'sql/services/notebook/notebookRegistry';
import { DEFAULT_NOTEBOOK_PROVIDER } from 'sql/services/notebook/notebookService'; import { DEFAULT_NOTEBOOK_PROVIDER } from 'sql/services/notebook/notebookService';
import { getProviderForFileName } from 'sql/parts/notebook/notebookUtils';
const fs = require('fs'); const fs = require('fs');
@@ -183,17 +184,6 @@ function getNotebookFileExtensions() {
return notebookRegistry.getSupportedFileExtensions(); 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 * Checks whether the given EditorInput is set to either undefined or sql mode
* @param input The EditorInput to check the mode of * @param input The EditorInput to check the mode of

View File

@@ -121,7 +121,7 @@ export interface IConnectionManagementService {
* otherwise tries to make a connection and returns the owner uri when connection is complete * otherwise tries to make a connection and returns the owner uri when connection is complete
* The purpose is connection by default * The purpose is connection by default
*/ */
connectIfNotConnected(connection: IConnectionProfile, purpose?: 'dashboard' | 'insights' | 'connection'): Promise<string>; connectIfNotConnected(connection: IConnectionProfile, purpose?: 'dashboard' | 'insights' | 'connection', saveConnection?: boolean): Promise<string>;
/** /**
* Adds the successful connection to MRU and send the connection error back to the connection handler for failed connections * Adds the successful connection to MRU and send the connection error back to the connection handler for failed connections

View File

@@ -380,14 +380,14 @@ export class ConnectionManagementService extends Disposable implements IConnecti
* otherwise tries to make a connection and returns the owner uri when connection is complete * otherwise tries to make a connection and returns the owner uri when connection is complete
* The purpose is connection by default * The purpose is connection by default
*/ */
public connectIfNotConnected(connection: IConnectionProfile, purpose?: 'dashboard' | 'insights' | 'connection'): Promise<string> { public connectIfNotConnected(connection: IConnectionProfile, purpose?: 'dashboard' | 'insights' | 'connection', saveConnection: boolean = false): Promise<string> {
return new Promise<string>((resolve, reject) => { return new Promise<string>((resolve, reject) => {
let ownerUri: string = Utils.generateUri(connection, purpose); let ownerUri: string = Utils.generateUri(connection, purpose);
if (this._connectionStatusManager.isConnected(ownerUri)) { if (this._connectionStatusManager.isConnected(ownerUri)) {
resolve(this._connectionStatusManager.getOriginalOwnerUri(ownerUri)); resolve(this._connectionStatusManager.getOriginalOwnerUri(ownerUri));
} else { } else {
const options: IConnectionCompletionOptions = { const options: IConnectionCompletionOptions = {
saveTheConnection: false, saveTheConnection: saveConnection,
showConnectionDialogOnError: true, showConnectionDialogOnError: true,
showDashboard: purpose === 'dashboard', showDashboard: purpose === 'dashboard',
params: undefined, params: undefined,
@@ -769,8 +769,19 @@ export class ConnectionManagementService extends Disposable implements IConnecti
return false; return false;
} }
} }
let tokens = await this._accountManagementService.getSecurityToken(account, AzureResource.Sql); let tokensByTenant = await this._accountManagementService.getSecurityToken(account, AzureResource.Sql);
connection.options['azureAccountToken'] = Object.values(tokens)[0].token; let token: string;
let tenantId = connection.azureTenantId;
if (tenantId && tokensByTenant[tenantId]) {
token = tokensByTenant[tenantId].token;
} else {
let tokens = Object.values(tokensByTenant);
if (tokens.length === 0) {
return false;
}
token = Object.values(tokensByTenant)[0].token;
}
connection.options['azureAccountToken'] = token;
connection.options['password'] = ''; connection.options['password'] = '';
return true; return true;
} }

View File

@@ -42,6 +42,7 @@ export class ConnectionProfile extends ProviderConnectionInfo implements interfa
this.savePassword = model.savePassword; this.savePassword = model.savePassword;
this.saveProfile = model.saveProfile; this.saveProfile = model.saveProfile;
this._id = model.id; this._id = model.id;
this.azureTenantId = model.azureTenantId;
} else { } else {
//Default for a new connection //Default for a new connection
this.savePassword = false; this.savePassword = false;
@@ -84,6 +85,14 @@ export class ConnectionProfile extends ProviderConnectionInfo implements interfa
this._id = value; this._id = value;
} }
public get azureTenantId(): string {
return this.options['azureTenantId'];
}
public set azureTenantId(value: string) {
this.options['azureTenantId'] = value;
}
public get groupFullName(): string { public get groupFullName(): string {
return this._groupName; return this._groupName;
} }
@@ -159,7 +168,8 @@ export class ConnectionProfile extends ProviderConnectionInfo implements interfa
userName: this.userName, userName: this.userName,
options: this.options, options: this.options,
saveProfile: this.saveProfile, saveProfile: this.saveProfile,
id: this.id id: this.id,
azureTenantId: this.azureTenantId
}; };
return result; return result;

View File

@@ -52,9 +52,11 @@ export class ConnectionWidget {
private _password: string; private _password: string;
private _rememberPasswordCheckBox: Checkbox; private _rememberPasswordCheckBox: Checkbox;
private _azureAccountDropdown: SelectBox; private _azureAccountDropdown: SelectBox;
private _azureTenantDropdown: SelectBox;
private _refreshCredentialsLinkBuilder: Builder; private _refreshCredentialsLinkBuilder: Builder;
private _addAzureAccountMessage: string = localize('connectionWidget.AddAzureAccount', 'Add an account...'); private _addAzureAccountMessage: string = localize('connectionWidget.AddAzureAccount', 'Add an account...');
private readonly _azureProviderId = 'azurePublicCloud'; private readonly _azureProviderId = 'azurePublicCloud';
private _azureTenantId: string;
private _azureAccountList: sqlops.Account[]; private _azureAccountList: sqlops.Account[];
private _advancedButton: Button; private _advancedButton: Button;
private _callbacks: IConnectionComponentCallbacks; private _callbacks: IConnectionComponentCallbacks;
@@ -215,6 +217,12 @@ export class ConnectionWidget {
let refreshCredentialsBuilder = DialogHelper.appendRow(this._tableContainer, '', 'connection-label', 'connection-input', 'azure-account-row refresh-credentials-link'); let refreshCredentialsBuilder = DialogHelper.appendRow(this._tableContainer, '', 'connection-label', 'connection-input', 'azure-account-row refresh-credentials-link');
this._refreshCredentialsLinkBuilder = refreshCredentialsBuilder.a({ href: '#' }).text(localize('connectionWidget.refreshAzureCredentials', 'Refresh account credentials')); this._refreshCredentialsLinkBuilder = refreshCredentialsBuilder.a({ href: '#' }).text(localize('connectionWidget.refreshAzureCredentials', 'Refresh account credentials'));
// Azure tenant picker
let tenantLabel = localize('connection.azureTenantDropdownLabel', 'Azure AD tenant');
let tenantDropdownBuilder = DialogHelper.appendRow(this._tableContainer, tenantLabel, 'connection-label', 'connection-input', 'azure-account-row azure-tenant-row');
this._azureTenantDropdown = new SelectBox([], undefined, this._contextViewService, tenantDropdownBuilder.getContainer(), { ariaLabel: tenantLabel });
DialogHelper.appendInputSelectBox(tenantDropdownBuilder, this._azureTenantDropdown);
// Database // Database
let databaseOption = this._optionsMaps[ConnectionOptionSpecialType.databaseName]; let databaseOption = this._optionsMaps[ConnectionOptionSpecialType.databaseName];
let databaseNameBuilder = DialogHelper.appendRow(this._tableContainer, databaseOption.displayName, 'connection-label', 'connection-input'); let databaseNameBuilder = DialogHelper.appendRow(this._tableContainer, databaseOption.displayName, 'connection-label', 'connection-input');
@@ -308,6 +316,13 @@ export class ConnectionWidget {
})); }));
} }
if (this._azureTenantDropdown) {
this._toDispose.push(styler.attachSelectBoxStyler(this._azureTenantDropdown, this._themeService));
this._toDispose.push(this._azureTenantDropdown.onDidSelect((selectInfo) => {
this.onAzureTenantSelected(selectInfo.index);
}));
}
if (this._refreshCredentialsLinkBuilder) { if (this._refreshCredentialsLinkBuilder) {
this._toDispose.push(this._refreshCredentialsLinkBuilder.on(DOM.EventType.CLICK, async () => { this._toDispose.push(this._refreshCredentialsLinkBuilder.on(DOM.EventType.CLICK, async () => {
let account = this._azureAccountList.find(account => account.key.accountId === this._azureAccountDropdown.value); let account = this._azureAccountList.find(account => account.key.accountId === this._azureAccountDropdown.value);
@@ -426,7 +441,7 @@ export class ConnectionWidget {
accountDropdownOptions.push(this._addAzureAccountMessage); accountDropdownOptions.push(this._addAzureAccountMessage);
this._azureAccountDropdown.setOptions(accountDropdownOptions); this._azureAccountDropdown.setOptions(accountDropdownOptions);
this._azureAccountDropdown.selectWithOptionName(oldSelection); this._azureAccountDropdown.selectWithOptionName(oldSelection);
this.updateRefreshCredentialsLink(); await this.onAzureAccountSelected();
} }
private async updateRefreshCredentialsLink(): Promise<void> { private async updateRefreshCredentialsLink(): Promise<void> {
@@ -441,7 +456,6 @@ export class ConnectionWidget {
private async onAzureAccountSelected(): Promise<void> { private async onAzureAccountSelected(): Promise<void> {
// Reset the dropdown's validation message if the old selection was not valid but the new one is // Reset the dropdown's validation message if the old selection was not valid but the new one is
this.validateAzureAccountSelection(false); this.validateAzureAccountSelection(false);
this._refreshCredentialsLinkBuilder.display('none');
// Open the add account dialog if needed, then select the added account // Open the add account dialog if needed, then select the added account
if (this._azureAccountDropdown.value === this._addAzureAccountMessage) { if (this._azureAccountDropdown.value === this._addAzureAccountMessage) {
@@ -461,6 +475,35 @@ export class ConnectionWidget {
} }
this.updateRefreshCredentialsLink(); this.updateRefreshCredentialsLink();
// Display the tenant select box if needed
const hideTenantsClassName = 'hide-azure-tenants';
let selectedAccount = this._azureAccountList.find(account => account.key.accountId === this._azureAccountDropdown.value);
if (selectedAccount && selectedAccount.properties.tenants && selectedAccount.properties.tenants.length > 1) {
// There are multiple tenants available so let the user select one
let options = selectedAccount.properties.tenants.map(tenant => tenant.displayName);
this._azureTenantDropdown.setOptions(options);
this._tableContainer.getContainer().classList.remove(hideTenantsClassName);
this.onAzureTenantSelected(0);
} else {
if (selectedAccount && selectedAccount.properties.tenants && selectedAccount.properties.tenants.length === 1) {
this._azureTenantId = selectedAccount.properties.tenants[0].id;
} else {
this._azureTenantId = undefined;
}
this._tableContainer.getContainer().classList.add(hideTenantsClassName);
}
}
private onAzureTenantSelected(tenantIndex: number): void {
this._azureTenantId = undefined;
let account = this._azureAccountList.find(account => account.key.accountId === this._azureAccountDropdown.value);
if (account && account.properties.tenants) {
let tenant = account.properties.tenants[tenantIndex];
if (tenant) {
this._azureTenantId = tenant.id;
}
}
} }
private serverNameChanged(serverName: string) { private serverNameChanged(serverName: string) {
@@ -518,6 +561,7 @@ export class ConnectionWidget {
this._passwordInputBox.value = connectionInfo.password ? Constants.passwordChars : ''; this._passwordInputBox.value = connectionInfo.password ? Constants.passwordChars : '';
this._password = this.getModelValue(connectionInfo.password); this._password = this.getModelValue(connectionInfo.password);
this._saveProfile = connectionInfo.saveProfile; this._saveProfile = connectionInfo.saveProfile;
this._azureTenantId = connectionInfo.azureTenantId;
let groupName: string; let groupName: string;
if (this._saveProfile) { if (this._saveProfile) {
if (!connectionInfo.groupFullName) { if (!connectionInfo.groupFullName) {
@@ -545,6 +589,26 @@ export class ConnectionWidget {
if (this._authTypeSelectBox) { if (this._authTypeSelectBox) {
this.onAuthTypeSelected(this._authTypeSelectBox.value); this.onAuthTypeSelected(this._authTypeSelectBox.value);
} else {
let tableContainerElement = this._tableContainer.getContainer();
tableContainerElement.classList.remove('hide-username-password');
tableContainerElement.classList.add('hide-azure-accounts');
}
if (this.authType === AuthenticationType.AzureMFA) {
this.fillInAzureAccountOptions().then(async () => {
this._azureAccountDropdown.selectWithOptionName(this.getModelValue(connectionInfo.userName));
await this.onAzureAccountSelected();
let tenantId = connectionInfo.azureTenantId;
let account = this._azureAccountList.find(account => account.key.accountId === this._azureAccountDropdown.value);
if (account && account.properties.tenants.length > 1) {
let tenant = account.properties.tenants.find(tenant => tenant.id === tenantId);
if (tenant) {
this._azureTenantDropdown.selectWithOptionName(tenant.displayName);
}
this.onAzureTenantSelected(this._azureTenantDropdown.values.indexOf(this._azureTenantDropdown.value));
}
});
} }
// Disable connect button if - // Disable connect button if -
@@ -712,6 +776,9 @@ export class ConnectionWidget {
model.saveProfile = true; model.saveProfile = true;
model.groupId = this.findGroupId(model.groupFullName); model.groupId = this.findGroupId(model.groupFullName);
} }
if (this.authType === AuthenticationType.AzureMFA) {
model.azureTenantId = this._azureTenantId;
}
} }
return validInputs; return validInputs;
} }

View File

@@ -128,3 +128,7 @@
.hide-refresh-link .azure-account-row.refresh-credentials-link { .hide-refresh-link .azure-account-row.refresh-credentials-link {
display: none; display: none;
} }
.hide-azure-tenants .azure-tenant-row {
display: none;
}

View File

@@ -3,7 +3,6 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { $ } from 'vs/base/browser/dom';
import { escape } from 'sql/base/common/strings'; import { escape } from 'sql/base/common/strings';
export class DBCellValue { 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 { export function textFormatter(row: number, cell: any, value: any, columnDef: any, dataContext: any): string {
let cellClasses = 'grid-cell-value-container'; let cellClasses = 'grid-cell-value-container';
let valueToDisplay: string = ''; let valueToDisplay = '';
let titleValue = '';
if (DBCellValue.isDBCellValue(value)) { if (DBCellValue.isDBCellValue(value)) {
valueToDisplay = 'NULL'; valueToDisplay = 'NULL';
if (!value.isNull) { 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 { } else {
cellClasses += ' missing-value'; cellClasses += ' missing-value';
} }
} else if (typeof value === 'string') { } 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 /** The following code is a rewrite over the both formatter function using dom builder

View File

@@ -17,6 +17,9 @@ import { AlertsViewComponent } from 'sql/parts/jobManagement/views/alertsView.co
import { OperatorsViewComponent } from 'sql/parts/jobManagement/views/operatorsView.component'; import { OperatorsViewComponent } from 'sql/parts/jobManagement/views/operatorsView.component';
import { ProxiesViewComponent } from 'sql/parts/jobManagement/views/proxiesView.component'; import { ProxiesViewComponent } from 'sql/parts/jobManagement/views/proxiesView.component';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import * as TelemetryKeys from 'sql/common/telemetryKeys';
import { telemetryURIDescriptor } from 'vs/platform/telemetry/common/telemetryUtils';
export enum JobActions { export enum JobActions {
Run = 'run', Run = 'run',
@@ -80,7 +83,8 @@ export class RunJobAction extends Action {
constructor( constructor(
@INotificationService private notificationService: INotificationService, @INotificationService private notificationService: INotificationService,
@IJobManagementService private jobManagementService: IJobManagementService, @IJobManagementService private jobManagementService: IJobManagementService,
@IInstantiationService private instantationService: IInstantiationService @IInstantiationService private instantationService: IInstantiationService,
@ITelemetryService private telemetryService: ITelemetryService
) { ) {
super(RunJobAction.ID, RunJobAction.LABEL, 'runJobIcon'); super(RunJobAction.ID, RunJobAction.LABEL, 'runJobIcon');
} }
@@ -89,6 +93,7 @@ export class RunJobAction extends Action {
let jobName = context.agentJobInfo.name; let jobName = context.agentJobInfo.name;
let ownerUri = context.ownerUri; let ownerUri = context.ownerUri;
let refreshAction = this.instantationService.createInstance(JobsRefreshAction); let refreshAction = this.instantationService.createInstance(JobsRefreshAction);
this.telemetryService.publicLog(TelemetryKeys.RunAgentJob);
return new TPromise<boolean>((resolve, reject) => { return new TPromise<boolean>((resolve, reject) => {
this.jobManagementService.jobAction(ownerUri, jobName, JobActions.Run).then(result => { this.jobManagementService.jobAction(ownerUri, jobName, JobActions.Run).then(result => {
if (result.success) { if (result.success) {
@@ -118,7 +123,8 @@ export class StopJobAction extends Action {
constructor( constructor(
@INotificationService private notificationService: INotificationService, @INotificationService private notificationService: INotificationService,
@IJobManagementService private jobManagementService: IJobManagementService, @IJobManagementService private jobManagementService: IJobManagementService,
@IInstantiationService private instantationService: IInstantiationService @IInstantiationService private instantationService: IInstantiationService,
@ITelemetryService private telemetryService: ITelemetryService
) { ) {
super(StopJobAction.ID, StopJobAction.LABEL, 'stopJobIcon'); super(StopJobAction.ID, StopJobAction.LABEL, 'stopJobIcon');
} }
@@ -127,6 +133,7 @@ export class StopJobAction extends Action {
let jobName = context.agentJobInfo.name; let jobName = context.agentJobInfo.name;
let ownerUri = context.ownerUri; let ownerUri = context.ownerUri;
let refreshAction = this.instantationService.createInstance(JobsRefreshAction); let refreshAction = this.instantationService.createInstance(JobsRefreshAction);
this.telemetryService.publicLog(TelemetryKeys.StopAgentJob);
return new TPromise<boolean>((resolve, reject) => { return new TPromise<boolean>((resolve, reject) => {
this.jobManagementService.jobAction(ownerUri, jobName, JobActions.Stop).then(result => { this.jobManagementService.jobAction(ownerUri, jobName, JobActions.Stop).then(result => {
if (result.success) { if (result.success) {
@@ -174,7 +181,8 @@ export class DeleteJobAction extends Action {
constructor( constructor(
@INotificationService private _notificationService: INotificationService, @INotificationService private _notificationService: INotificationService,
@IJobManagementService private _jobService: IJobManagementService @IJobManagementService private _jobService: IJobManagementService,
@ITelemetryService private _telemetryService: ITelemetryService
) { ) {
super(DeleteJobAction.ID, DeleteJobAction.LABEL); super(DeleteJobAction.ID, DeleteJobAction.LABEL);
} }
@@ -188,6 +196,7 @@ export class DeleteJobAction extends Action {
[{ [{
label: DeleteJobAction.LABEL, label: DeleteJobAction.LABEL,
run: () => { run: () => {
this._telemetryService.publicLog(TelemetryKeys.DeleteAgentJob);
self._jobService.deleteJob(actionInfo.ownerUri, actionInfo.targetObject).then(result => { self._jobService.deleteJob(actionInfo.ownerUri, actionInfo.targetObject).then(result => {
if (!result || !result.success) { if (!result || !result.success) {
let errorMessage = nls.localize("jobaction.failedToDeleteJob", "Could not delete job '{0}'.\nError: {1}", let errorMessage = nls.localize("jobaction.failedToDeleteJob", "Could not delete job '{0}'.\nError: {1}",
@@ -234,7 +243,8 @@ export class DeleteStepAction extends Action {
constructor( constructor(
@INotificationService private _notificationService: INotificationService, @INotificationService private _notificationService: INotificationService,
@IJobManagementService private _jobService: IJobManagementService, @IJobManagementService private _jobService: IJobManagementService,
@IInstantiationService private instantationService: IInstantiationService @IInstantiationService private instantationService: IInstantiationService,
@ITelemetryService private _telemetryService: ITelemetryService
) { ) {
super(DeleteStepAction.ID, DeleteStepAction.LABEL); super(DeleteStepAction.ID, DeleteStepAction.LABEL);
} }
@@ -249,6 +259,7 @@ export class DeleteStepAction extends Action {
[{ [{
label: DeleteStepAction.LABEL, label: DeleteStepAction.LABEL,
run: () => { run: () => {
this._telemetryService.publicLog(TelemetryKeys.DeleteAgentJobStep);
self._jobService.deleteJobStep(actionInfo.ownerUri, actionInfo.targetObject).then(result => { self._jobService.deleteJobStep(actionInfo.ownerUri, actionInfo.targetObject).then(result => {
if (!result || !result.success) { if (!result || !result.success) {
let errorMessage = nls.localize("jobaction.failedToDeleteStep", "Could not delete step '{0}'.\nError: {1}", let errorMessage = nls.localize("jobaction.failedToDeleteStep", "Could not delete step '{0}'.\nError: {1}",
@@ -318,7 +329,8 @@ export class DeleteAlertAction extends Action {
constructor( constructor(
@INotificationService private _notificationService: INotificationService, @INotificationService private _notificationService: INotificationService,
@IJobManagementService private _jobService: IJobManagementService @IJobManagementService private _jobService: IJobManagementService,
@ITelemetryService private _telemetryService: ITelemetryService
) { ) {
super(DeleteAlertAction.ID, DeleteAlertAction.LABEL); super(DeleteAlertAction.ID, DeleteAlertAction.LABEL);
} }
@@ -332,6 +344,7 @@ export class DeleteAlertAction extends Action {
[{ [{
label: DeleteAlertAction.LABEL, label: DeleteAlertAction.LABEL,
run: () => { run: () => {
this._telemetryService.publicLog(TelemetryKeys.DeleteAgentAlert);
self._jobService.deleteAlert(actionInfo.ownerUri, actionInfo.targetObject).then(result => { self._jobService.deleteAlert(actionInfo.ownerUri, actionInfo.targetObject).then(result => {
if (!result || !result.success) { if (!result || !result.success) {
let errorMessage = nls.localize("jobaction.failedToDeleteAlert", "Could not delete alert '{0}'.\nError: {1}", let errorMessage = nls.localize("jobaction.failedToDeleteAlert", "Could not delete alert '{0}'.\nError: {1}",
@@ -397,7 +410,8 @@ export class DeleteOperatorAction extends Action {
constructor( constructor(
@INotificationService private _notificationService: INotificationService, @INotificationService private _notificationService: INotificationService,
@IJobManagementService private _jobService: IJobManagementService @IJobManagementService private _jobService: IJobManagementService,
@ITelemetryService private _telemetryService: ITelemetryService
) { ) {
super(DeleteOperatorAction.ID, DeleteOperatorAction.LABEL); super(DeleteOperatorAction.ID, DeleteOperatorAction.LABEL);
} }
@@ -411,6 +425,7 @@ export class DeleteOperatorAction extends Action {
[{ [{
label: DeleteOperatorAction.LABEL, label: DeleteOperatorAction.LABEL,
run: () => { run: () => {
this._telemetryService.publicLog(TelemetryKeys.DeleteAgentOperator);
self._jobService.deleteOperator(actionInfo.ownerUri, actionInfo.targetObject).then(result => { self._jobService.deleteOperator(actionInfo.ownerUri, actionInfo.targetObject).then(result => {
if (!result || !result.success) { if (!result || !result.success) {
let errorMessage = nls.localize("jobaction.failedToDeleteOperator", "Could not delete operator '{0}'.\nError: {1}", let errorMessage = nls.localize("jobaction.failedToDeleteOperator", "Could not delete operator '{0}'.\nError: {1}",
@@ -477,7 +492,8 @@ export class DeleteProxyAction extends Action {
constructor( constructor(
@INotificationService private _notificationService: INotificationService, @INotificationService private _notificationService: INotificationService,
@IJobManagementService private _jobService: IJobManagementService @IJobManagementService private _jobService: IJobManagementService,
@ITelemetryService private _telemetryService: ITelemetryService
) { ) {
super(DeleteProxyAction.ID, DeleteProxyAction.LABEL); super(DeleteProxyAction.ID, DeleteProxyAction.LABEL);
} }
@@ -491,6 +507,7 @@ export class DeleteProxyAction extends Action {
[{ [{
label: DeleteProxyAction.LABEL, label: DeleteProxyAction.LABEL,
run: () => { run: () => {
this._telemetryService.publicLog(TelemetryKeys.DeleteAgentProxy);
self._jobService.deleteProxy(actionInfo.ownerUri, actionInfo.targetObject).then(result => { self._jobService.deleteProxy(actionInfo.ownerUri, actionInfo.targetObject).then(result => {
if (!result || !result.success) { if (!result || !result.success) {
let errorMessage = nls.localize("jobaction.failedToDeleteProxy", "Could not delete proxy '{0}'.\nError: {1}", let errorMessage = nls.localize("jobaction.failedToDeleteProxy", "Could not delete proxy '{0}'.\nError: {1}",

View File

@@ -77,7 +77,7 @@
<!-- Job History details --> <!-- Job History details -->
<div class='history-details'> <div class='history-details'>
<!-- Previous run list --> <!-- Previous run list -->
<div class="prev-run-list-container" style="min-width: 275px; height: 75vh"> <div class="prev-run-list-container" style="min-width: 250px">
<table *ngIf="_showPreviousRuns === true"> <table *ngIf="_showPreviousRuns === true">
<tr> <tr>
<td class="date-column"> <td class="date-column">
@@ -89,7 +89,9 @@
</tr> </tr>
</table> </table>
<h3 *ngIf="_showPreviousRuns === false" style="text-align: center">No Previous Runs Available</h3> <h3 *ngIf="_showPreviousRuns === false" style="text-align: center">No Previous Runs Available</h3>
<div #table class="step-table prev-run-list" style="position: relative; height: 100%; width: 100%"></div> <div class="step-table prev-run-list" style="position: relative; width: 100%">
<div #table style="position: absolute; width: 100%; height: 100%"></div>
</div>
</div> </div>
<!-- Job Steps --> <!-- Job Steps -->
<div class="job-steps" id="job-steps"> <div class="job-steps" id="job-steps">
@@ -154,8 +156,8 @@
</td> </td>
</tr> </tr>
</table> </table>
<div #jobsteps style="height: 100%"> <div #jobsteps style="flex: 1 1 auto; position: relative">
<jobstepsview-component *ngIf="showSteps === true"></jobstepsview-component> <jobstepsview-component *ngIf="showSteps === true" style="position: absolute; height: 100%; width: 100%"></jobstepsview-component>
</div> </div>
<h3 *ngIf="showSteps === false">No Steps Available</h3> <h3 *ngIf="showSteps === false">No Steps Available</h3>
</div> </div>

View File

@@ -23,13 +23,14 @@ import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/work
import { attachListStyler } from 'vs/platform/theme/common/styler'; import { attachListStyler } from 'vs/platform/theme/common/styler';
import { Tree } from 'vs/base/parts/tree/browser/treeImpl'; import { Tree } from 'vs/base/parts/tree/browser/treeImpl';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { ScrollbarVisibility } from 'vs/base/common/scrollable'; import { ScrollbarVisibility } from 'vs/base/common/scrollable';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { JobManagementView } from 'sql/parts/jobManagement/views/jobManagementView'; import { JobManagementView } from 'sql/parts/jobManagement/views/jobManagementView';
import { TabChild } from 'sql/base/browser/ui/panel/tab.component'; import { TabChild } from 'sql/base/browser/ui/panel/tab.component';
import { IDashboardService } from 'sql/services/dashboard/common/dashboardService'; import { IDashboardService } from 'sql/services/dashboard/common/dashboardService';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import * as TelemetryKeys from 'sql/common/telemetryKeys';
export const DASHBOARD_SELECTOR: string = 'jobhistory-component'; export const DASHBOARD_SELECTOR: string = 'jobhistory-component';
@@ -77,7 +78,8 @@ export class JobHistoryComponent extends JobManagementView implements OnInit {
@Inject(IContextMenuService) private contextMenuService: IContextMenuService, @Inject(IContextMenuService) private contextMenuService: IContextMenuService,
@Inject(IJobManagementService) private _jobManagementService: IJobManagementService, @Inject(IJobManagementService) private _jobManagementService: IJobManagementService,
@Inject(IKeybindingService) keybindingService: IKeybindingService, @Inject(IKeybindingService) keybindingService: IKeybindingService,
@Inject(IDashboardService) dashboardService: IDashboardService @Inject(IDashboardService) dashboardService: IDashboardService,
@Inject(ITelemetryService) private _telemetryService: ITelemetryService
) { ) {
super(commonService, dashboardService, contextMenuService, keybindingService, instantiationService); super(commonService, dashboardService, contextMenuService, keybindingService, instantiationService);
this._treeController = new JobHistoryController(); this._treeController = new JobHistoryController();
@@ -141,9 +143,9 @@ export class JobHistoryComponent extends JobManagementView implements OnInit {
renderer: this._treeRenderer renderer: this._treeRenderer
}, {verticalScrollMode: ScrollbarVisibility.Visible}); }, {verticalScrollMode: ScrollbarVisibility.Visible});
this._register(attachListStyler(this._tree, this.themeService)); this._register(attachListStyler(this._tree, this.themeService));
this._tree.layout(JobHistoryComponent.INITIAL_TREE_HEIGHT); this._tree.layout(dom.getContentHeight(this._tableContainer.nativeElement));
this.initActionBar(); this.initActionBar();
this._telemetryService.publicLog(TelemetryKeys.JobHistoryView);
} }
private loadHistory() { private loadHistory() {
@@ -293,6 +295,7 @@ export class JobHistoryComponent extends JobManagementView implements OnInit {
if (historyDetails && statusBar) { if (historyDetails && statusBar) {
let historyBottom = historyDetails.getBoundingClientRect().bottom; let historyBottom = historyDetails.getBoundingClientRect().bottom;
let statusTop = statusBar.getBoundingClientRect().top; let statusTop = statusBar.getBoundingClientRect().top;
let height: number = statusTop - historyBottom - JobHistoryComponent.HEADING_HEIGHT; let height: number = statusTop - historyBottom - JobHistoryComponent.HEADING_HEIGHT;
if (this._table) { if (this._table) {
@@ -302,14 +305,7 @@ export class JobHistoryComponent extends JobManagementView implements OnInit {
} }
if (this._tree) { if (this._tree) {
this._tree.layout(height); this._tree.layout(dom.getContentHeight(this._tableContainer.nativeElement));
}
if (this._jobStepsView) {
let element = this._jobStepsView.nativeElement as HTMLElement;
if (element) {
element.style.height = height + 'px';
}
} }
} }
} }

View File

@@ -177,17 +177,17 @@ table.step-list tr.step-row td {
} }
.history-details { .history-details {
height: 100%; flex: 1 1 auto;
display: flex; display: flex;
} }
.history-details > .job-steps { .history-details > .job-steps {
display: block; flex: 1 1 auto;
display: flex;
border-left: 3px solid #f4f4f4; border-left: 3px solid #f4f4f4;
padding-left: 10px; padding-left: 10px;
height: 100%; flex-direction: column;
width: 90%; width: 100%;
overflow-y: scroll;
} }
.vs-dark .history-details > .job-steps { .vs-dark .history-details > .job-steps {
@@ -241,13 +241,22 @@ table.step-list tr.step-row td {
width: 140px; width: 140px;
} }
.steps-tree .monaco-tree .monaco-tree-row { .step-table {
white-space: normal; flex: 1 1 auto;
min-height: 40px !important;
} }
jobhistory-component .jobhistory-heading-container { .prev-run-list-container {
display: -webkit-box; display: flex;
flex-direction: column;
}
jobhistory-component {
display: flex;
flex-direction: column;
}
jobhistory-component > .jobhistory-heading-container {
display: flex;
} }
jobhistory-component > .jobhistory-heading-container > .icon.in-progress { jobhistory-component > .jobhistory-heading-container > .icon.in-progress {
@@ -267,4 +276,4 @@ jobhistory-component > .agent-actionbar-container .monaco-action-bar > ul.action
.hc-black jobhistory-component > .agent-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; border-top: 3px solid #2b56f2;
} }

View File

@@ -22,4 +22,6 @@
</td> </td>
</tr> </tr>
</table> </table>
<div class='steps-tree' #table style="height: 100%; width: 100%"></div> <div class='steps-tree' style="flex: 1 1 auto; position: relative">
<div #table style="position: absolute; height: 100%; width: 100%" ></div>
</div>

View File

@@ -5,6 +5,7 @@
import 'vs/css!./jobStepsView'; import 'vs/css!./jobStepsView';
import * as dom from 'vs/base/browser/dom';
import { OnInit, Component, Inject, forwardRef, ElementRef, ChangeDetectorRef, ViewChild, Injectable, AfterContentChecked } from '@angular/core'; import { OnInit, Component, Inject, forwardRef, ElementRef, ChangeDetectorRef, ViewChild, Injectable, AfterContentChecked } from '@angular/core';
import { attachListStyler } from 'vs/platform/theme/common/styler'; import { attachListStyler } from 'vs/platform/theme/common/styler';
import { Tree } from 'vs/base/parts/tree/browser/treeImpl'; import { Tree } from 'vs/base/parts/tree/browser/treeImpl';
@@ -20,7 +21,8 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { TabChild } from 'sql/base/browser/ui/panel/tab.component'; import { TabChild } from 'sql/base/browser/ui/panel/tab.component';
import { IJobManagementService } from 'sql/parts/jobManagement/common/interfaces'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import * as TelemetryKeys from 'sql/common/telemetryKeys';
export const JOBSTEPSVIEW_SELECTOR: string = 'jobstepsview-component'; export const JOBSTEPSVIEW_SELECTOR: string = 'jobstepsview-component';
@@ -36,7 +38,6 @@ export class JobStepsViewComponent extends JobManagementView implements OnInit,
private _treeDataSource = new JobStepsViewDataSource(); private _treeDataSource = new JobStepsViewDataSource();
private _treeRenderer = new JobStepsViewRenderer(); private _treeRenderer = new JobStepsViewRenderer();
private _treeFilter = new JobStepsViewFilter(); private _treeFilter = new JobStepsViewFilter();
private _pageSize = 1024;
@ViewChild('table') private _tableContainer: ElementRef; @ViewChild('table') private _tableContainer: ElementRef;
@@ -49,7 +50,8 @@ export class JobStepsViewComponent extends JobManagementView implements OnInit,
@Inject(IInstantiationService) instantiationService: IInstantiationService, @Inject(IInstantiationService) instantiationService: IInstantiationService,
@Inject(IContextMenuService) contextMenuService: IContextMenuService, @Inject(IContextMenuService) contextMenuService: IContextMenuService,
@Inject(IKeybindingService) keybindingService: IKeybindingService, @Inject(IKeybindingService) keybindingService: IKeybindingService,
@Inject(IDashboardService) dashboardService: IDashboardService @Inject(IDashboardService) dashboardService: IDashboardService,
@Inject(ITelemetryService) private _telemetryService: ITelemetryService
) { ) {
super(commonService, dashboardService, contextMenuService, keybindingService, instantiationService); super(commonService, dashboardService, contextMenuService, keybindingService, instantiationService);
} }
@@ -57,17 +59,8 @@ export class JobStepsViewComponent extends JobManagementView implements OnInit,
ngAfterContentChecked() { ngAfterContentChecked() {
if (this._jobHistoryComponent.stepRows.length > 0) { if (this._jobHistoryComponent.stepRows.length > 0) {
this._treeDataSource.data = this._jobHistoryComponent.stepRows; this._treeDataSource.data = this._jobHistoryComponent.stepRows;
if (!this._tree) {
this._tree = new Tree(this._tableContainer.nativeElement, {
controller: this._treeController,
dataSource: this._treeDataSource,
filter: this._treeFilter,
renderer: this._treeRenderer
}, { verticalScrollMode: ScrollbarVisibility.Visible });
this._register(attachListStyler(this._tree, this.themeService));
}
this._tree.layout(this._pageSize);
this._tree.setInput(new JobStepsViewModel()); this._tree.setInput(new JobStepsViewModel());
this.layout();
$('jobstepsview-component .steps-tree .monaco-tree').attr('tabIndex', '-1'); $('jobstepsview-component .steps-tree .monaco-tree').attr('tabIndex', '-1');
$('jobstepsview-component .steps-tree .monaco-tree-row').attr('tabIndex', '0'); $('jobstepsview-component .steps-tree .monaco-tree-row').attr('tabIndex', '0');
} }
@@ -79,14 +72,20 @@ export class JobStepsViewComponent extends JobManagementView implements OnInit,
dataSource: this._treeDataSource, dataSource: this._treeDataSource,
filter: this._treeFilter, filter: this._treeFilter,
renderer: this._treeRenderer renderer: this._treeRenderer
}, {verticalScrollMode: ScrollbarVisibility.Visible}); }, {verticalScrollMode: ScrollbarVisibility.Visible, horizontalScrollMode: ScrollbarVisibility.Visible });
this.layout();
this._register(attachListStyler(this._tree, this.themeService)); this._register(attachListStyler(this._tree, this.themeService));
this._telemetryService.publicLog(TelemetryKeys.JobStepsView);
} }
public onFirstVisible() { public onFirstVisible() {
} }
public layout() { public layout() {
if (this._tree) {
let treeheight = dom.getContentHeight(this._tableContainer.nativeElement);
this._tree.layout(treeheight);
}
} }
} }

View File

@@ -78,5 +78,6 @@
} }
jobstepsview-component { jobstepsview-component {
padding-top: 10px; display: flex;
} flex-direction: column;
}

View File

@@ -5,6 +5,7 @@
import * as DOM from 'vs/base/browser/dom'; import * as DOM from 'vs/base/browser/dom';
import { $ } from 'vs/base/browser/builder';
import * as tree from 'vs/base/parts/tree/browser/tree'; import * as tree from 'vs/base/parts/tree/browser/tree';
import * as TreeDefaults from 'vs/base/parts/tree/browser/treeDefaults'; import * as TreeDefaults from 'vs/base/parts/tree/browser/treeDefaults';
import { Promise, TPromise } from 'vs/base/common/winjs.base'; import { Promise, TPromise } from 'vs/base/common/winjs.base';
@@ -86,7 +87,7 @@ export class JobStepsViewRenderer implements tree.IRenderer {
private _statusIcon: HTMLElement; private _statusIcon: HTMLElement;
public getHeight(tree: tree.ITree, element: JobStepsViewRow): number { public getHeight(tree: tree.ITree, element: JobStepsViewRow): number {
return 22 * Math.ceil(element.message.length/JobManagementUtilities.jobMessageLength); return 40;
} }
public getTemplateId(tree: tree.ITree, element: JobStepsViewRow | JobStepsViewModel): string { public getTemplateId(tree: tree.ITree, element: JobStepsViewRow | JobStepsViewModel): string {
@@ -118,6 +119,7 @@ export class JobStepsViewRenderer implements tree.IRenderer {
let stepMessageCol: HTMLElement = DOM.$('div'); let stepMessageCol: HTMLElement = DOM.$('div');
stepMessageCol.className = 'tree-message-col'; stepMessageCol.className = 'tree-message-col';
stepMessageCol.innerText = element.message; stepMessageCol.innerText = element.message;
$(templateData.label).empty();
templateData.label.appendChild(stepIdCol); templateData.label.appendChild(stepIdCol);
templateData.label.appendChild(stepNameCol); templateData.label.appendChild(stepNameCol);
templateData.label.appendChild(stepMessageCol); templateData.label.appendChild(stepMessageCol);

View File

@@ -37,6 +37,8 @@ import { IDashboardService } from 'sql/services/dashboard/common/dashboardServic
import { escape } from 'sql/base/common/strings'; import { escape } from 'sql/base/common/strings';
import { IWorkbenchThemeService, IColorTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { IWorkbenchThemeService, IColorTheme } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { tableBackground, cellBackground, cellBorderColor } from 'sql/common/theme/colors'; import { tableBackground, cellBackground, cellBorderColor } from 'sql/common/theme/colors';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import * as TelemetryKeys from 'sql/common/telemetryKeys';
export const JOBSVIEW_SELECTOR: string = 'jobsview-component'; export const JOBSVIEW_SELECTOR: string = 'jobsview-component';
export const ROW_HEIGHT: number = 45; export const ROW_HEIGHT: number = 45;
@@ -106,7 +108,8 @@ export class JobsViewComponent extends JobManagementView implements OnInit, OnDe
@Inject(IInstantiationService) instantiationService: IInstantiationService, @Inject(IInstantiationService) instantiationService: IInstantiationService,
@Inject(IContextMenuService) contextMenuService: IContextMenuService, @Inject(IContextMenuService) contextMenuService: IContextMenuService,
@Inject(IKeybindingService) keybindingService: IKeybindingService, @Inject(IKeybindingService) keybindingService: IKeybindingService,
@Inject(IDashboardService) _dashboardService: IDashboardService @Inject(IDashboardService) _dashboardService: IDashboardService,
@Inject(ITelemetryService) private _telemetryService: ITelemetryService
) { ) {
super(commonService, _dashboardService, contextMenuService, keybindingService, instantiationService); super(commonService, _dashboardService, contextMenuService, keybindingService, instantiationService);
this._didTabChange = false; this._didTabChange = false;
@@ -127,6 +130,7 @@ export class JobsViewComponent extends JobManagementView implements OnInit, OnDe
this._visibilityElement = this._gridEl; this._visibilityElement = this._gridEl;
this._parentComponent = this._agentViewComponent; this._parentComponent = this._agentViewComponent;
this._register(this._themeService.onDidColorThemeChange(e => this.updateTheme(e))); this._register(this._themeService.onDidColorThemeChange(e => this.updateTheme(e)));
this._telemetryService.publicLog(TelemetryKeys.JobsView);
} }
ngOnDestroy() { ngOnDestroy() {
@@ -933,19 +937,19 @@ export class JobsViewComponent extends JobManagementView implements OnInit, OnDe
// add steps // add steps
if (this.jobSteps && this.jobSteps[jobId]) { if (this.jobSteps && this.jobSteps[jobId]) {
let steps = this.jobSteps[jobId]; let steps = this.jobSteps[jobId];
job[0].JobSteps = steps; job[0].jobSteps = steps;
} }
// add schedules // add schedules
if (this.jobSchedules && this.jobSchedules[jobId]) { if (this.jobSchedules && this.jobSchedules[jobId]) {
let schedules = this.jobSchedules[jobId]; let schedules = this.jobSchedules[jobId];
job[0].JobSchedules = schedules; job[0].jobSchedules = schedules;
} }
// add alerts // add alerts
if (this.jobAlerts && this.jobAlerts[jobId]) { if (this.jobAlerts && this.jobAlerts[jobId]) {
let alerts = this.jobAlerts[jobId]; let alerts = this.jobAlerts[jobId];
job[0].Alerts = alerts; job[0].alerts = alerts;
} }
return job && job.length > 0 ? job[0] : undefined; return job && job.length > 0 ? job[0] : undefined;
} }

View File

@@ -119,7 +119,20 @@ export class QueryTextEditor extends BaseTextEditor {
if (!this._config) { if (!this._config) {
this._config = new Configuration(undefined, editorWidget.getDomNode()); this._config = new Configuration(undefined, editorWidget.getDomNode());
} }
let editorHeightUsingLines = this._config.editor.lineHeight * editorWidget.getModel().getLineCount(); let editorWidgetModel = editorWidget.getModel();
let lineCount = editorWidgetModel.getLineCount();
// Need to also keep track of lines that wrap; if we just keep into account line count, then the editor's height would not be
// tall enough and we would need to show a scrollbar. Unfortunately, it looks like there isn't any metadata saved in a ICodeEditor
// around max column length for an editor (which we could leverage to see if we need to loop through every line to determine
// number of lines that wrap). Finally, viewportColumn is calculated on editor resizing automatically; we can use it to ensure
// that the viewportColumn will always be greater than any character's column in an editor.
let numberWrappedLines = 0;
for (let line = 1; line <= lineCount; line++) {
if (editorWidgetModel.getLineMaxColumn(line) >= this._config.editor.layoutInfo.viewportColumn - 1) {
numberWrappedLines += Math.ceil(editorWidgetModel.getLineMaxColumn(line) / this._config.editor.layoutInfo.viewportColumn);
}
}
let editorHeightUsingLines = this._config.editor.lineHeight * (lineCount + numberWrappedLines);
let editorHeightUsingMinHeight = Math.max(editorHeightUsingLines, this._minHeight); let editorHeightUsingMinHeight = Math.max(editorHeightUsingLines, this._minHeight);
this.setHeight(editorHeightUsingMinHeight); this.setHeight(editorHeightUsingMinHeight);
} }

View File

@@ -9,6 +9,6 @@
</div> </div>
<div #editor class="editor" style="flex: 1 1 auto; overflow: hidden;"> <div #editor class="editor" style="flex: 1 1 auto; overflow: hidden;">
</div> </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 #moreactions class="toolbar" style="flex: 0 0 auto; display: flex; flex-flow:column; width: 20px; min-height: 20px; max-height: 20px; padding-top: 0px; orientation: portrait">
</div> </div>
</div> </div>

View File

@@ -29,7 +29,7 @@ import { IModelService } from 'vs/editor/common/services/modelService';
import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { ICellModel } from 'sql/parts/notebook/models/modelInterfaces'; import { ICellModel } from 'sql/parts/notebook/models/modelInterfaces';
import { Taskbar } from 'sql/base/browser/ui/taskbar/taskbar'; import { Taskbar } from 'sql/base/browser/ui/taskbar/taskbar';
import { RunCellAction, CellContext, NotebookCellToggleMoreActon } from 'sql/parts/notebook/cellViews/codeActions'; import { RunCellAction, DeleteCellAction, AddCellAction, CellContext } from 'sql/parts/notebook/cellViews/codeActions';
import { NotebookModel } from 'sql/parts/notebook/models/notebookModel'; import { NotebookModel } from 'sql/parts/notebook/models/notebookModel';
import { ToggleMoreWidgetAction } from 'sql/parts/dashboard/common/actions'; import { ToggleMoreWidgetAction } from 'sql/parts/dashboard/common/actions';
import { CellTypes } from 'sql/parts/notebook/models/contracts'; import { CellTypes } from 'sql/parts/notebook/models/contracts';
@@ -59,14 +59,15 @@ export class CodeComponent extends AngularDisposable implements OnInit, OnChange
} }
protected _actionBar: Taskbar; protected _actionBar: Taskbar;
protected _moreActions: ActionBar;
private readonly _minimumHeight = 30; private readonly _minimumHeight = 30;
private _editor: QueryTextEditor; private _editor: QueryTextEditor;
private _editorInput: UntitledEditorInput; private _editorInput: UntitledEditorInput;
private _editorModel: ITextModel; private _editorModel: ITextModel;
private _uri: string; private _uri: string;
private _model: NotebookModel; private _model: NotebookModel;
private _actions: Action[] = [];
private _activeCellId: string; private _activeCellId: string;
private _toggleMoreActions: NotebookCellToggleMoreActon;
constructor( constructor(
@Inject(forwardRef(() => CommonServiceInterface)) private _bootstrapService: CommonServiceInterface, @Inject(forwardRef(() => CommonServiceInterface)) private _bootstrapService: CommonServiceInterface,
@@ -80,6 +81,13 @@ export class CodeComponent extends AngularDisposable implements OnInit, OnChange
@Inject(INotificationService) private notificationService: INotificationService, @Inject(INotificationService) private notificationService: INotificationService,
) { ) {
super(); super();
this._actions.push(
this._instantiationService.createInstance(AddCellAction, 'codeBefore', localize('codeBefore', 'Insert Code before'), CellTypes.Code, false),
this._instantiationService.createInstance(AddCellAction, 'codeAfter', localize('codeAfter', 'Insert Code after'), CellTypes.Code, true),
this._instantiationService.createInstance(AddCellAction, 'markdownBefore', localize('markdownBefore', 'Insert Markdown before'), CellTypes.Markdown, false),
this._instantiationService.createInstance(AddCellAction, 'markdownAfter', localize('markdownAfter', 'Insert Markdown after'), CellTypes.Markdown, true),
this._instantiationService.createInstance(DeleteCellAction, 'delete', localize('delete', 'Delete'))
);
} }
ngOnInit() { ngOnInit() {
@@ -90,20 +98,20 @@ export class CodeComponent extends AngularDisposable implements OnInit, OnChange
} }
} }
ngAfterViewInit() {
this._toggleMoreActions = new NotebookCellToggleMoreActon(
this._instantiationService,
this.contextMenuService,
this.notificationService,
this.moreActionsElementRef,
this.model);
}
ngOnChanges(changes: { [propKey: string]: SimpleChange }) { ngOnChanges(changes: { [propKey: string]: SimpleChange }) {
this.updateLanguageMode(); this.updateLanguageMode();
this.updateModel(); this.updateModel();
if (this._toggleMoreActions) { for (let propName in changes) {
this._toggleMoreActions.onChange(this.cellModel, changes); if (propName === 'activeCellId') {
let changedProp = changes[propName];
if (this.cellModel.id === changedProp.currentValue) {
this.toggleMoreActions(true);
}
else {
this.toggleMoreActions(false);
}
break;
}
} }
} }
@@ -165,6 +173,21 @@ export class CodeComponent extends AngularDisposable implements OnInit, OnChange
]); ]);
} }
private toggleMoreActions(showIcon: boolean) {
let context = new CellContext(this.model, this.cellModel);
let moreActionsElement = <HTMLElement>this.moreActionsElementRef.nativeElement;
if (showIcon) {
if (moreActionsElement.childNodes.length > 0) {
moreActionsElement.removeChild(moreActionsElement.childNodes[0]);
}
this._moreActions = new ActionBar(moreActionsElement, { orientation: ActionsOrientation.VERTICAL });
this._moreActions.context = { target: moreActionsElement };
this._moreActions.push(this._instantiationService.createInstance(ToggleMoreWidgetAction, this._actions, context), { icon: showIcon, label: false });
}
else if (moreActionsElement.childNodes.length > 0) {
moreActionsElement.removeChild(moreActionsElement.childNodes[0]);
}
}
private createUri(): URI { private createUri(): URI {
let uri = URI.from({ scheme: Schemas.untitled, path: `notebook-editor-${this.cellModel.id}` }); let uri = URI.from({ scheme: Schemas.untitled, path: `notebook-editor-${this.cellModel.id}` });
@@ -192,8 +215,8 @@ export class CodeComponent extends AngularDisposable implements OnInit, OnChange
let toolbarEl = <HTMLElement>this.toolbarElement.nativeElement; let toolbarEl = <HTMLElement>this.toolbarElement.nativeElement;
toolbarEl.style.borderRightColor = theme.getColor(themeColors.SIDE_BAR_BACKGROUND, true).toString(); toolbarEl.style.borderRightColor = theme.getColor(themeColors.SIDE_BAR_BACKGROUND, true).toString();
let moreactionsEl = <HTMLElement>this.moreActionsElementRef.nativeElement; let moreActionsEl = <HTMLElement>this.moreActionsElementRef.nativeElement;
moreactionsEl.style.borderRightColor = theme.getColor(themeColors.SIDE_BAR_BACKGROUND, true).toString(); moreActionsEl.style.borderRightColor = theme.getColor(themeColors.SIDE_BAR_BACKGROUND, true).toString();
} }
} }

View File

@@ -5,20 +5,13 @@
import { nb } from 'sqlops'; import { nb } from 'sqlops';
import { ElementRef, SimpleChange } from '@angular/core';
import { Action } from 'vs/base/common/actions'; import { Action } from 'vs/base/common/actions';
import { TPromise } from 'vs/base/common/winjs.base'; import { TPromise } from 'vs/base/common/winjs.base';
import { localize } from 'vs/nls'; import { localize } from 'vs/nls';
import { ActionBar, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; import { CellType } from 'sql/parts/notebook/models/contracts';
import { ToggleMoreWidgetAction } from 'sql/parts/dashboard/common/actions';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { CellType, CellTypes } from 'sql/parts/notebook/models/contracts';
import { NotebookModel } from 'sql/parts/notebook/models/notebookModel'; import { NotebookModel } from 'sql/parts/notebook/models/notebookModel';
import { getErrorMessage } from 'sql/parts/notebook/notebookUtils'; 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 { ICellModel, FutureInternal } from 'sql/parts/notebook/models/modelInterfaces';
import { ToggleableAction } from 'sql/parts/notebook/notebookActions'; import { ToggleableAction } from 'sql/parts/notebook/notebookActions';
@@ -191,51 +184,4 @@ export class DeleteCellAction extends CellActionBase {
} }
return Promise.resolve(); return Promise.resolve();
} }
}
export class NotebookCellToggleMoreActon {
private _actions: Action[] = [];
private _moreActions: ActionBar;
constructor (
private _instantiationService: IInstantiationService,
private contextMenuService: IContextMenuService,
private notificationService: INotificationService,
private moreActionElementRef: ElementRef,
private model: NotebookModel
) {
this._actions.push(
this._instantiationService.createInstance(AddCellAction, 'codeBefore', localize('codeBefore', 'Insert Code before'), CellTypes.Code, false, this.notificationService),
this._instantiationService.createInstance(AddCellAction, 'codeBefore', localize('codeAfter', 'Insert Code after'), CellTypes.Code, true, this.notificationService),
this._instantiationService.createInstance(AddCellAction, 'markdownBefore', localize('markdownBefore', 'Insert Markdown before'), CellTypes.Markdown, false, this.notificationService),
this._instantiationService.createInstance(AddCellAction, 'markdownAfter', localize('markdownAfter', 'Insert Markdown after'), CellTypes.Markdown, true, this.notificationService),
this._instantiationService.createInstance(DeleteCellAction, 'delete', localize('delete', 'Delete'), this.notificationService)
);
let moreActionsElement = <HTMLElement>this.moreActionElementRef.nativeElement;
this._moreActions = new ActionBar(moreActionsElement, { orientation: ActionsOrientation.VERTICAL });
this._moreActions.context = { target: moreActionsElement };
}
toggle(showIcon: boolean): void {
if (showIcon) {
this._moreActions.push(this._instantiationService.createInstance(ToggleMoreWidgetAction, this._actions, this.model, this.contextMenuService), { icon: showIcon, label: false });
} else if (this._moreActions) {
this._moreActions.clear();
}
}
public onChange(cellModel: ICellModel, changes: { [propKey: string]: SimpleChange }): void {
for (let propName in changes) {
if (propName === 'activeCellId') {
let changedProp = changes[propName];
if (cellModel.id === changedProp.currentValue) {
this.toggle(true);
}
else {
this.toggle(false);
}
break;
}
}
}
} }

View File

@@ -8,7 +8,7 @@
<div class="notebook-code" style="flex: 0 0 auto;"> <div class="notebook-code" style="flex: 0 0 auto;">
<code-component [cellModel]="cellModel" [model]="model" [activeCellId]="activeCellId"></code-component> <code-component [cellModel]="cellModel" [model]="model" [activeCellId]="activeCellId"></code-component>
</div> </div>
<div #codeCellOutput class="notebook-output" style="flex: 0 0 auto;"> <div style="flex: 0 0 auto; width: 100%; height: 100%; display: block">
<output-area-component *ngIf="cellModel.outputs && cellModel.outputs.length > 0" [cellModel]="cellModel"> <output-area-component *ngIf="cellModel.outputs && cellModel.outputs.length > 0" [cellModel]="cellModel">
</output-area-component> </output-area-component>
</div> </div>

View File

@@ -2,15 +2,10 @@
* Copyright (c) Microsoft Corporation. All rights reserved. * Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import 'vs/css!./codeCell';
import { OnInit, Component, Input, Inject, forwardRef, ElementRef, ChangeDetectorRef, OnDestroy, ViewChild, SimpleChange, OnChanges } from '@angular/core'; import { OnInit, Component, Input, Inject, forwardRef, ElementRef, ChangeDetectorRef, OnDestroy, ViewChild, SimpleChange, OnChanges } from '@angular/core';
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service'; import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
import { CellView } from 'sql/parts/notebook/cellViews/interfaces'; 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 { ICellModel } from 'sql/parts/notebook/models/modelInterfaces';
import { NotebookModel } from 'sql/parts/notebook/models/notebookModel'; import { NotebookModel } from 'sql/parts/notebook/models/notebookModel';
@@ -21,9 +16,9 @@ export const CODE_SELECTOR: string = 'code-cell-component';
selector: CODE_SELECTOR, selector: CODE_SELECTOR,
templateUrl: decodeURI(require.toUrl('./codeCell.component.html')) templateUrl: decodeURI(require.toUrl('./codeCell.component.html'))
}) })
export class CodeCellComponent extends CellView implements OnInit, OnChanges { export class CodeCellComponent extends CellView implements OnInit, OnChanges {
@ViewChild('codeCellOutput', { read: ElementRef }) private outputPreview: ElementRef; @ViewChild('codeCellOutput', { read: ElementRef }) private outputPreview: ElementRef;
@Input() cellModel: ICellModel; @Input() cellModel: ICellModel;
@Input() set model(value: NotebookModel) { @Input() set model(value: NotebookModel) {
this._model = value; this._model = value;
@@ -37,14 +32,11 @@ export class CodeCellComponent extends CellView implements OnInit, OnChanges {
constructor( constructor(
@Inject(forwardRef(() => ChangeDetectorRef)) private _changeRef: ChangeDetectorRef, @Inject(forwardRef(() => ChangeDetectorRef)) private _changeRef: ChangeDetectorRef,
@Inject(IWorkbenchThemeService) private themeService: IWorkbenchThemeService
) { ) {
super(); super();
} }
ngOnInit() { ngOnInit() {
this._register(this.themeService.onDidColorThemeChange(this.updateTheme, this));
this.updateTheme(this.themeService.getColorTheme());
if (this.cellModel) { if (this.cellModel) {
this.cellModel.onOutputsChanged(() => { this.cellModel.onOutputsChanged(() => {
this._changeRef.detectChanges(); this._changeRef.detectChanges();
@@ -74,10 +66,4 @@ export class CodeCellComponent extends CellView implements OnInit, OnChanges {
public 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();
}
} }

View File

@@ -5,7 +5,7 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
--> -->
<div style="overflow: hidden; width: 100%; height: 100%; display: flex; flex-flow: column"> <div style="overflow: hidden; width: 100%; height: 100%; display: flex; flex-flow: column">
<div style="flex: 0 0 auto;"> <div #outputarea class="notebook-output" style="flex: 0 0 auto;">
<output-component *ngFor="let output of cellModel.outputs" [cellOutput]="output" [trustedMode] = "cellModel.trustedMode" > <output-component *ngFor="let output of cellModel.outputs" [cellOutput]="output" [trustedMode] = "cellModel.trustedMode" >
</output-component> </output-component>
</div> </div>

View File

@@ -3,9 +3,12 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import 'vs/css!./code'; import 'vs/css!./code';
import { OnInit, Component, Input, Inject, forwardRef, ChangeDetectorRef } from '@angular/core'; import 'vs/css!./outputArea';
import { OnInit, Component, Input, Inject, ElementRef, ViewChild, forwardRef, ChangeDetectorRef } from '@angular/core';
import { AngularDisposable } from 'sql/base/common/lifecycle'; import { AngularDisposable } from 'sql/base/common/lifecycle';
import { ICellModel } from 'sql/parts/notebook/models/modelInterfaces'; import { ICellModel } from 'sql/parts/notebook/models/modelInterfaces';
import * as themeColors from 'vs/workbench/common/theme';
import { IWorkbenchThemeService, IColorTheme } from 'vs/workbench/services/themes/common/workbenchThemeService';
export const OUTPUT_AREA_SELECTOR: string = 'output-area-component'; export const OUTPUT_AREA_SELECTOR: string = 'output-area-component';
@@ -14,20 +17,30 @@ export const OUTPUT_AREA_SELECTOR: string = 'output-area-component';
templateUrl: decodeURI(require.toUrl('./outputArea.component.html')) templateUrl: decodeURI(require.toUrl('./outputArea.component.html'))
}) })
export class OutputAreaComponent extends AngularDisposable implements OnInit { export class OutputAreaComponent extends AngularDisposable implements OnInit {
@ViewChild('outputarea', { read: ElementRef }) private outputArea: ElementRef;
@Input() cellModel: ICellModel; @Input() cellModel: ICellModel;
private readonly _minimumHeight = 30; private readonly _minimumHeight = 30;
constructor( constructor(
@Inject(forwardRef(() => ChangeDetectorRef)) private _changeRef: ChangeDetectorRef, @Inject(IWorkbenchThemeService) private themeService: IWorkbenchThemeService,
@Inject(forwardRef(() => ChangeDetectorRef)) private _changeRef: ChangeDetectorRef
) { ) {
super(); super();
} }
ngOnInit(): void {
ngOnInit() {
this._register(this.themeService.onDidColorThemeChange(this.updateTheme, this));
this.updateTheme(this.themeService.getColorTheme());
if (this.cellModel) { if (this.cellModel) {
this.cellModel.onOutputsChanged(() => { this.cellModel.onOutputsChanged(() => {
this._changeRef.detectChanges(); this._changeRef.detectChanges();
}); });
} }
} }
private updateTheme(theme: IColorTheme): void {
let outputElement = <HTMLElement>this.outputArea.nativeElement;
outputElement.style.borderTopColor = theme.getColor(themeColors.SIDE_BAR_BACKGROUND, true).toString();
}
} }

View File

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

View File

@@ -6,8 +6,9 @@
--> -->
<div style="overflow: hidden; width: 100%; height: 100%; display: flex; flex-flow: column"> <div style="overflow: hidden; width: 100%; height: 100%; display: flex; flex-flow: column">
<div class="notebook-text" style="flex: 0 0 auto;"> <div class="notebook-text" style="flex: 0 0 auto;">
<code-component *ngIf="isEditMode" [cellModel]="cellModel" (onContentChanged)="handleContentChanged()" [activeCellId]="activeCellId" [hideVerticalToolbar]=1></code-component> <code-component *ngIf="isEditMode" [cellModel]="cellModel" (onContentChanged)="handleContentChanged()" [model]="model" [activeCellId]="activeCellId" [hideVerticalToolbar]=true>
</code-component>
</div> </div>
<div #preview class="notebook-preview" style="flex: 0 0 auto;" (dblclick)="toggleEditMode()"> <div #preview style="flex: 0 0 auto;" (dblclick)="toggleEditMode()">
</div> </div>
</div> </div>

View File

@@ -15,6 +15,7 @@ import { ICommandService } from 'vs/platform/commands/common/commands';
import { ICellModel } from 'sql/parts/notebook/models/modelInterfaces'; import { ICellModel } from 'sql/parts/notebook/models/modelInterfaces';
import { ISanitizer, defaultSanitizer } from 'sql/parts/notebook/outputs/sanitizer'; import { ISanitizer, defaultSanitizer } from 'sql/parts/notebook/outputs/sanitizer';
import { localize } from 'vs/nls'; import { localize } from 'vs/nls';
import { NotebookModel } from 'sql/parts/notebook/models/notebookModel';
export const TEXT_SELECTOR: string = 'text-cell-component'; export const TEXT_SELECTOR: string = 'text-cell-component';
@@ -25,12 +26,20 @@ export const TEXT_SELECTOR: string = 'text-cell-component';
export class TextCellComponent extends CellView implements OnInit, OnChanges { export class TextCellComponent extends CellView implements OnInit, OnChanges {
@ViewChild('preview', { read: ElementRef }) private output: ElementRef; @ViewChild('preview', { read: ElementRef }) private output: ElementRef;
@Input() cellModel: ICellModel; @Input() cellModel: ICellModel;
@Input() set model(value: NotebookModel) {
this._model = value;
}
@Input() set activeCellId(value: string) { @Input() set activeCellId(value: string) {
this._activeCellId = value; this._activeCellId = value;
} }
private _content: string; private _content: string;
private isEditMode: boolean; private isEditMode: boolean;
private _sanitizer: ISanitizer; private _sanitizer: ISanitizer;
private _previewCssApplied: boolean = false;
private _model: NotebookModel;
private _activeCellId: string; private _activeCellId: string;
constructor( constructor(
@@ -43,17 +52,6 @@ export class TextCellComponent extends CellView implements OnInit, OnChanges {
this.isEditMode = false; this.isEditMode = false;
} }
ngOnChanges(changes: { [propKey: string]: SimpleChange }) {
this.updatePreview();
for (let propName in changes) {
if (propName === 'activeCellId') {
let changedProp = changes[propName];
this._activeCellId = changedProp.currentValue;
break;
}
}
}
//Gets sanitizer from ISanitizer interface //Gets sanitizer from ISanitizer interface
private get sanitizer(): ISanitizer { private get sanitizer(): ISanitizer {
if (this._sanitizer) { if (this._sanitizer) {
@@ -62,17 +60,41 @@ export class TextCellComponent extends CellView implements OnInit, OnChanges {
return this._sanitizer = defaultSanitizer; return this._sanitizer = defaultSanitizer;
} }
get model(): NotebookModel {
return this._model;
}
get activeCellId(): string { get activeCellId(): string {
return this._activeCellId; return this._activeCellId;
} }
ngOnInit() {
this.updatePreview();
this._register(this.themeService.onDidColorThemeChange(this.updateTheme, this));
this.updateTheme(this.themeService.getColorTheme());
this.cellModel.onOutputsChanged(e => {
this.updatePreview();
});
}
ngOnChanges(changes: { [propKey: string]: SimpleChange }) {
for (let propName in changes) {
if (propName === 'activeCellId') {
let changedProp = changes[propName];
this._activeCellId = changedProp.currentValue;
this.toggleEditMode(false);
break;
}
}
}
/** /**
* Updates the preview of markdown component with latest changes * 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' * 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 * Sanitizes the data to be shown in markdown cell
*/ */
private updatePreview() { private updatePreview() {
if (this._content !== this.cellModel.source) { if (this._content !== this.cellModel.source || this.cellModel.source.length === 0) {
if (!this.cellModel.source && !this.isEditMode) { if (!this.cellModel.source && !this.isEditMode) {
(<HTMLElement>this.output.nativeElement).innerHTML = localize('doubleClickEdit', 'Double-click to edit'); (<HTMLElement>this.output.nativeElement).innerHTML = localize('doubleClickEdit', 'Double-click to edit');
} else { } else {
@@ -94,14 +116,6 @@ export class TextCellComponent extends CellView implements OnInit, OnChanges {
return 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 // Todo: implement layout
public layout() { public layout() {
@@ -113,12 +127,29 @@ export class TextCellComponent extends CellView implements OnInit, OnChanges {
} }
public handleContentChanged(): void { public handleContentChanged(): void {
if (!this._previewCssApplied) {
this.updatePreviewCssClass();
}
this.updatePreview(); this.updatePreview();
} }
public toggleEditMode(): void { public toggleEditMode(editMode?: boolean): void {
this.isEditMode = !this.isEditMode; this.isEditMode = editMode !== undefined? editMode : !this.isEditMode;
this.updatePreviewCssClass();
this.updatePreview(); this.updatePreview();
this._changeRef.detectChanges(); this._changeRef.detectChanges();
} }
// Updates the css class to preview 'div' based on edit mode
private updatePreviewCssClass() {
let outputElement = <HTMLElement>this.output.nativeElement;
if (this.isEditMode && this.cellModel.source) {
outputElement.className = 'notebook-preview';
this._previewCssApplied = true;
}
else {
outputElement.className = '';
this._previewCssApplied = false;
}
}
} }

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>save_inverse</title><path class="cls-1" d="M14,1a1,1,0,0,1,.39.08,1,1,0,0,1,.53.53A1,1,0,0,1,15,2V15H2.79L1,13.2V2a1,1,0,0,1,.08-.39,1,1,0,0,1,.53-.53A1,1,0,0,1,2,1Zm0,1H13V8H3V2H2V12.79L3.2,14H4V10h7v4h3ZM4,7h8V2H4Zm6,4H5v3H6V12H7v2h3Z"/></svg>

After

Width:  |  Height:  |  Size: 391 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"><title>save</title><path d="M14,1a1,1,0,0,1,.39.08,1,1,0,0,1,.53.53A1,1,0,0,1,15,2V15H2.79L1,13.2V2a1,1,0,0,1,.08-.39,1,1,0,0,1,.53-.53A1,1,0,0,1,2,1Zm0,1H13V8H3V2H2V12.79L3.2,14H4V10h7v4h3ZM4,7h8V2H4Zm6,4H5v3H6V12H7v2h3Z"/></svg>

After

Width:  |  Height:  |  Size: 323 B

View File

@@ -14,6 +14,7 @@ import { ICellModelOptions, IModelFactory, FutureInternal } from './modelInterfa
import * as notebookUtils from '../notebookUtils'; import * as notebookUtils from '../notebookUtils';
import { CellTypes, CellType, NotebookChangeType } from 'sql/parts/notebook/models/contracts'; import { CellTypes, CellType, NotebookChangeType } from 'sql/parts/notebook/models/contracts';
import { ICellModel } from 'sql/parts/notebook/models/modelInterfaces'; import { ICellModel } from 'sql/parts/notebook/models/modelInterfaces';
import { NotebookModel } from 'sql/parts/notebook/models/notebookModel';
let modelId = 0; let modelId = 0;
@@ -34,7 +35,7 @@ export class CellModel implements ICellModel {
private _active: boolean; private _active: boolean;
private _cellUri: URI; private _cellUri: URI;
constructor(private factory: IModelFactory, cellData?: nb.ICell, private _options?: ICellModelOptions) { constructor(private factory: IModelFactory, cellData?: nb.ICellContents, private _options?: ICellModelOptions) {
this.id = `${modelId++}`; this.id = `${modelId++}`;
CellModel.CreateLanguageMappings(); CellModel.CreateLanguageMappings();
// Do nothing for now // Do nothing for now
@@ -222,18 +223,48 @@ export class CellModel implements ICellModel {
if (output) { if (output) {
// deletes transient node in the serialized JSON // deletes transient node in the serialized JSON
delete output['transient']; delete output['transient'];
this._outputs.push(output); this._outputs.push(this.rewriteOutputUrls(output));
this.fireOutputsChanged(); this.fireOutputsChanged();
} }
} }
private rewriteOutputUrls(output: nb.ICellOutput): nb.ICellOutput {
// Only rewrite if this is coming back during execution, not when loading from disk.
// A good approximation is that the model has a future (needed for execution)
if (this.future) {
try {
let result = output as nb.IDisplayResult;
if (result && result.data && result.data['text/html']) {
let nbm = (this as CellModel).options.notebook as NotebookModel;
if (nbm.hadoopConnection) {
let host = nbm.hadoopConnection.host;
let html = result.data['text/html'];
html = html.replace(/(https?:\/\/mssql-master.*\/proxy)(.*)/g, function (a, b, c) {
let ret = '';
if (b !== '') {
ret = 'https://' + host + ':30443/gateway/default/yarn/proxy';
}
if (c !== '') {
ret = ret + c;
}
return ret;
});
(<nb.IDisplayResult>output).data['text/html'] = html;
}
}
}
catch (e) {}
}
return output;
}
private getDisplayId(msg: nb.IIOPubMessage): string | undefined { private getDisplayId(msg: nb.IIOPubMessage): string | undefined {
let transient = (msg.content.transient || {}); let transient = (msg.content.transient || {});
return transient['display_id'] as string; return transient['display_id'] as string;
} }
public toJSON(): nb.ICell { public toJSON(): nb.ICellContents {
let cellJson: Partial<nb.ICell> = { let cellJson: Partial<nb.ICellContents> = {
cell_type: this._cellType, cell_type: this._cellType,
source: this._source, source: this._source,
metadata: { metadata: {
@@ -244,10 +275,10 @@ export class CellModel implements ICellModel {
cellJson.outputs = this._outputs; cellJson.outputs = this._outputs;
cellJson.execution_count = 1; // TODO: keep track of actual execution count cellJson.execution_count = 1; // TODO: keep track of actual execution count
} }
return cellJson as nb.ICell; return cellJson as nb.ICellContents;
} }
public fromJSON(cell: nb.ICell): void { public fromJSON(cell: nb.ICellContents): void {
if (!cell) { if (!cell) {
return; return;
} }

View File

@@ -13,7 +13,7 @@ import { ClientSession } from './clientSession';
export class ModelFactory implements IModelFactory { export class ModelFactory implements IModelFactory {
public createCell(cell: nb.ICell, options: ICellModelOptions): ICellModel { public createCell(cell: nb.ICellContents, options: ICellModelOptions): ICellModel {
return new CellModel(this, cell, options); return new CellModel(this, cell, options);
} }

View File

@@ -19,6 +19,7 @@ import { INotebookManager } from 'sql/services/notebook/notebookService';
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces'; import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
import { NotebookConnection } from 'sql/parts/notebook/models/notebookConnection'; import { NotebookConnection } from 'sql/parts/notebook/models/notebookConnection';
import { IConnectionManagementService } from 'sql/parts/connection/common/connectionManagement'; import { IConnectionManagementService } from 'sql/parts/connection/common/connectionManagement';
import { ISingleNotebookEditOperation } from 'sql/workbench/api/common/sqlExtHostTypes';
export interface IClientSessionOptions { export interface IClientSessionOptions {
notebookUri: URI; notebookUri: URI;
@@ -328,6 +329,14 @@ export interface INotebookModel {
* Notifies the notebook of a change in the cell * Notifies the notebook of a change in the cell
*/ */
onCellChange(cell: ICellModel, change: NotebookChangeType): void; onCellChange(cell: ICellModel, change: NotebookChangeType): void;
/**
* Push edit operations, basically editing the model. This is the preferred way of
* editing the model. Long-term, this will ensure edit operations can be added to the undo stack
* @param edits The edit operations to perform
*/
pushEditOperations(edits: ISingleNotebookEditOperation[]): void;
} }
export interface ICellModelOptions { export interface ICellModelOptions {
@@ -348,7 +357,7 @@ export interface ICellModel {
readonly onOutputsChanged: Event<ReadonlyArray<nb.ICellOutput>>; readonly onOutputsChanged: Event<ReadonlyArray<nb.ICellOutput>>;
setFuture(future: FutureInternal): void; setFuture(future: FutureInternal): void;
equals(cellModel: ICellModel): boolean; equals(cellModel: ICellModel): boolean;
toJSON(): nb.ICell; toJSON(): nb.ICellContents;
} }
export interface FutureInternal extends nb.IFuture { export interface FutureInternal extends nb.IFuture {
@@ -357,7 +366,7 @@ export interface FutureInternal extends nb.IFuture {
export interface IModelFactory { export interface IModelFactory {
createCell(cell: nb.ICell, options: ICellModelOptions): ICellModel; createCell(cell: nb.ICellContents, options: ICellModelOptions): ICellModel;
createClientSession(options: IClientSessionOptions): IClientSession; createClientSession(options: IClientSessionOptions): IClientSession;
} }

View File

@@ -22,6 +22,7 @@ import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
import { NotebookConnection } from 'sql/parts/notebook/models/notebookConnection'; import { NotebookConnection } from 'sql/parts/notebook/models/notebookConnection';
import { INotification, Severity } from 'vs/platform/notification/common/notification'; import { INotification, Severity } from 'vs/platform/notification/common/notification';
import { Schemas } from 'vs/base/common/network'; import { Schemas } from 'vs/base/common/network';
import { ISingleNotebookEditOperation } from 'sql/workbench/api/common/sqlExtHostTypes';
/* /*
* Used to control whether a message in a dialog/wizard is displayed as an error, * Used to control whether a message in a dialog/wizard is displayed as an error,
@@ -237,7 +238,7 @@ export class NotebookModel extends Disposable implements INotebookModel {
} }
private createCell(cellType: CellType): ICellModel { private createCell(cellType: CellType): ICellModel {
let singleCell: nb.ICell = { let singleCell: nb.ICellContents = {
cell_type: cellType, cell_type: cellType,
source: '', source: '',
metadata: {}, metadata: {},
@@ -263,6 +264,25 @@ export class NotebookModel extends Disposable implements INotebookModel {
} }
} }
pushEditOperations(edits: ISingleNotebookEditOperation[]): void {
if (this.inErrorState || !this._cells) {
return;
}
for (let edit of edits) {
let newCells: ICellModel[] = [];
if (edit.cell) {
// TODO: should we validate and complete required missing parameters?
let contents: nb.ICellContents = edit.cell as nb.ICellContents;
newCells.push(this.notebookOptions.factory.createCell(contents, { notebook: this, isTrusted: this._trustedMode }));
}
this._cells.splice(edit.range.start, edit.range.end - edit.range.start, ...newCells);
this._contentChangedEmitter.fire({
changeType: NotebookChangeType.CellsAdded
});
}
}
public get activeCell(): ICellModel { public get activeCell(): ICellModel {
return this._activeCell; return this._activeCell;
} }
@@ -281,9 +301,14 @@ export class NotebookModel extends Disposable implements INotebookModel {
notebookManager: this.notebookManager, notebookManager: this.notebookManager,
notificationService: this.notebookOptions.notificationService notificationService: this.notebookOptions.notificationService
}); });
let id: string = this.connectionProfile ? this.connectionProfile.id : undefined; let profile = this.connectionProfile as IConnectionProfile;
if (this.isValidKnoxConnection(profile)) {
this._hadoopConnection = new NotebookConnection(this.connectionProfile);
} else {
this._hadoopConnection = undefined;
}
this._hadoopConnection = this.connectionProfile ? new NotebookConnection(this.connectionProfile) : undefined;
this._clientSession.initialize(this._hadoopConnection); this._clientSession.initialize(this._hadoopConnection);
this._sessionLoadFinished = this._clientSession.ready.then(async () => { this._sessionLoadFinished = this._clientSession.ready.then(async () => {
if (this._clientSession.isInErrorState) { if (this._clientSession.isInErrorState) {
@@ -389,7 +414,7 @@ export class NotebookModel extends Disposable implements INotebookModel {
// Get default language if saved in notebook file // Get default language if saved in notebook file
// Otherwise, default to python // Otherwise, default to python
private getDefaultLanguageInfo(notebook: nb.INotebook): nb.ILanguageInfo { private getDefaultLanguageInfo(notebook: nb.INotebookContents): nb.ILanguageInfo {
return notebook!.metadata!.language_info || { return notebook!.metadata!.language_info || {
name: 'python', name: 'python',
version: '', version: '',
@@ -398,7 +423,7 @@ export class NotebookModel extends Disposable implements INotebookModel {
} }
// Get default kernel info if saved in notebook file // Get default kernel info if saved in notebook file
private getSavedKernelInfo(notebook: nb.INotebook): nb.IKernelInfo { private getSavedKernelInfo(notebook: nb.INotebookContents): nb.IKernelInfo {
return notebook!.metadata!.kernelspec; return notebook!.metadata!.kernelspec;
} }
@@ -490,8 +515,8 @@ export class NotebookModel extends Disposable implements INotebookModel {
/** /**
* Serialize the model to JSON. * Serialize the model to JSON.
*/ */
toJSON(): nb.INotebook { toJSON(): nb.INotebookContents {
let cells: nb.ICell[] = this.cells.map(c => c.toJSON()); let cells: nb.ICellContents[] = this.cells.map(c => c.toJSON());
let metadata = Object.create(null) as nb.INotebookMetadata; let metadata = Object.create(null) as nb.INotebookMetadata;
// TODO update language and kernel when these change // TODO update language and kernel when these change
metadata.kernelspec = this._savedKernelInfo; metadata.kernelspec = this._savedKernelInfo;

View File

@@ -140,9 +140,16 @@ export class SparkMagicContexts {
* @param savedKernelInfo kernel info loaded from * @param savedKernelInfo kernel info loaded from
*/ */
public static getDefaultKernel(specs: nb.IAllKernels, connectionInfo: IConnectionProfile, savedKernelInfo: nb.IKernelInfo, notificationService: INotificationService): nb.IKernelSpec { 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 foundSavedKernelInSpecs;
let defaultKernel;
if (specs) {
defaultKernel = specs.kernels.find((kernel) => kernel.name === specs.defaultKernel);
if (savedKernelInfo) {
foundSavedKernelInSpecs = specs.kernels.find((kernel) => kernel.name === savedKernelInfo.name);
}
}
let profile = connectionInfo as IConnectionProfile; let profile = connectionInfo as IConnectionProfile;
if (specs && connectionInfo && profile.providerName === notebookConstants.hadoopKnoxProviderName) { if (foundSavedKernelInSpecs && specs && connectionInfo && profile.providerName === notebookConstants.hadoopKnoxProviderName) {
// set default kernel to default spark kernel if profile exists // set default kernel to default spark kernel if profile exists
// otherwise, set default to kernel info loaded from existing file // otherwise, set default to kernel info loaded from existing file
defaultKernel = !savedKernelInfo ? specs.kernels.find((spec) => spec.name === notebookConstants.defaultSparkKernel) : savedKernelInfo; defaultKernel = !savedKernelInfo ? specs.kernels.find((spec) => spec.name === notebookConstants.defaultSparkKernel) : savedKernelInfo;

View File

@@ -7,12 +7,12 @@
<div style="overflow: hidden; width: 100%; height: 100%; display: flex; flex-flow: column"> <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 #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>
<div class="scrollable" style="flex: 1 1 auto; position: relative"> <div class="scrollable" style="flex: 1 1 auto; position: relative" (click)="unselectActiveCell()">
<loading-spinner [loading]="isLoading"></loading-spinner> <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)"> <div class="notebook-cell" *ngFor="let cell of cells" (click)="selectCell(cell, $event)" [class.active]="cell.active">
<code-cell-component *ngIf="cell.cellType === 'code'" [cellModel]="cell" [model]="model" [activeCellId]="activeCellId"> <code-cell-component *ngIf="cell.cellType === 'code'" [cellModel]="cell" [model]="model" [activeCellId]="activeCellId">
</code-cell-component> </code-cell-component>
<text-cell-component *ngIf="cell.cellType === 'markdown'" [cellModel]="cell" [activeCellId]="activeCellId"> <text-cell-component *ngIf="cell.cellType === 'markdown'" [cellModel]="cell" [model]="model" [activeCellId]="activeCellId">
</text-cell-component> </text-cell-component>
</div> </div>
</div> </div>

View File

@@ -7,9 +7,8 @@ import './notebookStyles';
import { nb } from 'sqlops'; import { nb } from 'sqlops';
import { OnInit, Component, Inject, forwardRef, ElementRef, ChangeDetectorRef, ViewChild } from '@angular/core'; import { OnInit, Component, Inject, forwardRef, ElementRef, ChangeDetectorRef, ViewChild, OnDestroy } from '@angular/core';
import URI from 'vs/base/common/uri';
import { IColorTheme, IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { IColorTheme, IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
import * as themeColors from 'vs/workbench/common/theme'; import * as themeColors from 'vs/workbench/common/theme';
import { INotificationService, INotification } from 'vs/platform/notification/common/notification'; import { INotificationService, INotification } from 'vs/platform/notification/common/notification';
@@ -18,12 +17,12 @@ import { localize } from 'vs/nls';
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service'; import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
import { AngularDisposable } from 'sql/base/common/lifecycle'; import { AngularDisposable } from 'sql/base/common/lifecycle';
import { CellTypes, CellType, NotebookChangeType } from 'sql/parts/notebook/models/contracts'; import { CellTypes, CellType } from 'sql/parts/notebook/models/contracts';
import { ICellModel, IModelFactory } from 'sql/parts/notebook/models/modelInterfaces'; import { ICellModel, IModelFactory, notebookConstants } from 'sql/parts/notebook/models/modelInterfaces';
import { IConnectionManagementService, IConnectionDialogService } from 'sql/parts/connection/common/connectionManagement'; import { IConnectionManagementService, IConnectionDialogService } from 'sql/parts/connection/common/connectionManagement';
import { INotebookService, INotebookParams, INotebookManager } from 'sql/services/notebook/notebookService'; import { INotebookService, INotebookParams, INotebookManager, INotebookEditor } from 'sql/services/notebook/notebookService';
import { IBootstrapParams } from 'sql/services/bootstrap/bootstrapService'; import { IBootstrapParams } from 'sql/services/bootstrap/bootstrapService';
import { NotebookModel, ErrorInfo, MessageLevel, NotebookContentChange } from 'sql/parts/notebook/models/notebookModel'; import { NotebookModel, NotebookContentChange } from 'sql/parts/notebook/models/notebookModel';
import { ModelFactory } from 'sql/parts/notebook/models/modelFactory'; import { ModelFactory } from 'sql/parts/notebook/models/modelFactory';
import * as notebookUtils from './notebookUtils'; import * as notebookUtils from './notebookUtils';
import { Deferred } from 'sql/base/common/promise'; import { Deferred } from 'sql/base/common/promise';
@@ -31,8 +30,17 @@ import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
import { Taskbar } from 'sql/base/browser/ui/taskbar/taskbar'; import { Taskbar } from 'sql/base/browser/ui/taskbar/taskbar';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { KernelsDropdown, AttachToDropdown, AddCellAction, TrustedAction } from 'sql/parts/notebook/notebookActions'; import { KernelsDropdown, AttachToDropdown, AddCellAction, TrustedAction, SaveNotebookAction } from 'sql/parts/notebook/notebookActions';
import { attachSelectBoxStyler } from 'vs/platform/theme/common/styler'; import { attachSelectBoxStyler } from 'vs/platform/theme/common/styler';
import { MenuId, IMenuService, MenuItemAction } from 'vs/platform/actions/common/actions';
import { IAction, Action, IActionItem } from 'vs/base/common/actions';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { fillInActions, LabeledMenuItemActionItem } from 'vs/platform/actions/browser/menuItemActionItem';
import { IObjectExplorerService } from 'sql/parts/objectExplorer/common/objectExplorerService';
import * as TaskUtilities from 'sql/workbench/common/taskUtilities';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { ISingleNotebookEditOperation } from 'sql/workbench/api/common/sqlExtHostTypes';
export const NOTEBOOK_SELECTOR: string = 'notebook-component'; export const NOTEBOOK_SELECTOR: string = 'notebook-component';
@@ -41,7 +49,7 @@ export const NOTEBOOK_SELECTOR: string = 'notebook-component';
selector: NOTEBOOK_SELECTOR, selector: NOTEBOOK_SELECTOR,
templateUrl: decodeURI(require.toUrl('./notebook.component.html')) templateUrl: decodeURI(require.toUrl('./notebook.component.html'))
}) })
export class NotebookComponent extends AngularDisposable implements OnInit { export class NotebookComponent extends AngularDisposable implements OnInit, OnDestroy, INotebookEditor {
@ViewChild('toolbar', { read: ElementRef }) private toolbar: ElementRef; @ViewChild('toolbar', { read: ElementRef }) private toolbar: ElementRef;
private _model: NotebookModel; private _model: NotebookModel;
private _isInErrorState: boolean = false; private _isInErrorState: boolean = false;
@@ -62,26 +70,56 @@ export class NotebookComponent extends AngularDisposable implements OnInit {
@Inject(forwardRef(() => ChangeDetectorRef)) private _changeRef: ChangeDetectorRef, @Inject(forwardRef(() => ChangeDetectorRef)) private _changeRef: ChangeDetectorRef,
@Inject(IWorkbenchThemeService) private themeService: IWorkbenchThemeService, @Inject(IWorkbenchThemeService) private themeService: IWorkbenchThemeService,
@Inject(IConnectionManagementService) private connectionManagementService: IConnectionManagementService, @Inject(IConnectionManagementService) private connectionManagementService: IConnectionManagementService,
@Inject(IObjectExplorerService) private objectExplorerService: IObjectExplorerService,
@Inject(IEditorService) private editorService: IEditorService,
@Inject(INotificationService) private notificationService: INotificationService, @Inject(INotificationService) private notificationService: INotificationService,
@Inject(INotebookService) private notebookService: INotebookService, @Inject(INotebookService) private notebookService: INotebookService,
@Inject(IBootstrapParams) private notebookParams: INotebookParams, @Inject(IBootstrapParams) private _notebookParams: INotebookParams,
@Inject(IInstantiationService) private instantiationService: IInstantiationService, @Inject(IInstantiationService) private instantiationService: IInstantiationService,
@Inject(IContextMenuService) private contextMenuService: IContextMenuService, @Inject(IContextMenuService) private contextMenuService: IContextMenuService,
@Inject(IContextViewService) private contextViewService: IContextViewService, @Inject(IContextViewService) private contextViewService: IContextViewService,
@Inject(IConnectionDialogService) private connectionDialogService: IConnectionDialogService @Inject(IConnectionDialogService) private connectionDialogService: IConnectionDialogService,
@Inject(IContextKeyService) private contextKeyService: IContextKeyService,
@Inject(IMenuService) private menuService: IMenuService,
@Inject(IKeybindingService) private keybindingService: IKeybindingService
) { ) {
super(); super();
this.profile = this.notebookParams!.profile; this.updateProfile();
this.isLoading = true; this.isLoading = true;
} }
private updateProfile(): void {
this.profile = this.notebookParams!.profile;
if (!this.profile) {
// use global connection if possible
let profile = TaskUtilities.getCurrentGlobalConnection(this.objectExplorerService, this.connectionManagementService, this.editorService);
// TODO use generic method to match kernel with valid connection that's compatible. For now, we only have 1
if (profile && profile.providerName === notebookConstants.hadoopKnoxProviderName) {
this.profile = profile;
} else {
// if not, try 1st active connection that matches our filter
let profiles = this.connectionManagementService.getActiveConnections([notebookConstants.hadoopKnoxProviderName]);
if (profiles && profiles.length > 0) {
this.profile = profiles[0];
}
}
}
}
ngOnInit() { ngOnInit() {
this._register(this.themeService.onDidColorThemeChange(this.updateTheme, this)); this._register(this.themeService.onDidColorThemeChange(this.updateTheme, this));
this.updateTheme(this.themeService.getColorTheme()); this.updateTheme(this.themeService.getColorTheme());
this.notebookService.addNotebookEditor(this);
this.initActionBar(); this.initActionBar();
this.doLoad(); this.doLoad();
} }
ngOnDestroy() {
if (this.notebookService) {
this.notebookService.removeNotebookEditor(this);
}
}
public get model(): NotebookModel { public get model(): NotebookModel {
return this._model; return this._model;
} }
@@ -103,7 +141,10 @@ export class NotebookComponent extends AngularDisposable implements OnInit {
toolbarEl.style.borderBottomColor = theme.getColor(themeColors.SIDE_BAR_BACKGROUND, true).toString(); toolbarEl.style.borderBottomColor = theme.getColor(themeColors.SIDE_BAR_BACKGROUND, true).toString();
} }
public selectCell(cell: ICellModel) { public selectCell(cell: ICellModel, event?: Event) {
if (event) {
event.stopPropagation();
}
if (cell !== this._activeCell) { if (cell !== this._activeCell) {
if (this._activeCell) { if (this._activeCell) {
this._activeCell.active = false; this._activeCell.active = false;
@@ -116,6 +157,16 @@ export class NotebookComponent extends AngularDisposable implements OnInit {
} }
} }
public unselectActiveCell() {
if (this._activeCell) {
this._activeCell.active = false;
}
this._activeCell = null;
this._model.activeCell = null;
this._activeCellId = null;
this._changeRef.detectChanges();
}
// Add cell based on cell type // Add cell based on cell type
public addCell(cellType: CellType) public addCell(cellType: CellType)
{ {
@@ -171,16 +222,16 @@ export class NotebookComponent extends AngularDisposable implements OnInit {
} }
private async loadModel(): Promise<void> { private async loadModel(): Promise<void> {
this.notebookManager = await this.notebookService.getOrCreateNotebookManager(this.notebookParams.providerId, this.notebookParams.notebookUri); this.notebookManager = await this.notebookService.getOrCreateNotebookManager(this._notebookParams.providerId, this._notebookParams.notebookUri);
let model = new NotebookModel({ let model = new NotebookModel({
factory: this.modelFactory, factory: this.modelFactory,
notebookUri: this.notebookParams.notebookUri, notebookUri: this._notebookParams.notebookUri,
connectionService: this.connectionManagementService, connectionService: this.connectionManagementService,
notificationService: this.notificationService, notificationService: this.notificationService,
notebookManager: this.notebookManager notebookManager: this.notebookManager
}, false, this.profile); }, false, this.profile);
model.onError((errInfo: INotification) => this.handleModelError(errInfo)); model.onError((errInfo: INotification) => this.handleModelError(errInfo));
await model.requestModelLoad(this.notebookParams.isTrusted); await model.requestModelLoad(this._notebookParams.isTrusted);
model.contentChanged((change) => this.handleContentChanged(change)); model.contentChanged((change) => this.handleContentChanged(change));
this._model = model; this._model = model;
this.updateToolbarComponents(this._model.trustedMode); this.updateToolbarComponents(this._model.trustedMode);
@@ -201,10 +252,10 @@ export class NotebookComponent extends AngularDisposable implements OnInit {
} }
private get modelFactory(): IModelFactory { private get modelFactory(): IModelFactory {
if (!this.notebookParams.modelFactory) { if (!this._notebookParams.modelFactory) {
this.notebookParams.modelFactory = new ModelFactory(); this._notebookParams.modelFactory = new ModelFactory();
} }
return this.notebookParams.modelFactory; return this._notebookParams.modelFactory;
} }
private handleModelError(notification: INotification): void { private handleModelError(notification: INotification): void {
this.notificationService.notify(notification); this.notificationService.notify(notification);
@@ -239,9 +290,6 @@ export class NotebookComponent extends AngularDisposable implements OnInit {
attachTodropdwon.render(attachToContainer); attachTodropdwon.render(attachToContainer);
attachSelectBoxStyler(attachTodropdwon, this.themeService); 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'); let addCodeCellButton = new AddCellAction('notebook.AddCodeCell', localize('code', 'Code'), 'notebook-button icon-add');
addCodeCellButton.cellType = CellTypes.Code; addCodeCellButton.cellType = CellTypes.Code;
@@ -252,21 +300,44 @@ export class NotebookComponent extends AngularDisposable implements OnInit {
this._trustedAction = this.instantiationService.createInstance(TrustedAction, 'notebook.Trusted'); this._trustedAction = this.instantiationService.createInstance(TrustedAction, 'notebook.Trusted');
this._trustedAction.enabled = false; this._trustedAction.enabled = false;
let saveNotebookButton = this.instantiationService.createInstance(SaveNotebookAction, 'notebook.SaveNotebook', localize('save', 'Save'), 'notebook-button icon-save');
// Get all of the menu contributions that use the ID 'notebook/toolbar'.
// Then, find all groups (currently we don't leverage the contributed
// groups functionality for the notebook toolbar), and fill in the 'primary'
// array with items that don't list a group. Finally, add any actions from
// the primary array to the end of the toolbar.
const notebookBarMenu = this.menuService.createMenu(MenuId.NotebookToolbar, this.contextKeyService);
let groups = notebookBarMenu.getActions({ arg: null, shouldForwardArgs: true });
let primary: IAction[] = [];
let secondary: IAction[] = [];
fillInActions(groups, {primary, secondary}, false, (group: string) => group === undefined);
let taskbar = <HTMLElement>this.toolbar.nativeElement; let taskbar = <HTMLElement>this.toolbar.nativeElement;
this._actionBar = new Taskbar(taskbar, this.contextMenuService); this._actionBar = new Taskbar(taskbar, this.contextMenuService, { actionItemProvider: action => this.actionItemProvider(action as Action)});
this._actionBar.context = this; this._actionBar.context = this;
this._actionBar.setContent([ this._actionBar.setContent([
{ element: kernelContainer }, { element: kernelContainer },
{ element: attachToContainer }, { element: attachToContainer },
{ action: addCodeCellButton}, { action: addCodeCellButton },
{ action: addTextCellButton}, { action: addTextCellButton },
{ action: this._trustedAction} { action: saveNotebookButton },
{ action: this._trustedAction }
]); ]);
// Primary actions are categorized as those that are added to the 'horizontal' group.
// For the vertical toolbar, we can do the same thing and instead use the 'vertical' group.
for (let action of primary) {
this._actionBar.addAction(action);
}
} }
public async save(): Promise<boolean> { public async save(): Promise<boolean> {
try { try {
let saved = await this._model.saveModel(); let saved = await this._model.saveModel();
if (saved) {
this.setDirty(false);
}
return saved; return saved;
} catch (err) { } catch (err) {
this.notificationService.error(localize('saveFailed', 'Failed to save notebook: {0}', notebookUtils.getErrorMessage(err))); this.notificationService.error(localize('saveFailed', 'Failed to save notebook: {0}', notebookUtils.getErrorMessage(err)));
@@ -275,11 +346,46 @@ export class NotebookComponent extends AngularDisposable implements OnInit {
} }
private setDirty(isDirty: boolean): void { private setDirty(isDirty: boolean): void {
// TODO reenable handling of isDirty if(this._notebookParams.input){
// if (this.editor) { this._notebookParams.input.setDirty(isDirty);
// this.editor.isDirty = isDirty; }
// }
} }
private actionItemProvider(action: Action): IActionItem {
// Check extensions to create ActionItem; otherwise, return undefined
// This is similar behavior that exists in MenuItemActionItem
if (action instanceof MenuItemAction) {
return new LabeledMenuItemActionItem(action, this.keybindingService, this.notificationService, this.contextMenuService, 'notebook-button');
}
return undefined;
}
public get notebookParams(): INotebookParams {
return this._notebookParams;
}
public get id(): string {
return this._notebookParams.notebookUri.toString();
}
isActive(): boolean {
return this.editorService.activeEditor === this.notebookParams.input;
}
isVisible(): boolean {
let notebookEditor = this.notebookParams.input;
return this.editorService.visibleEditors.some(e => e === notebookEditor);
}
isDirty(): boolean {
return this.notebookParams.input.isDirty();
}
executeEdits(edits: ISingleNotebookEditOperation[]): boolean {
if (!edits || edits.length === 0) {
return false;
}
this._model.pushEditOperations(edits);
return true;
}
} }

View File

@@ -4,50 +4,10 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { Registry } from 'vs/platform/registry/common/platform'; import { Registry } from 'vs/platform/registry/common/platform';
import { EditorDescriptor, IEditorRegistry, Extensions as EditorExtensions } from 'vs/workbench/browser/editor'; 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 { 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 { NotebookInput } from 'sql/parts/notebook/notebookInput';
import { NotebookEditor } from 'sql/parts/notebook/notebookEditor'; 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 // Model View editor registration
const viewModelEditorDescriptor = new EditorDescriptor( const viewModelEditorDescriptor = new EditorDescriptor(
@@ -58,31 +18,3 @@ const viewModelEditorDescriptor = new EditorDescriptor(
Registry.as<IEditorRegistry>(EditorExtensions.Editors) Registry.as<IEditorRegistry>(EditorExtensions.Editors)
.registerEditor(viewModelEditorDescriptor, [new SyncDescriptor(NotebookInput)]); .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

@@ -31,9 +31,13 @@
text-align: center; text-align: center;
cursor: pointer; cursor: pointer;
padding-left: 15px; padding-left: 15px;
background-size: 11px; background-size: 13px;
margin-right: 0.3em; margin-right: 0.3em;
font-size: 11px; font-size: 13px;
}
.notebookEditor .monaco-select-box {
min-width: 150px;
} }
.notebookEditor .notebook-button.icon-add{ .notebookEditor .notebook-button.icon-add{
@@ -61,4 +65,13 @@
.vs-dark .notebookEditor .notebook-button.icon-notTrusted, .vs-dark .notebookEditor .notebook-button.icon-notTrusted,
.hc-black .notebookEditor .notebook-button.icon-notTrusted{ .hc-black .notebookEditor .notebook-button.icon-notTrusted{
background-image: url("./media/dark/nottrusted_inverse.svg"); background-image: url("./media/dark/nottrusted_inverse.svg");
}
.notebookEditor .notebook-button.icon-save{
background-image: url("./media/light/save.svg");
}
.vs-dark .notebookEditor .notebook-button.icon-save,
.hc-black .notebookEditor .notebook-button.icon-save{
background-image: url("./media/dark/save_inverse.svg");
} }

View File

@@ -18,6 +18,7 @@ import { NotebookComponent } from 'sql/parts/notebook/notebook.component';
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces'; import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
import { IConnectionManagementService, IConnectionDialogService } from 'sql/parts/connection/common/connectionManagement'; import { IConnectionManagementService, IConnectionDialogService } from 'sql/parts/connection/common/connectionManagement';
import { getErrorMessage } from 'sql/parts/notebook/notebookUtils'; import { getErrorMessage } from 'sql/parts/notebook/notebookUtils';
import { noKernel } from 'sql/services/notebook/sessionManager';
const msgLoading = localize('loading', 'Loading kernels...'); const msgLoading = localize('loading', 'Loading kernels...');
const kernelLabel: string = localize('Kernel', 'Kernel: '); const kernelLabel: string = localize('Kernel', 'Kernel: ');
@@ -26,6 +27,7 @@ const msgLoadingContexts = localize('loadingContexts', 'Loading contexts...');
const msgAddNewConnection = localize('addNewConnection', 'Add new connection'); const msgAddNewConnection = localize('addNewConnection', 'Add new connection');
const msgSelectConnection = localize('selectConnection', 'Select connection'); const msgSelectConnection = localize('selectConnection', 'Select connection');
const msgConnectionNotApplicable = localize('connectionNotSupported', 'n/a'); const msgConnectionNotApplicable = localize('connectionNotSupported', 'n/a');
const msgLocalHost = localize('localhost', 'Localhost');
// Action to add a cell to notebook based on cell type(code/markdown). // Action to add a cell to notebook based on cell type(code/markdown).
export class AddCellAction extends Action { export class AddCellAction extends Action {
@@ -48,6 +50,28 @@ export class AddCellAction extends Action {
} }
} }
export class SaveNotebookAction extends Action {
private static readonly notebookSavedMsg = localize('notebookSavedMsg', 'Notebook saved successfully.');
private static readonly notebookFailedSaveMsg = localize('notebookFailedSaveMsg', 'Failed to save Notebook.');
constructor(
id: string, label: string, cssClass: string,
@INotificationService private _notificationService: INotificationService
) {
super(id, label, cssClass);
}
public async run(context: NotebookComponent): TPromise<boolean> {
const actions: INotificationActions = { primary: [] };
let saved = await context.save();
if (saved) {
this._notificationService.notify({ severity: Severity.Info, message: SaveNotebookAction.notebookSavedMsg, actions });
} else {
this._notificationService.error(SaveNotebookAction.notebookFailedSaveMsg);
}
return saved;
}
}
export interface IToggleableState { export interface IToggleableState {
baseClass?: string; baseClass?: string;
shouldToggleTooltip?: boolean; shouldToggleTooltip?: boolean;
@@ -215,9 +239,8 @@ export class AttachToDropdown extends SelectBox {
// Load "Attach To" dropdown with the values corresponding to Kernel dropdown // Load "Attach To" dropdown with the values corresponding to Kernel dropdown
public async loadAttachToDropdown(model: INotebookModel, currentKernel: string): Promise<void> { public async loadAttachToDropdown(model: INotebookModel, currentKernel: string): Promise<void> {
if (currentKernel === notebookConstants.python3) { if (currentKernel === notebookConstants.python3 || currentKernel === noKernel) {
this.setOptions([msgConnectionNotApplicable]); this.setOptions([msgLocalHost]);
this.disable();
} }
else { else {
let hadoopConnections = this.getHadoopConnections(model); let hadoopConnections = this.getHadoopConnections(model);
@@ -292,6 +315,7 @@ export class AttachToDropdown extends SelectBox {
attachToConnections = attachToConnections.filter(val => val !== msgSelectConnection); attachToConnections = attachToConnections.filter(val => val !== msgSelectConnection);
let index = attachToConnections.findIndex((connection => connection === connectedServer)); let index = attachToConnections.findIndex((connection => connection === connectedServer));
this.setOptions([]);
this.setOptions(attachToConnections); this.setOptions(attachToConnections);
if (!index || index < 0 || index >= attachToConnections.length) { if (!index || index < 0 || index >= attachToConnections.length) {
index = 0; index = 0;

View File

@@ -86,6 +86,7 @@ export class NotebookEditor extends BaseEditor {
input.hasBootstrapped = true; input.hasBootstrapped = true;
let params: INotebookParams = { let params: INotebookParams = {
notebookUri: input.notebookUri, notebookUri: input.notebookUri,
input: input,
providerId: input.providerId ? input.providerId : DEFAULT_NOTEBOOK_PROVIDER, providerId: input.providerId ? input.providerId : DEFAULT_NOTEBOOK_PROVIDER,
isTrusted: input.isTrusted isTrusted: input.isTrusted
}; };

View File

@@ -11,6 +11,7 @@ import { EditorInput, EditorModel, ConfirmResult } from 'vs/workbench/common/edi
import { Emitter, Event } from 'vs/base/common/event'; import { Emitter, Event } from 'vs/base/common/event';
import URI from 'vs/base/common/uri'; import URI from 'vs/base/common/uri';
import { IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import * as resources from 'vs/base/common/resources';
import { INotebookService } from 'sql/services/notebook/notebookService'; import { INotebookService } from 'sql/services/notebook/notebookService';
@@ -88,15 +89,6 @@ export class NotebookInput extends EditorInput {
) { ) {
super(); super();
this._model.onDidChangeDirty(() => this._onDidChangeDirty.fire()); 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 { public get notebookUri(): URI {
@@ -116,6 +108,10 @@ export class NotebookInput extends EditorInput {
} }
public getName(): string { public getName(): string {
if (!this._title) {
this._title = resources.basenameOrAuthority(this._model.notebookUri);
}
return this._title; return this._title;
} }
@@ -173,4 +169,12 @@ export class NotebookInput extends EditorInput {
save(): TPromise<boolean> { save(): TPromise<boolean> {
return this._model.save(); return this._model.save();
} }
/**
* Sets active editor with dirty value.
* @param isDirty boolean value to set editor dirty
*/
setDirty(isDirty: boolean): void {
this._model.setDirty(isDirty);
}
} }

View File

@@ -16,7 +16,8 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
collector.addRule(` collector.addRule(`
.notebookEditor .notebook-cell.active { .notebookEditor .notebook-cell.active {
border-color: ${activeBorder}; border-color: ${activeBorder};
border-width: 2px; border-width: 1px;
box-shadow: 0px 4px 6px 0px rgba(0,0,0,0.14);
} }
`); `);
} }

View File

@@ -5,11 +5,15 @@
'use strict'; 'use strict';
import * as path from 'path';
import { nb } from 'sqlops'; import { nb } from 'sqlops';
import * as os from 'os'; import * as os from 'os';
import * as pfs from 'vs/base/node/pfs'; import * as pfs from 'vs/base/node/pfs';
import { localize } from 'vs/nls'; import { localize } from 'vs/nls';
import { IOutputChannel } from 'vs/workbench/parts/output/common/output'; import { IOutputChannel } from 'vs/workbench/parts/output/common/output';
import { Registry } from 'vs/platform/registry/common/platform';
import { INotebookProviderRegistry, Extensions } from 'sql/services/notebook/notebookRegistry';
import { DEFAULT_NOTEBOOK_PROVIDER, DEFAULT_NOTEBOOK_FILETYPE } from 'sql/services/notebook/notebookService';
/** /**
@@ -36,3 +40,23 @@ export async function mkDir(dirPath: string, outputChannel?: IOutputChannel): Pr
await pfs.mkdirp(dirPath); await pfs.mkdirp(dirPath);
} }
} }
export function getProviderForFileName(fileName: string): string {
let fileExt = path.extname(fileName);
let provider: string;
let notebookRegistry = Registry.as<INotebookProviderRegistry>(Extensions.NotebookProviderContribution);
// First try to get provider for actual file type
if (fileExt && fileExt.startsWith('.')) {
fileExt = fileExt.slice(1,fileExt.length);
provider = notebookRegistry.getProviderForFileType(fileExt);
}
// Fallback to provider for default file type (assume this is a global handler)
if (!provider) {
provider = notebookRegistry.getProviderForFileType(DEFAULT_NOTEBOOK_FILETYPE);
}
// Finally if all else fails, use the built-in handler
if (!provider) {
provider = DEFAULT_NOTEBOOK_PROVIDER;
}
return provider;
}

View File

@@ -9,7 +9,7 @@ import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiati
import * as nls from 'vs/nls'; import * as nls from 'vs/nls';
import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { IJSONSchema } from 'vs/base/common/jsonSchema';
import { IEditorService, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { IEditorService, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService';
import { IConnectionManagementService, IConnectionDialogService} from 'sql/parts/connection/common/connectionManagement'; import { IConnectionManagementService, IConnectionDialogService } from 'sql/parts/connection/common/connectionManagement';
import { IObjectExplorerService } from '../../objectExplorer/common/objectExplorerService'; import { IObjectExplorerService } from '../../objectExplorer/common/objectExplorerService';
import { ProfilerInput } from 'sql/parts/profiler/editor/profilerInput'; import { ProfilerInput } from 'sql/parts/profiler/editor/profilerInput';
import { TPromise } from 'vs/base/common/winjs.base'; import { TPromise } from 'vs/base/common/winjs.base';
@@ -55,10 +55,10 @@ CommandsRegistry.registerCommand({
let promise; let promise;
if (connectionProfile) { if (connectionProfile) {
promise = connectionService.connectIfNotConnected(connectionProfile); promise = connectionService.connectIfNotConnected(connectionProfile, 'connection', true);
} else { } else {
// if still no luck, we will open the Connection dialog and let user connect to a server // if still no luck, we will open the Connection dialog and let user connect to a server
promise = connectionDialogService.openDialogAndWait(connectionService, { connectionType: 1, providers: [mssqlProviderName] }).then((profile) => { promise = connectionDialogService.openDialogAndWait(connectionService, { connectionType: 0, showDashboard: false, providers: [mssqlProviderName] }).then((profile) => {
connectionProfile = profile as ConnectionProfile; connectionProfile = profile as ConnectionProfile;
}); });
} }

View File

@@ -308,8 +308,6 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
// ----- Public // ----- Public
public focusFindInput(): void { public focusFindInput(): void {
this._findInput.select();
// Edge browser requires focus() in addition to select()
this._findInput.focus(); this._findInput.focus();
} }

View File

@@ -218,6 +218,7 @@ export class ProfilerTableEditor extends BaseEditor implements IProfilerControll
if (p) { if (p) {
this._profilerTable.setActiveCell(p.row, p.col); this._profilerTable.setActiveCell(p.row, p.col);
this._updateFinderMatchState(); this._updateFinderMatchState();
this._finder.focusFindInput();
} }
}); });
} else { } else {

View File

@@ -557,6 +557,7 @@ export class ProfilerEditor extends BaseEditor {
} }
public focus() { public focus() {
this._profilerEditorContextKey.set(true);
super.focus(); super.focus();
let savedViewState = this._savedTableViewStates.get(this.input); let savedViewState = this._savedTableViewStates.get(this.input);
if (savedViewState) { if (savedViewState) {

View File

@@ -66,7 +66,8 @@ export class ProfilerInput extends EditorInput implements IProfilerSession {
let searchFn = (val: { [x: string]: string }, exp: string): Array<number> => { let searchFn = (val: { [x: string]: string }, exp: string): Array<number> => {
let ret = new Array<number>(); let ret = new Array<number>();
for (let i = 0; i < this._columns.length; i++) { for (let i = 0; i < this._columns.length; i++) {
if (val[this._columns[i]].includes(exp)) { let colVal = val[this._columns[i]];
if (colVal && colVal.toLocaleLowerCase().includes(exp.toLocaleLowerCase())) {
ret.push(i); ret.push(i);
} }
} }

View File

@@ -29,7 +29,7 @@ export interface IQueryManagementService {
runQueryStatement(ownerUri: string, line: number, column: number): Thenable<void>; runQueryStatement(ownerUri: string, line: number, column: number): Thenable<void>;
runQueryString(ownerUri: string, queryString: string): Thenable<void>; runQueryString(ownerUri: string, queryString: string): Thenable<void>;
runQueryAndReturn(ownerUri: string, queryString: string): Thenable<sqlops.SimpleExecuteResult>; runQueryAndReturn(ownerUri: string, queryString: string): Thenable<sqlops.SimpleExecuteResult>;
parseSyntax(ownerUri:string, query: string): Thenable<sqlops.SyntaxParseResult>; parseSyntax(ownerUri: string, query: string): Thenable<sqlops.SyntaxParseResult>;
getQueryRows(rowData: sqlops.QueryExecuteSubsetParams): Thenable<sqlops.QueryExecuteSubsetResult>; getQueryRows(rowData: sqlops.QueryExecuteSubsetParams): Thenable<sqlops.QueryExecuteSubsetResult>;
disposeQuery(ownerUri: string): Thenable<void>; disposeQuery(ownerUri: string): Thenable<void>;
saveResults(requestParams: sqlops.SaveResultsRequestParams): Thenable<sqlops.SaveResultRequestResult>; saveResults(requestParams: sqlops.SaveResultsRequestParams): Thenable<sqlops.SaveResultRequestResult>;
@@ -38,7 +38,8 @@ export interface IQueryManagementService {
onQueryComplete(result: sqlops.QueryExecuteCompleteNotificationResult): void; onQueryComplete(result: sqlops.QueryExecuteCompleteNotificationResult): void;
onBatchStart(batchInfo: sqlops.QueryExecuteBatchNotificationParams): void; onBatchStart(batchInfo: sqlops.QueryExecuteBatchNotificationParams): void;
onBatchComplete(batchInfo: sqlops.QueryExecuteBatchNotificationParams): void; onBatchComplete(batchInfo: sqlops.QueryExecuteBatchNotificationParams): void;
onResultSetComplete(resultSetInfo: sqlops.QueryExecuteResultSetCompleteNotificationParams): void; onResultSetAvailable(resultSetInfo: sqlops.QueryExecuteResultSetNotificationParams): void;
onResultSetUpdated(resultSetInfo: sqlops.QueryExecuteResultSetNotificationParams): void;
onMessage(message: sqlops.QueryExecuteMessageParams): void; onMessage(message: sqlops.QueryExecuteMessageParams): void;
// Edit Data Callbacks // Edit Data Callbacks
@@ -65,7 +66,7 @@ export interface IQueryRequestHandler {
runQueryStatement(ownerUri: string, line: number, column: number): Thenable<void>; runQueryStatement(ownerUri: string, line: number, column: number): Thenable<void>;
runQueryString(ownerUri: string, queryString: string): Thenable<void>; runQueryString(ownerUri: string, queryString: string): Thenable<void>;
runQueryAndReturn(ownerUri: string, queryString: string): Thenable<sqlops.SimpleExecuteResult>; runQueryAndReturn(ownerUri: string, queryString: string): Thenable<sqlops.SimpleExecuteResult>;
parseSyntax(ownerUri:string, query: string): Thenable<sqlops.SyntaxParseResult>; parseSyntax(ownerUri: string, query: string): Thenable<sqlops.SyntaxParseResult>;
getQueryRows(rowData: sqlops.QueryExecuteSubsetParams): Thenable<sqlops.QueryExecuteSubsetResult>; getQueryRows(rowData: sqlops.QueryExecuteSubsetParams): Thenable<sqlops.QueryExecuteSubsetResult>;
disposeQuery(ownerUri: string): Thenable<void>; disposeQuery(ownerUri: string): Thenable<void>;
saveResults(requestParams: sqlops.SaveResultsRequestParams): Thenable<sqlops.SaveResultRequestResult>; saveResults(requestParams: sqlops.SaveResultsRequestParams): Thenable<sqlops.SaveResultRequestResult>;
@@ -244,9 +245,15 @@ export class QueryManagementService implements IQueryManagementService {
}); });
} }
public onResultSetComplete(resultSetInfo: sqlops.QueryExecuteResultSetCompleteNotificationParams): void { public onResultSetAvailable(resultSetInfo: sqlops.QueryExecuteResultSetNotificationParams): void {
this._notify(resultSetInfo.ownerUri, (runner: QueryRunner) => { this._notify(resultSetInfo.ownerUri, (runner: QueryRunner) => {
runner.handleResultSetComplete(resultSetInfo); runner.handleResultSetAvailable(resultSetInfo);
});
}
public onResultSetUpdated(resultSetInfo: sqlops.QueryExecuteResultSetNotificationParams): void {
this._notify(resultSetInfo.ownerUri, (runner: QueryRunner) => {
runner.handleResultSetUpdated(resultSetInfo);
}); });
} }

View File

@@ -4,6 +4,8 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
'use strict'; 'use strict';
import * as pretty from 'pretty-data';
import { attachTableStyler } from 'sql/common/theme/styler'; import { attachTableStyler } from 'sql/common/theme/styler';
import QueryRunner from 'sql/parts/query/execution/queryRunner'; import QueryRunner from 'sql/parts/query/execution/queryRunner';
import { VirtualizedCollection, AsyncDataProvider } from 'sql/base/browser/ui/table/asyncDataView'; import { VirtualizedCollection, AsyncDataProvider } from 'sql/base/browser/ui/table/asyncDataView';
@@ -12,7 +14,7 @@ import { ScrollableSplitView } from 'sql/base/browser/ui/scrollableSplitview/scr
import { MouseWheelSupport } from 'sql/base/browser/ui/table/plugins/mousewheelTableScroll.plugin'; import { MouseWheelSupport } from 'sql/base/browser/ui/table/plugins/mousewheelTableScroll.plugin';
import { AutoColumnSize } from 'sql/base/browser/ui/table/plugins/autoSizeColumns.plugin'; import { AutoColumnSize } from 'sql/base/browser/ui/table/plugins/autoSizeColumns.plugin';
import { SaveFormat } from 'sql/parts/grid/common/interfaces'; import { SaveFormat } from 'sql/parts/grid/common/interfaces';
import { IGridActionContext, SaveResultAction, CopyResultAction, SelectAllGridAction, MaximizeTableAction, RestoreTableAction, ChartDataAction, ShowQueryPlanAction } from 'sql/parts/query/editor/actions'; import { IGridActionContext, SaveResultAction, CopyResultAction, SelectAllGridAction, MaximizeTableAction, RestoreTableAction, ChartDataAction } from 'sql/parts/query/editor/actions';
import { CellSelectionModel } from 'sql/base/browser/ui/table/plugins/cellSelectionModel.plugin'; import { CellSelectionModel } from 'sql/base/browser/ui/table/plugins/cellSelectionModel.plugin';
import { RowNumberColumn } from 'sql/base/browser/ui/table/plugins/rowNumberColumn.plugin'; import { RowNumberColumn } from 'sql/base/browser/ui/table/plugins/rowNumberColumn.plugin';
import { escape } from 'sql/base/common/strings'; import { escape } from 'sql/base/common/strings';
@@ -20,9 +22,9 @@ import { hyperLinkFormatter, textFormatter } from 'sql/parts/grid/services/share
import { CopyKeybind } from 'sql/base/browser/ui/table/plugins/copyKeybind.plugin'; import { CopyKeybind } from 'sql/base/browser/ui/table/plugins/copyKeybind.plugin';
import { AdditionalKeyBindings } from 'sql/base/browser/ui/table/plugins/additionalKeyBindings.plugin'; import { AdditionalKeyBindings } from 'sql/base/browser/ui/table/plugins/additionalKeyBindings.plugin';
import { ITableStyles, ITableMouseEvent } from 'sql/base/browser/ui/table/interfaces'; import { ITableStyles, ITableMouseEvent } from 'sql/base/browser/ui/table/interfaces';
import { warn } from 'sql/base/common/log';
import * as sqlops from 'sqlops'; import * as sqlops from 'sqlops';
import * as pretty from 'pretty-data';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
@@ -38,7 +40,7 @@ import { $ } from 'vs/base/browser/builder';
import { generateUuid } from 'vs/base/common/uuid'; import { generateUuid } from 'vs/base/common/uuid';
import { TPromise } from 'vs/base/common/winjs.base'; import { TPromise } from 'vs/base/common/winjs.base';
import { Separator, ActionBar, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; import { Separator, ActionBar, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
import { Dimension, getContentWidth, isInDOM } from 'vs/base/browser/dom'; import { isInDOM } from 'vs/base/browser/dom';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
@@ -175,7 +177,8 @@ export class GridPanel extends ViewletPanel {
this.reset(); this.reset();
this.queryRunnerDisposables = []; this.queryRunnerDisposables = [];
this.runner = runner; this.runner = runner;
this.queryRunnerDisposables.push(this.runner.onResultSet(e => this.onResultSet(e))); this.queryRunnerDisposables.push(this.runner.onResultSet(this.onResultSet, this));
this.queryRunnerDisposables.push(this.runner.onResultSetUpdate(this.updateResultSet, this));
this.queryRunnerDisposables.push(this.runner.onQueryStart(() => { this.queryRunnerDisposables.push(this.runner.onQueryStart(() => {
if (this.state) { if (this.state) {
this.state.tableStates = []; this.state.tableStates = [];
@@ -200,6 +203,33 @@ export class GridPanel extends ViewletPanel {
} }
} }
private updateResultSet(resultSet: sqlops.ResultSetSummary | sqlops.ResultSetSummary[]) {
let resultsToUpdate: sqlops.ResultSetSummary[];
if (!Array.isArray(resultSet)) {
resultsToUpdate = [resultSet];
} else {
resultsToUpdate = resultSet;
}
for (let set of resultsToUpdate) {
let table = this.tables.find(t => t.resultSet.batchId === set.batchId && t.resultSet.id === set.id);
if (table) {
table.updateResult(set);
} else {
warn('Got result set update request for non-existant table');
}
}
this.maximumBodySize = this.tables.reduce((p, c) => {
return p + c.maximumSize;
}, 0);
if (this.state && this.state.scrollPosition) {
this.splitView.setScrollPosition(this.state.scrollPosition);
}
}
private addResultSet(resultSet: sqlops.ResultSetSummary | sqlops.ResultSetSummary[]) { private addResultSet(resultSet: sqlops.ResultSetSummary | sqlops.ResultSetSummary[]) {
let resultsToAdd: sqlops.ResultSetSummary[]; let resultsToAdd: sqlops.ResultSetSummary[];
if (!Array.isArray(resultSet)) { if (!Array.isArray(resultSet)) {
@@ -323,9 +353,12 @@ class GridTable<T> extends Disposable implements IView {
private selectionModel = new CellSelectionModel(); private selectionModel = new CellSelectionModel();
private styles: ITableStyles; private styles: ITableStyles;
private currentHeight: number; private currentHeight: number;
private dataProvider: AsyncDataProvider<T>;
private columns: Slick.Column<T>[]; private columns: Slick.Column<T>[];
private rowNumberColumn: RowNumberColumn<T>;
private _onDidChange = new Emitter<number>(); private _onDidChange = new Emitter<number>();
public readonly onDidChange: Event<number> = this._onDidChange.event; public readonly onDidChange: Event<number> = this._onDidChange.event;
@@ -335,13 +368,20 @@ class GridTable<T> extends Disposable implements IView {
private _state: GridTableState; private _state: GridTableState;
private scrolled = false; private scrolled = false;
private visible = false;
public get resultSet(): sqlops.ResultSetSummary {
return this._resultSet;
}
// this handles if the row count is small, like 4-5 rows // this handles if the row count is small, like 4-5 rows
private readonly maxSize = ((this.resultSet.rowCount) * ROW_HEIGHT) + HEADER_HEIGHT + ESTIMATED_SCROLL_BAR_HEIGHT; private get maxSize(): number {
return ((this.resultSet.rowCount) * ROW_HEIGHT) + HEADER_HEIGHT + ESTIMATED_SCROLL_BAR_HEIGHT;
}
constructor( constructor(
private runner: QueryRunner, private runner: QueryRunner,
public readonly resultSet: sqlops.ResultSetSummary, private _resultSet: sqlops.ResultSetSummary,
@IContextMenuService private contextMenuService: IContextMenuService, @IContextMenuService private contextMenuService: IContextMenuService,
@IInstantiationService private instantiationService: IInstantiationService, @IInstantiationService private instantiationService: IInstantiationService,
@IEditorService private editorService: IEditorService, @IEditorService private editorService: IEditorService,
@@ -367,7 +407,31 @@ class GridTable<T> extends Disposable implements IView {
}); });
} }
public onAdd() {
this.visible = true;
let collection = new VirtualizedCollection(
50,
index => this.placeholdGenerator(index),
this.resultSet.rowCount,
(offset, count) => this.loadData(offset, count)
);
collection.setCollectionChangedCallback((startIndex, count) => {
this.renderGridDataRowsRange(startIndex, count);
});
this.dataProvider.dataRows = collection;
this.table.updateRowCount();
}
public onRemove() { public onRemove() {
this.visible = false;
let collection = new VirtualizedCollection(
50,
index => this.placeholdGenerator(index),
0,
() => TPromise.as([])
);
this.dataProvider.dataRows = collection;
this.table.updateRowCount();
// when we are removed slickgrid acts badly so we need to account for that // when we are removed slickgrid acts badly so we need to account for that
this.scrolled = false; this.scrolled = false;
} }
@@ -379,34 +443,38 @@ class GridTable<T> extends Disposable implements IView {
private build(): void { private build(): void {
let tableContainer = document.createElement('div'); let tableContainer = document.createElement('div');
tableContainer.style.display = 'inline-block'; tableContainer.style.display = 'inline-block';
tableContainer.style.width = `calc(100% - ${ACTIONBAR_WIDTH}px)`;
this.container.appendChild(tableContainer); this.container.appendChild(tableContainer);
let collection = new VirtualizedCollection(50, this.resultSet.rowCount, let collection = new VirtualizedCollection(
(offset, count) => this.loadData(offset, count), 50,
index => this.placeholdGenerator(index) index => this.placeholdGenerator(index),
0,
() => TPromise.as([])
); );
collection.setCollectionChangedCallback((change, startIndex, count) => { collection.setCollectionChangedCallback((startIndex, count) => {
this.renderGridDataRowsRange(startIndex, count); this.renderGridDataRowsRange(startIndex, count);
}); });
let numberColumn = new RowNumberColumn({ numberOfRows: this.resultSet.rowCount }); this.rowNumberColumn = new RowNumberColumn({ numberOfRows: this.resultSet.rowCount });
let copyHandler = new CopyKeybind(); let copyHandler = new CopyKeybind();
copyHandler.onCopy(e => { copyHandler.onCopy(e => {
new CopyResultAction(CopyResultAction.COPY_ID, CopyResultAction.COPY_LABEL, false).run(this.generateContext()); new CopyResultAction(CopyResultAction.COPY_ID, CopyResultAction.COPY_LABEL, false).run(this.generateContext());
}); });
this.columns.unshift(numberColumn.getColumnDefinition()); this.columns.unshift(this.rowNumberColumn.getColumnDefinition());
let tableOptions: Slick.GridOptions<T> = { let tableOptions: Slick.GridOptions<T> = {
rowHeight: ROW_HEIGHT, rowHeight: ROW_HEIGHT,
showRowNumber: true, showRowNumber: true,
forceFitColumns: false, forceFitColumns: false,
defaultColumnWidth: 120 defaultColumnWidth: 120
}; };
this.table = this._register(new Table(tableContainer, { dataProvider: new AsyncDataProvider(collection), columns: this.columns }, tableOptions)); this.dataProvider = new AsyncDataProvider(collection);
this.table = this._register(new Table(tableContainer, { dataProvider: this.dataProvider, columns: this.columns }, tableOptions));
this.table.setSelectionModel(this.selectionModel); this.table.setSelectionModel(this.selectionModel);
this.table.registerPlugin(new MouseWheelSupport()); this.table.registerPlugin(new MouseWheelSupport());
this.table.registerPlugin(new AutoColumnSize()); this.table.registerPlugin(new AutoColumnSize());
this.table.registerPlugin(copyHandler); this.table.registerPlugin(copyHandler);
this.table.registerPlugin(numberColumn); this.table.registerPlugin(this.rowNumberColumn);
this.table.registerPlugin(new AdditionalKeyBindings()); this.table.registerPlugin(new AdditionalKeyBindings());
this._register(this.table.onContextMenu(this.contextMenu, this)); this._register(this.table.onContextMenu(this.contextMenu, this));
this._register(this.table.onClick(this.onTableClick, this)); this._register(this.table.onClick(this.onTableClick, this));
@@ -507,7 +575,7 @@ class GridTable<T> extends Disposable implements IView {
} catch (e) { } catch (e) {
// If Xml fails to parse, fall back on original Xml content // If Xml fails to parse, fall back on original Xml content
} }
} else { } else {
let jsonContent: string = undefined; let jsonContent: string = undefined;
try { try {
jsonContent = JSON.parse(content); jsonContent = JSON.parse(content);
@@ -526,6 +594,15 @@ class GridTable<T> extends Disposable implements IView {
} }
} }
public updateResult(resultSet: sqlops.ResultSetSummary) {
this._resultSet = resultSet;
if (this.table && this.visible) {
this.dataProvider.length = resultSet.rowCount;
this.table.updateRowCount();
}
this.rowNumberColumn.updateRowCount(resultSet.rowCount);
}
private generateContext(cell?: Slick.Cell): IGridActionContext { private generateContext(cell?: Slick.Cell): IGridActionContext {
const selection = this.selectionModel.getSelectedRanges(); const selection = this.selectionModel.getSelectedRanges();
return <IGridActionContext>{ return <IGridActionContext>{
@@ -577,12 +654,7 @@ class GridTable<T> extends Disposable implements IView {
} else { } else {
this.currentHeight = size; this.currentHeight = size;
} }
this.table.layout( this.table.layout(size, Orientation.VERTICAL);
new Dimension(
getContentWidth(this.container) - ACTIONBAR_WIDTH,
size
)
);
} }
public get minimumSize(): number { public get minimumSize(): number {
@@ -596,6 +668,9 @@ class GridTable<T> extends Disposable implements IView {
private loadData(offset: number, count: number): Thenable<T[]> { private loadData(offset: number, count: number): Thenable<T[]> {
return this.runner.getQueryRows(offset, count, this.resultSet.batchId, this.resultSet.id).then(response => { return this.runner.getQueryRows(offset, count, this.resultSet.batchId, this.resultSet.id).then(response => {
if (!response.resultSubset) {
return [];
}
return response.resultSubset.rows.map(r => { return response.resultSubset.rows.map(r => {
let dataWithSchema = {}; let dataWithSchema = {};
// skip the first column since its a number column // skip the first column since its a number column

View File

@@ -282,6 +282,7 @@ export class QueryResultsView extends Disposable {
} }
public dispose() { public dispose() {
dispose(this.runnerDisposables);
super.dispose(); super.dispose();
} }
} }

View File

@@ -100,6 +100,18 @@ export default class QueryRunner extends Disposable {
private _echoedResultSet = echo(this._debouncedResultSet.event); private _echoedResultSet = echo(this._debouncedResultSet.event);
public readonly onResultSet = this._echoedResultSet.event; public readonly onResultSet = this._echoedResultSet.event;
private _onResultSetUpdate = this._register(new Emitter<sqlops.ResultSetSummary>());
private _debouncedResultSetUpdate = debounceEvent<sqlops.ResultSetSummary, sqlops.ResultSetSummary[]>(this._onResultSetUpdate.event, (l, e) => {
// on first run
if (types.isUndefinedOrNull(l)) {
return [e];
} else {
return l.concat(e);
}
});
private _echoedResultSetUpdate = echo(this._debouncedResultSetUpdate.event);
public readonly onResultSetUpdate = this._echoedResultSetUpdate.event;
private _onQueryStart = this._register(new Emitter<void>()); private _onQueryStart = this._register(new Emitter<void>());
public readonly onQueryStart: Event<void> = this._onQueryStart.event; public readonly onQueryStart: Event<void> = this._onQueryStart.event;
@@ -336,7 +348,7 @@ export default class QueryRunner extends Disposable {
/** /**
* Handle a ResultSetComplete from the service layer * Handle a ResultSetComplete from the service layer
*/ */
public handleResultSetComplete(result: sqlops.QueryExecuteResultSetCompleteNotificationParams): void { public handleResultSetAvailable(result: sqlops.QueryExecuteResultSetNotificationParams): void {
if (result && result.resultSetSummary) { if (result && result.resultSetSummary) {
let resultSet = result.resultSetSummary; let resultSet = result.resultSetSummary;
let batchSet: sqlops.BatchSummary; let batchSet: sqlops.BatchSummary;
@@ -365,7 +377,9 @@ export default class QueryRunner extends Disposable {
this.getQueryRows(0, 1, result.resultSetSummary.batchId, result.resultSetSummary.id).then(e => this._planXml.resolve(e.resultSubset.rows[0][0].displayValue)); this.getQueryRows(0, 1, result.resultSetSummary.batchId, result.resultSetSummary.id).then(e => this._planXml.resolve(e.resultSubset.rows[0][0].displayValue));
} }
} }
if (batchSet) { // we will just ignore the set if we already have it
// ideally this should never happen
if (batchSet && !batchSet.resultSetSummaries[resultSet.id]) {
// Store the result set in the batch and emit that a result set has completed // Store the result set in the batch and emit that a result set has completed
batchSet.resultSetSummaries[resultSet.id] = resultSet; batchSet.resultSetSummaries[resultSet.id] = resultSet;
this._eventEmitter.emit(EventType.RESULT_SET, resultSet); this._eventEmitter.emit(EventType.RESULT_SET, resultSet);
@@ -374,6 +388,27 @@ export default class QueryRunner extends Disposable {
} }
} }
public handleResultSetUpdated(result: sqlops.QueryExecuteResultSetNotificationParams): void {
if (result && result.resultSetSummary) {
let resultSet = result.resultSetSummary;
let batchSet: sqlops.BatchSummary;
batchSet = this.batchSets[resultSet.batchId];
// handle getting queryPlanxml if we need too
if (this.isQueryPlan) {
// check if this result has show plan, this needs work, it won't work for any other provider
let hasShowPlan = !!result.resultSetSummary.columnInfo.find(e => e.columnName === 'Microsoft SQL Server 2005 XML Showplan');
if (hasShowPlan) {
this.getQueryRows(0, 1, result.resultSetSummary.batchId, result.resultSetSummary.id).then(e => this._planXml.resolve(e.resultSubset.rows[0][0].displayValue));
}
}
if (batchSet) {
// Store the result set in the batch and emit that a result set has completed
batchSet.resultSetSummaries[resultSet.id] = resultSet;
this._onResultSetUpdate.fire(resultSet);
}
}
}
/** /**
* Handle a Mssage from the service layer * Handle a Mssage from the service layer
*/ */

View File

@@ -249,9 +249,9 @@ export class QueryEditorService implements IQueryEditorService {
QueryEditorService.editorService.openEditor(newEditorInput, options, group).then((editor) => { QueryEditorService.editorService.openEditor(newEditorInput, options, group).then((editor) => {
resolve(QueryEditorService._onEditorOpened(editor, uri.toString(), undefined, options.pinned)); resolve(QueryEditorService._onEditorOpened(editor, uri.toString(), undefined, options.pinned));
}, },
(error) => { (error) => {
reject(error); reject(error);
}); });
}); });
}); });
} }

View File

@@ -22,8 +22,9 @@ export class CustomDialogService {
constructor( @IInstantiationService private _instantiationService: IInstantiationService) { } constructor( @IInstantiationService private _instantiationService: IInstantiationService) { }
public showDialog(dialog: Dialog, options?: IModalOptions): void { public showDialog(dialog: Dialog, dialogName?: string, options?: IModalOptions): void {
let dialogModal = this._instantiationService.createInstance(DialogModal, dialog, 'CustomDialog', options || defaultOptions); let name = dialogName ? dialogName : 'CustomDialog';
let dialogModal = this._instantiationService.createInstance(DialogModal, dialog, name, options || defaultOptions);
this._dialogModals.set(dialog, dialogModal); this._dialogModals.set(dialog, dialogModal);
dialogModal.render(); dialogModal.render();
dialogModal.open(); dialogModal.open();

View File

@@ -6,11 +6,11 @@
'use strict'; 'use strict';
import * as sqlops from 'sqlops'; import * as sqlops from 'sqlops';
import * as nls from 'vs/nls';
import * as platform from 'vs/platform/registry/common/platform'; import * as platform from 'vs/platform/registry/common/platform';
import * as statusbar from 'vs/workbench/browser/parts/statusbar/statusbar'; import * as statusbar from 'vs/workbench/browser/parts/statusbar/statusbar';
import { Event, Emitter } from 'vs/base/common/event'; import { Event, Emitter } from 'vs/base/common/event';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IStorageService } from 'vs/platform/storage/common/storage'; import { IStorageService } from 'vs/platform/storage/common/storage';
import { Memento, Scope as MementoScope } from 'vs/workbench/common/memento'; import { Memento, Scope as MementoScope } from 'vs/workbench/common/memento';
@@ -20,8 +20,8 @@ import { AccountDialogController } from 'sql/parts/accountManagement/accountDial
import { AutoOAuthDialogController } from 'sql/parts/accountManagement/autoOAuthDialog/autoOAuthDialogController'; import { AutoOAuthDialogController } from 'sql/parts/accountManagement/autoOAuthDialog/autoOAuthDialogController';
import { AccountListStatusbarItem } from 'sql/parts/accountManagement/accountListStatusbar/accountListStatusbarItem'; import { AccountListStatusbarItem } from 'sql/parts/accountManagement/accountListStatusbar/accountListStatusbarItem';
import { AccountProviderAddedEventParams, UpdateAccountListEventParams } from 'sql/services/accountManagement/eventTypes'; import { AccountProviderAddedEventParams, UpdateAccountListEventParams } from 'sql/services/accountManagement/eventTypes';
import { IAccountManagementService, AzureResource } from 'sql/services/accountManagement/interfaces'; import { IAccountManagementService } from 'sql/services/accountManagement/interfaces';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { Deferred } from 'sql/base/common/promise';
export class AccountManagementService implements IAccountManagementService { export class AccountManagementService implements IAccountManagementService {
// CONSTANTS /////////////////////////////////////////////////////////// // CONSTANTS ///////////////////////////////////////////////////////////
@@ -198,11 +198,6 @@ export class AccountManagementService implements IAccountManagementService {
public getAccountsForProvider(providerId: string): Thenable<sqlops.Account[]> { public getAccountsForProvider(providerId: string): Thenable<sqlops.Account[]> {
let self = this; let self = this;
// Make sure the provider exists before attempting to retrieve accounts
if (!this._providers[providerId]) {
return Promise.reject(new Error(nls.localize('accountManagementNoProvider', 'Account provider does not exist'))).then();
}
// 1) Get the accounts from the store // 1) Get the accounts from the store
// 2) Update our local cache of accounts // 2) Update our local cache of accounts
return this.doWithProvider(providerId, provider => { return this.doWithProvider(providerId, provider => {
@@ -217,7 +212,7 @@ export class AccountManagementService implements IAccountManagementService {
/** /**
* Generates a security token by asking the account's provider * Generates a security token by asking the account's provider
* @param {Account} account Account to generate security token for * @param {Account} account Account to generate security token for
* @param {AzureResource} resource The resource to get the security token for * @param {sqlops.AzureResource} resource The resource to get the security token for
* @return {Thenable<{}>} Promise to return the security token * @return {Thenable<{}>} Promise to return the security token
*/ */
public getSecurityToken(account: sqlops.Account, resource: sqlops.AzureResource): Thenable<{}> { public getSecurityToken(account: sqlops.Account, resource: sqlops.AzureResource): Thenable<{}> {
@@ -389,10 +384,17 @@ export class AccountManagementService implements IAccountManagementService {
// PRIVATE HELPERS ///////////////////////////////////////////////////// // PRIVATE HELPERS /////////////////////////////////////////////////////
private doWithProvider<T>(providerId: string, op: (provider: AccountProviderWithMetadata) => Thenable<T>): Thenable<T> { private doWithProvider<T>(providerId: string, op: (provider: AccountProviderWithMetadata) => Thenable<T>): Thenable<T> {
// Make sure the provider exists before attempting to retrieve accounts
let provider = this._providers[providerId]; let provider = this._providers[providerId];
if (!provider) { if (!provider) {
return Promise.reject(new Error(nls.localize('accountManagementNoProvider', 'Account provider does not exist'))).then(); // If the provider doesn't already exist wait until it gets registered
let deferredPromise = new Deferred<T>();
let toDispose = this.addAccountProviderEvent(params => {
if (params.addedProvider.id === providerId) {
toDispose.dispose();
deferredPromise.resolve(op(this._providers[providerId]));
}
});
return deferredPromise;
} }
return op(provider); return op(provider);

View File

@@ -12,10 +12,9 @@ import * as pfs from 'vs/base/node/pfs';
import URI from 'vs/base/common/uri'; import URI from 'vs/base/common/uri';
import ContentManager = nb.ContentManager; import ContentManager = nb.ContentManager;
import INotebook = nb.INotebook;
export class LocalContentManager implements ContentManager { export class LocalContentManager implements ContentManager {
public async getNotebookContents(notebookUri: URI): Promise<INotebook> { public async getNotebookContents(notebookUri: URI): Promise<nb.INotebookContents> {
if (!notebookUri) { if (!notebookUri) {
return undefined; return undefined;
} }
@@ -23,10 +22,10 @@ export class LocalContentManager implements ContentManager {
let path = notebookUri.fsPath; let path = notebookUri.fsPath;
// Note: intentionally letting caller handle exceptions // Note: intentionally letting caller handle exceptions
let notebookFileBuffer = await pfs.readFile(path); let notebookFileBuffer = await pfs.readFile(path);
return <INotebook>json.parse(notebookFileBuffer.toString()); return <nb.INotebookContents>json.parse(notebookFileBuffer.toString());
} }
public async save(notebookUri: URI, notebook: INotebook): Promise<INotebook> { public async save(notebookUri: URI, notebook: nb.INotebookContents): Promise<nb.INotebookContents> {
// Convert to JSON with pretty-print functionality // Convert to JSON with pretty-print functionality
let contents = JSON.stringify(notebook, undefined, ' '); let contents = JSON.stringify(notebook, undefined, ' ');
let path = notebookUri.fsPath; let path = notebookUri.fsPath;

View File

@@ -6,21 +6,29 @@
'use strict'; 'use strict';
import * as sqlops from 'sqlops'; import * as sqlops from 'sqlops';
import { Event } from 'vs/base/common/event';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import URI from 'vs/base/common/uri'; import URI from 'vs/base/common/uri';
import { IBootstrapParams } from 'sql/services/bootstrap/bootstrapService'; import { IBootstrapParams } from 'sql/services/bootstrap/bootstrapService';
import { RenderMimeRegistry } from 'sql/parts/notebook/outputs/registry'; import { RenderMimeRegistry } from 'sql/parts/notebook/outputs/registry';
import { ModelFactory } from 'sql/parts/notebook/models/modelFactory'; import { ModelFactory } from 'sql/parts/notebook/models/modelFactory';
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces'; import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
import { NotebookInput } from 'sql/parts/notebook/notebookInput';
import { ISingleNotebookEditOperation } from 'sql/workbench/api/common/sqlExtHostTypes';
export const SERVICE_ID = 'notebookService'; export const SERVICE_ID = 'notebookService';
export const INotebookService = createDecorator<INotebookService>(SERVICE_ID); export const INotebookService = createDecorator<INotebookService>(SERVICE_ID);
export const DEFAULT_NOTEBOOK_PROVIDER = 'builtin'; export const DEFAULT_NOTEBOOK_PROVIDER = 'builtin';
export const DEFAULT_NOTEBOOK_FILETYPE = 'IPYNB';
export interface INotebookService { export interface INotebookService {
_serviceBrand: any; _serviceBrand: any;
onNotebookEditorAdd: Event<INotebookEditor>;
onNotebookEditorRemove: Event<INotebookEditor>;
/** /**
* Register a metadata provider * Register a metadata provider
*/ */
@@ -40,7 +48,11 @@ export interface INotebookService {
*/ */
getOrCreateNotebookManager(providerId: string, uri: URI): Thenable<INotebookManager>; getOrCreateNotebookManager(providerId: string, uri: URI): Thenable<INotebookManager>;
handleNotebookClosed(uri: URI): void; addNotebookEditor(editor: INotebookEditor): void;
removeNotebookEditor(editor: INotebookEditor): void;
listNotebookEditors(): INotebookEditor[];
shutdown(): void; shutdown(): void;
@@ -62,8 +74,19 @@ export interface INotebookManager {
export interface INotebookParams extends IBootstrapParams { export interface INotebookParams extends IBootstrapParams {
notebookUri: URI; notebookUri: URI;
input: NotebookInput;
providerId: string; providerId: string;
isTrusted: boolean; isTrusted: boolean;
profile?: IConnectionProfile; profile?: IConnectionProfile;
modelFactory?: ModelFactory; modelFactory?: ModelFactory;
}
export interface INotebookEditor {
readonly notebookParams: INotebookParams;
readonly id: string;
isDirty(): boolean;
isActive(): boolean;
isVisible(): boolean;
save(): Promise<boolean>;
executeEdits(edits: ISingleNotebookEditOperation[]): boolean;
} }

View File

@@ -10,20 +10,26 @@ import { localize } from 'vs/nls';
import URI from 'vs/base/common/uri'; import URI from 'vs/base/common/uri';
import { Registry } from 'vs/platform/registry/common/platform'; import { Registry } from 'vs/platform/registry/common/platform';
import { INotebookService, INotebookManager, INotebookProvider, DEFAULT_NOTEBOOK_PROVIDER } from 'sql/services/notebook/notebookService'; import {
INotebookService, INotebookManager, INotebookProvider, DEFAULT_NOTEBOOK_PROVIDER,
DEFAULT_NOTEBOOK_FILETYPE, INotebookEditor
} from 'sql/services/notebook/notebookService';
import { RenderMimeRegistry } from 'sql/parts/notebook/outputs/registry'; import { RenderMimeRegistry } from 'sql/parts/notebook/outputs/registry';
import { standardRendererFactories } from 'sql/parts/notebook/outputs/factories'; import { standardRendererFactories } from 'sql/parts/notebook/outputs/factories';
import { LocalContentManager } from 'sql/services/notebook/localContentManager'; import { LocalContentManager } from 'sql/services/notebook/localContentManager';
import { SessionManager } from 'sql/services/notebook/sessionManager'; import { SessionManager } from 'sql/services/notebook/sessionManager';
import { Extensions, INotebookProviderRegistry } from 'sql/services/notebook/notebookRegistry'; import { Extensions, INotebookProviderRegistry } from 'sql/services/notebook/notebookRegistry';
import { Emitter, Event } from 'vs/base/common/event';
const DEFAULT_NOTEBOOK_FILETYPE = 'IPYNB';
export class NotebookService implements INotebookService { export class NotebookService implements INotebookService {
_serviceBrand: any; _serviceBrand: any;
private _mimeRegistry: RenderMimeRegistry; private _mimeRegistry: RenderMimeRegistry;
private _providers: Map<string, INotebookProvider> = new Map(); private _providers: Map<string, INotebookProvider> = new Map();
private _managers: Map<string, INotebookManager> = new Map(); private _managers: Map<string, INotebookManager> = new Map();
private _onNotebookEditorAdd = new Emitter<INotebookEditor>();
private _onNotebookEditorRemove = new Emitter<INotebookEditor>();
private _editors = new Map<string, INotebookEditor>();
constructor() { constructor() {
this.registerDefaultProvider(); this.registerDefaultProvider();
@@ -71,8 +77,34 @@ export class NotebookService implements INotebookService {
return manager; return manager;
} }
handleNotebookClosed(notebookUri: URI): void { get onNotebookEditorAdd(): Event<INotebookEditor> {
return this._onNotebookEditorAdd.event;
}
get onNotebookEditorRemove(): Event<INotebookEditor> {
return this._onNotebookEditorRemove.event;
}
addNotebookEditor(editor: INotebookEditor): void {
this._editors.set(editor.id, editor);
this._onNotebookEditorAdd.fire(editor);
}
removeNotebookEditor(editor: INotebookEditor): void {
if (this._editors.delete(editor.id)) {
this._onNotebookEditorRemove.fire(editor);
}
// Remove the manager from the tracked list, and let the notebook provider know that it should update its mappings // Remove the manager from the tracked list, and let the notebook provider know that it should update its mappings
this.sendNotebookCloseToProvider(editor);
}
listNotebookEditors(): INotebookEditor[] {
let editors = [];
this._editors.forEach(e => editors.push(e));
return editors;
}
private sendNotebookCloseToProvider(editor: INotebookEditor) {
let notebookUri = editor.notebookParams.notebookUri;
let uriString = notebookUri.toString(); let uriString = notebookUri.toString();
let manager = this._managers.get(uriString); let manager = this._managers.get(uriString);
if (manager) { if (manager) {
@@ -82,15 +114,20 @@ export class NotebookService implements INotebookService {
} }
} }
// PRIVATE HELPERS ///////////////////////////////////////////////////// // PRIVATE HELPERS /////////////////////////////////////////////////////
private doWithProvider<T>(providerId: string, op: (provider: INotebookProvider) => Thenable<T>): Thenable<T> { private doWithProvider<T>(providerId: string, op: (provider: INotebookProvider) => Thenable<T>): Thenable<T> {
// Make sure the provider exists before attempting to retrieve accounts // Make sure the provider exists before attempting to retrieve accounts
let provider = this._providers.get(providerId); let provider: INotebookProvider;
if (this._providers.has(providerId)) {
provider = this._providers.get(providerId);
}
else {
provider = this._providers.get(DEFAULT_NOTEBOOK_PROVIDER);
}
if (!provider) { if (!provider) {
return Promise.reject(new Error(localize('notebookServiceNoProvider', 'Notebook provider does not exist'))).then(); return Promise.reject(new Error(localize('notebookServiceNoProvider', 'Notebook provider does not exist'))).then();
} }
return op(provider); return op(provider);
} }
@@ -103,8 +140,6 @@ export class NotebookService implements INotebookService {
} }
return this._mimeRegistry; return this._mimeRegistry;
} }
} }
export class BuiltinProvider implements INotebookProvider { export class BuiltinProvider implements INotebookProvider {

View File

@@ -4,7 +4,7 @@ import { nb } from 'sqlops';
import { localize } from 'vs/nls'; import { localize } from 'vs/nls';
import { FutureInternal } from 'sql/parts/notebook/models/modelInterfaces'; import { FutureInternal } from 'sql/parts/notebook/models/modelInterfaces';
const noKernel: string = localize('noKernel', 'No Kernel'); export const noKernel: string = localize('noKernel', 'No Kernel');
const runNotebookDisabled = localize('runNotebookDisabled', 'Cannot run cells as no kernel has been configured'); const runNotebookDisabled = localize('runNotebookDisabled', 'Cannot run cells as no kernel has been configured');
let noKernelSpec: nb.IKernelSpec = ({ let noKernelSpec: nb.IKernelSpec = ({
@@ -24,9 +24,9 @@ export class SessionManager implements nb.SessionManager {
public get specs(): nb.IAllKernels { public get specs(): nb.IAllKernels {
let allKernels: nb.IAllKernels = { let allKernels: nb.IAllKernels = {
defaultKernel: noKernel, defaultKernel: noKernel,
kernels: [noKernelSpec] kernels: [noKernelSpec]
}; };
return allKernels; return allKernels;
} }
@@ -40,7 +40,7 @@ export class SessionManager implements nb.SessionManager {
} }
} }
class EmptySession implements nb.ISession { export class EmptySession implements nb.ISession {
private _kernel: EmptyKernel; private _kernel: EmptyKernel;
private _defaultKernelLoaded = false; private _defaultKernelLoaded = false;
@@ -146,7 +146,7 @@ class EmptyKernel implements nb.IKernel {
} }
} }
class EmptyFuture implements FutureInternal { export class EmptyFuture implements FutureInternal {
get inProgress(): boolean { get inProgress(): boolean {

26
src/sql/sqlops.d.ts vendored
View File

@@ -214,6 +214,7 @@ declare module 'sqlops' {
providerName: string; providerName: string;
saveProfile: boolean; saveProfile: boolean;
id: string; id: string;
azureTenantId?: string;
} }
/** /**
@@ -694,7 +695,8 @@ declare module 'sqlops' {
registerOnQueryComplete(handler: (result: QueryExecuteCompleteNotificationResult) => any): void; registerOnQueryComplete(handler: (result: QueryExecuteCompleteNotificationResult) => any): void;
registerOnBatchStart(handler: (batchInfo: QueryExecuteBatchNotificationParams) => any): void; registerOnBatchStart(handler: (batchInfo: QueryExecuteBatchNotificationParams) => any): void;
registerOnBatchComplete(handler: (batchInfo: QueryExecuteBatchNotificationParams) => any): void; registerOnBatchComplete(handler: (batchInfo: QueryExecuteBatchNotificationParams) => any): void;
registerOnResultSetComplete(handler: (resultSetInfo: QueryExecuteResultSetCompleteNotificationParams) => any): void; registerOnResultSetAvailable(handler: (resultSetInfo: QueryExecuteResultSetNotificationParams) => any): void;
registerOnResultSetUpdated(handler: (resultSetInfo: QueryExecuteResultSetNotificationParams) => any): void;
registerOnMessage(handler: (message: QueryExecuteMessageParams) => any): void; registerOnMessage(handler: (message: QueryExecuteMessageParams) => any): void;
// Edit Data Requests // Edit Data Requests
@@ -769,6 +771,7 @@ declare module 'sqlops' {
batchId: number; batchId: number;
rowCount: number; rowCount: number;
columnInfo: IDbColumn[]; columnInfo: IDbColumn[];
complete: boolean;
} }
export interface BatchSummary { export interface BatchSummary {
@@ -837,7 +840,7 @@ declare module 'sqlops' {
} }
export interface QueryExecuteResultSetCompleteNotificationParams { export interface QueryExecuteResultSetNotificationParams {
resultSetSummary: ResultSetSummary; resultSetSummary: ResultSetSummary;
ownerUri: string; ownerUri: string;
} }
@@ -1297,15 +1300,16 @@ declare module 'sqlops' {
lastRun: string; lastRun: string;
nextRun: string; nextRun: string;
jobId: string; jobId: string;
EmailLevel: JobCompletionActionCondition; startStepId: number;
PageLevel: JobCompletionActionCondition; emailLevel: JobCompletionActionCondition;
EventLogLevel: JobCompletionActionCondition; pageLevel: JobCompletionActionCondition;
DeleteLevel: JobCompletionActionCondition; eventLogLevel: JobCompletionActionCondition;
OperatorToEmail: string; deleteLevel: JobCompletionActionCondition;
OperatorToPage: string; operatorToEmail: string;
JobSteps: AgentJobStepInfo[]; operatorToPage: string;
JobSchedules: AgentJobScheduleInfo[]; jobSteps: AgentJobStepInfo[];
Alerts: AgentAlertInfo[]; jobSchedules: AgentJobScheduleInfo[];
alerts: AgentAlertInfo[];
} }
export interface AgentJobScheduleInfo { export interface AgentJobScheduleInfo {

View File

@@ -839,7 +839,7 @@ declare module 'sqlops' {
* Create a dialog with the given title * Create a dialog with the given title
* @param title The title of the dialog, displayed at the top * @param title The title of the dialog, displayed at the top
*/ */
export function createDialog(title: string): Dialog; export function createDialog(title: string, dialogName?: string): Dialog;
/** /**
* Create a dialog tab which can be included as part of the content of a dialog * Create a dialog tab which can be included as part of the content of a dialog
@@ -951,6 +951,12 @@ declare module 'sqlops' {
*/ */
message: DialogMessage; message: DialogMessage;
/**
* Set the dialog name when opening
* the dialog for telemetry
*/
dialogName?: string;
/** /**
* Register a callback that will be called when the user tries to click done. Only * Register a callback that will be called when the user tries to click done. Only
* one callback can be registered at once, so each registration call will clear * one callback can be registered at once, so each registration call will clear
@@ -1368,6 +1374,282 @@ declare module 'sqlops' {
} }
export namespace nb { export namespace nb {
/**
* All notebook documents currently known to the system.
*
* @readonly
*/
export let notebookDocuments: NotebookDocument[];
/**
* The currently active Notebook editor or `undefined`. The active editor is the one
* that currently has focus or, when none has focus, the one that has changed
* input most recently.
*/
export let activeNotebookEditor: NotebookEditor | undefined;
/**
* The currently visible editors or an empty array.
*/
export let visibleNotebookEditors: NotebookEditor[];
/**
* An event that is emitted when a [notebook document](#NotebookDocument) is opened.
*
* To add an event listener when a visible text document is opened, use the [TextEditor](#TextEditor) events in the
* [window](#window) namespace. Note that:
*
* - The event is emitted before the [document](#NotebookDocument) is updated in the
* [active notebook editor](#nb.activeNotebookEditor)
* - When a [notebook document](#NotebookDocument) is already open (e.g.: open in another visible notebook editor) this event is not emitted
*
*/
export const onDidOpenNotebookDocument: vscode.Event<NotebookDocument>;
/**
* An event that is emitted when a [notebook's](#NotebookDocument) cell contents are changed.
*/
export const onDidChangeNotebookCell: vscode.Event<NotebookCellChangeEvent>;
/**
* Show the given document in a notebook editor. A [column](#ViewColumn) can be provided
* to control where the editor is being shown. Might change the [active editor](#nb.activeNotebookEditor).
*
* The document is denoted by an [uri](#Uri). Depending on the [scheme](#Uri.scheme) the
* following rules apply:
* `file`-scheme: Open a file on disk, will be rejected if the file does not exist or cannot be loaded.
* `untitled`-scheme: A new file that should be saved on disk, e.g. `untitled:c:\frodo\new.js`. The language
* will be derived from the file name.
* For all other schemes the registered notebook providers are consulted.
*
* @param document A document to be shown.
* @param column A view column in which the [editor](#NotebookEditor) should be shown. The default is the [active](#ViewColumn.Active), other values
* are adjusted to be `Min(column, columnCount + 1)`, the [active](#ViewColumn.Active)-column is not adjusted. Use [`ViewColumn.Beside`](#ViewColumn.Beside)
* to open the editor to the side of the currently active one.
* @param preserveFocus When `true` the editor will not take focus.
* @return A promise that resolves to a [notebook editor](#NotebookEditor).
*/
export function showNotebookDocument(uri: vscode.Uri, showOptions?: NotebookShowOptions): Thenable<NotebookEditor>;
export interface NotebookDocument {
/**
* The associated uri for this notebook document.
*
* *Note* that most documents use the `file`-scheme, which means they are files on disk. However, **not** all documents are
* saved on disk and therefore the `scheme` must be checked before trying to access the underlying file or siblings on disk.
*
*/
readonly uri: vscode.Uri;
/**
* The file system path of the associated resource. Shorthand
* notation for [TextDocument.uri.fsPath](#TextDocument.uri). Independent of the uri scheme.
*/
readonly fileName: string;
/**
* Is this document representing an untitled file which has never been saved yet. *Note* that
* this does not mean the document will be saved to disk, use [`uri.scheme`](#Uri.scheme)
* to figure out where a document will be [saved](#FileSystemProvider), e.g. `file`, `ftp` etc.
*/
readonly isUntitled: boolean;
/**
* The identifier of the Notebook provider associated with this document.
*/
readonly providerId: string;
/**
* `true` if there are unpersisted changes.
*/
readonly isDirty: boolean;
/**
* `true` if the document have been closed. A closed document isn't synchronized anymore
* and won't be re-used when the same resource is opened again.
*/
readonly isClosed: boolean;
/**
* All cells.
*/
readonly cells: NotebookCell[];
/**
* Save the underlying file.
*
* @return A promise that will resolve to true when the file
* has been saved. If the file was not dirty or the save failed,
* will return false.
*/
save(): Thenable<boolean>;
/**
* Ensure a cell range is completely contained in this document.
*
* @param range A cell range.
* @return The given range or a new, adjusted range.
*/
validateCellRange(range: CellRange): CellRange;
}
/**
* A cell range represents an ordered pair of two positions in a list of cells.
* It is guaranteed that [start](#CellRange.start).isBeforeOrEqual([end](#CellRange.end))
*
* CellRange objects are __immutable__.
*/
export class CellRange {
/**
* The start index. It is before or equal to [end](#CellRange.end).
*/
readonly start: number;
/**
* The end index. It is after or equal to [start](#CellRange.start).
*/
readonly end: number;
/**
* Create a new range from two positions. If `start` is not
* before or equal to `end`, the values will be swapped.
*
* @param start A number.
* @param end A number.
*/
constructor(start: number, end: number);
}
export interface NotebookEditor {
/**
* The document associated with this editor. The document will be the same for the entire lifetime of this editor.
*/
readonly document: NotebookDocument;
/**
* The column in which this editor shows. Will be `undefined` in case this
* isn't one of the main editors, e.g an embedded editor, or when the editor
* column is larger than three.
*/
viewColumn?: vscode.ViewColumn;
/**
* Perform an edit on the document associated with this notebook editor.
*
* The given callback-function is invoked with an [edit-builder](#NotebookEditorEdit) which must
* be used to make edits. Note that the edit-builder is only valid while the
* callback executes.
*
* @param callback A function which can create edits using an [edit-builder](#NotebookEditorEdit).
* @param options The undo/redo behavior around this edit. By default, undo stops will be created before and after this edit.
* @return A promise that resolves with a value indicating if the edits could be applied.
*/
edit(callback: (editBuilder: NotebookEditorEdit) => void, options?: { undoStopBefore: boolean; undoStopAfter: boolean; }): Thenable<boolean>;
}
export interface NotebookCell {
contents: ICellContents;
}
export interface NotebookShowOptions {
/**
* An optional view column in which the [editor](#NotebookEditor) should be shown.
* The default is the [active](#ViewColumn.Active), other values are adjusted to
* be `Min(column, columnCount + 1)`, the [active](#ViewColumn.Active)-column is
* not adjusted. Use [`ViewColumn.Beside`](#ViewColumn.Beside) to open the
* editor to the side of the currently active one.
*/
viewColumn?: vscode.ViewColumn;
/**
* An optional flag that when `true` will stop the [editor](#NotebookEditor) from taking focus.
*/
preserveFocus?: boolean;
/**
* An optional flag that controls if an [editor](#NotebookEditor)-tab will be replaced
* with the next editor or if it will be kept.
*/
preview?: boolean;
/**
* An optional string indicating which notebook provider to initially use
*/
providerId?: string;
/**
* Optional ID indicating the initial connection to use for this editor
*/
connectionId?: string;
}
/**
* Represents an event describing the change in a [notebook documents's cells](#NotebookDocument.cells).
*/
export interface NotebookCellChangeEvent {
/**
* The [notebook document](#NotebookDocument) for which the selections have changed.
*/
notebook: NotebookDocument;
/**
* The new value for the [notebook documents's cells](#NotebookDocument.cells).
*/
cell: NotebookCell[];
/**
* The [change kind](#TextEditorSelectionChangeKind) which has triggered this
* event. Can be `undefined`.
*/
kind?: vscode.TextEditorSelectionChangeKind;
}
/**
* A complex edit that will be applied in one transaction on a TextEditor.
* This holds a description of the edits and if the edits are valid (i.e. no overlapping regions, document was not changed in the meantime, etc.)
* they can be applied on a [document](#TextDocument) associated with a [text editor](#TextEditor).
*
*/
export interface NotebookEditorEdit {
/**
* Replace a cell range with a new cell.
*
* @param location The range this operation should remove.
* @param value The new cell this operation should insert after removing `location`.
*/
replace(location: number | CellRange, value: ICellContents): void;
/**
* Insert a cell (optionally) at a specific index. Any index outside of the length of the cells
* will result in the cell being added at the end.
*
* @param index The position where the new text should be inserted.
* @param value The new text this operation should insert.
*/
insertCell(value: ICellContents, index?: number): void;
/**
* Delete a certain cell.
*
* @param index The index of the cell to remove.
*/
deleteCell(index: number): void;
}
/**
* Register a notebook provider. The supported file types handled by this
* provider are defined in the `package.json:
* ```json
* {
* "contributes": {
* "notebook.providers": [{
* "provider": "providername",
* "fileExtensions": ["FILEEXT"]
* }]
* }
* }
* ```
* @export
* @param {NotebookProvider} provider
* @returns {vscode.Disposable}
*/
export function registerNotebookProvider(provider: NotebookProvider): vscode.Disposable; export function registerNotebookProvider(provider: NotebookProvider): vscode.Disposable;
export interface NotebookProvider { export interface NotebookProvider {
@@ -1431,7 +1713,7 @@ declare module 'sqlops' {
/* Reads contents from a Uri representing a local or remote notebook and returns a /* Reads contents from a Uri representing a local or remote notebook and returns a
* JSON object containing the cells and metadata about the notebook * JSON object containing the cells and metadata about the notebook
*/ */
getNotebookContents(notebookUri: vscode.Uri): Thenable<INotebook>; getNotebookContents(notebookUri: vscode.Uri): Thenable<INotebookContents>;
/** /**
* Save a file. * Save a file.
@@ -1443,12 +1725,19 @@ declare module 'sqlops' {
* @returns A thenable which resolves with the file content model when the * @returns A thenable which resolves with the file content model when the
* file is saved. * file is saved.
*/ */
save(notebookUri: vscode.Uri, notebook: INotebook): Thenable<INotebook>; save(notebookUri: vscode.Uri, notebook: INotebookContents): Thenable<INotebookContents>;
} }
export interface INotebook {
readonly cells: ICell[]; /**
* Interface defining the file format contents of a notebook, usually in a serializable
* format. This interface does not have any methods for manipulating or interacting
* with a notebook object.
*
*/
export interface INotebookContents {
readonly cells: ICellContents[];
readonly metadata: INotebookMetadata; readonly metadata: INotebookMetadata;
readonly nbformat: number; readonly nbformat: number;
readonly nbformat_minor: number; readonly nbformat_minor: number;
@@ -1477,7 +1766,13 @@ declare module 'sqlops' {
version: string; version: string;
} }
export interface ICell { /**
* Interface defining the file format contents of a notebook cell, usually in a serializable
* format. This interface does not have any methods for manipulating or interacting
* with a cell object.
*
*/
export interface ICellContents {
cell_type: CellType; cell_type: CellType;
source: string | string[]; source: string | string[];
metadata: { metadata: {

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