Compare commits

...

100 Commits

Author SHA1 Message Date
Leila Lali
9bf4a4b18c Added node selectdEvent (#2153)
* Added model view tree node selectedEvent
2018-08-06 13:19:37 -07:00
Karl Burtram
8a01553c49 Remove edit under vs directory from previous commit (#2154) 2018-08-06 15:32:15 -04:00
AlexFsmn
21b913845f Fixed bug where proper file extension wasn't appended to filename. (#2151)
#892
2018-08-06 14:54:58 -04:00
Karl Burtram
220e4feb1d Bump MIME module (#2148) 2018-08-03 21:24:26 -04:00
Matt Irvine
13884c0457 Support QueryInput when handling renamed files (#2146) 2018-08-03 21:12:31 -04:00
Matt Irvine
b3bb6ebc6e Use event element to determine whether to hide dropdown (#2145) 2018-08-03 21:12:18 -04:00
Matt Irvine
424eb90dd8 Save edit data scroll position when switching tabs (#2129) 2018-08-03 16:18:18 -07:00
Matt Irvine
df804d0729 Make sure chart viewer height gets set correctly (#2143) 2018-08-03 16:17:49 -07:00
Madeline MacDonald
79269cdfd5 Add session templates to profiler (#2115)
* Initial support for handling available sessions

* Displaying sessions in drop down, send session name in start profiling request

* More support for starting existing sessions

* New session dialog and session templates in user files

* Create profiler dialog and session templates

* Preliminary session template changes

* Saving some changes

* Send session templates when creating sessions

* Saving changes

* UI Fixes for dialog

* Formatting fixes

* removing comments

* Fixing PR comments

* bumping toolsservice and dataprotocolclient versions

* Fixing starting existing sessions
2018-08-03 15:24:50 -07:00
Matt Irvine
2a650d4d74 Do not disconnect editor when canceling change connection (#2144) 2018-08-03 15:22:11 -07:00
Aditya Bist
017b4ecdb3 fixed breaking bug in job history and fixed accordion style (#2138) 2018-08-03 13:04:47 -07:00
Matt Irvine
9c84bf3fd5 Fix directory name error exporting results multiple times (#2134) 2018-08-03 11:52:01 -07:00
Karl Burtram
397b54a8c3 Update dashboard, profiler and query icons (#2135) 2018-08-03 00:32:12 -04:00
Chris LaFreniere
cb3604c0a1 Add NodeLabel to TreeNodeContextKey (#2113) 2018-08-02 13:38:55 -07:00
Karl Burtram
1a7f0673ea Prevent the insights dialog from showing duplicate buttons (#2122) 2018-08-02 13:58:05 -04:00
Leila Lali
0d043207b9 Feature/tree component (#2077)
*added tree component to the model builder
2018-08-02 10:50:05 -07:00
Karl Burtram
f995dea971 Connect the editor for Script operations (#2123) 2018-08-02 13:48:47 -04:00
Sebastian Pfliegel
49e20488bc Added more saveAsCsv options (#2099)
Fixes #203
Options:
lineSeperator
textIdentifier
encoding
2018-08-01 22:51:31 -04:00
Karl Burtram
3e47b27192 Bump Tools Service to 1.5.20 (#2121)
This picks up a fix for https://github.com/Microsoft/sqlopsstudio/issues/2117
2018-08-01 21:05:30 -04:00
Karl Burtram
f4fa18ec05 Add GetConnectionString command (without build break) (#2120)
* Revert "Revert "Adds "Get Connection String" command (#2108)" (#2116)"

This reverts commit c6d1fa2b7d.

* Fix build breaks
2018-08-01 20:15:14 -04:00
Karl Burtram
c6d1fa2b7d Revert "Adds "Get Connection String" command (#2108)" (#2116)
This reverts commit c4df7667ff.
2018-08-01 19:00:24 -04:00
Karl Burtram
c4df7667ff Adds "Get Connection String" command (#2108)
* Initial connection string changes

* Add Get Connection String methods

* Add IncludePassword parameter

* Update Data Protocol and Tools Service versions

* Update dataprotocol to 0.2.2

* Try to update the dataprotocol component again

* Fix a few bugs related to getting the current editor connection uri
2018-08-01 18:44:54 -04:00
Karl Burtram
6a303cfa25 Bump tools service to 1.5.0-alpha.18 2018-08-01 00:17:49 -07:00
Karl Burtram
4ffa5cc1da Bump Server Reports extension to 0.1.3 (#2104) 2018-07-31 22:15:56 -04:00
Aditya Bist
172e1cf3bf put parse syntax in command palette instead of editor (#2103) 2018-07-31 16:27:11 -07:00
Karl Burtram
e6a32e52f5 Bump SQL Ops to 0.32.3 (#2088) 2018-07-30 18:06:44 -04:00
Matt Irvine
d2b6f6844d Add connection API method to get URI (#2021) 2018-07-30 10:52:24 -07:00
Matt Irvine
e9ef95ef1f Save query result selection/scroll when switching tabs (#2052) 2018-07-27 14:01:34 -07:00
Aditya Bist
10eeb5374f enabled button to import queries from sql files (#2042) 2018-07-27 13:44:20 -07:00
Karl Burtram
332951bc8e Pick-up newer version proxy node module (#2074) 2018-07-27 15:20:25 -04:00
Amir Ali Omidi
4daf3280ff Bring in all the extensibility updates we added during the hackathon (#2056)
Extensibility updates
2018-07-26 16:33:32 -07:00
Aditya Bist
87bb2c74d9 added .sql to associated files to sql ops (#2060) 2018-07-26 16:33:08 -07:00
Abbie Petchtes
ba011853a0 Add text editor component for model view (#2058)
* add text editor component for model view

* add comments
2018-07-26 15:52:33 -07:00
Aditya Bist
461a158ac3 bumped version for insider build (#2053) 2018-07-26 13:41:21 -07:00
Aditya Bist
85f59f1103 removed the export from defaultSort function (#2048) 2018-07-26 11:10:14 -07:00
Aditya Bist
335b9f445f Feature: Parse Query Syntax (#1997)
* feature/parseSyntax

* added error when no connection

* removed elapsed time logic

* code review comments

* changed error message to match SSMS
2018-07-25 16:11:58 -07:00
ranasaria
7cda45c904 fix for issue #1604 (#2029)
* Adding min height to .connection-dialog .tabbedPanel so that connection dialog add a vertical scroll bar appropriately when the ops studio window is vertically resized.
2018-07-25 12:31:17 -07:00
Anthony Dresser
4159fdc1a3 add off by one handler for selection (#2009) 2018-07-25 12:30:38 -07:00
Matt Irvine
190da30979 Update some Agent license headers (#2008) 2018-07-24 18:14:57 -07:00
Aditya Bist
ed9c74b900 localized the unlocalized strings (#2018) 2018-07-24 16:49:06 -07:00
Leila Lali
6783766c33 declarative table layout and option (#2007)
* added scroll bar to table content
2018-07-24 15:04:21 -07:00
Karl Burtram
b27018b379 Update SQL Tools to 1.5.0-alpha.14 2018-07-24 12:22:21 -07:00
Karl Burtram
021d07e04a Add VS Code version to product metadata (#1998) 2018-07-24 13:54:29 -04:00
Ian Y. Choi
bf0baec392 Fixes a typo: Mimunum -> Minimum (#1994)
(trivial)
2018-07-24 12:35:46 -04:00
Karl Burtram
923cbac400 Add // {{SQL CARBON EDIT}} for previous commit 2018-07-23 19:09:56 -07:00
Karl Burtram
ab938f2536 Remove @ from word separators (#1990) 2018-07-23 21:10:01 -04:00
Amir Ali Omidi
a55b1804e9 Tackles issue #1723 (#1988)
* Tackles issue #1723
2018-07-23 16:55:53 -07:00
Matt Irvine
4bfa6b3a5d Save editor cursor/scroll position when switching sql files (#1978)
* Save editor layouts when switching sql files

* Move import

* Restore the view state after laying out the editor
2018-07-20 10:48:12 -04:00
Anthony Dresser
d20f24be18 Bump slickgrid to fix html content issue (#1980)
* add ' to escape strings for html

* update slickgrid to fix injection
2018-07-20 10:47:16 -04:00
Matt Irvine
8a17bae7a6 Loading spinner while validating next/done (#1975) 2018-07-19 17:31:30 -07:00
Anthony Dresser
d14c73fad5 vbump service-downloader (#1965) 2018-07-19 14:06:41 -07:00
Anthony Dresser
ce878e1def Rework slickgrid keyboard navigation (#1930)
* rewrite keybind nav to handle ctrl + home and end

* testing different options

* working on removed slickgrid changes we don't need

* formatting

* handle click handler to rowNumber

* fixing various bugs

* formatting

* readd click column to select

* add shift key to column select

* added logic for additional keybindings on grid

* add down and up arrow into keyboard navigation

* update styling and update slickgrid

* formatting

* update angular-slickgrid version

* remove index.js changes
2018-07-19 14:05:54 -07:00
Karl Burtram
a64a0d1db6 Bump SQL Ops to 0.32.1 for August iteration 2018-07-19 11:43:32 -07:00
Anthony Dresser
feab43f16d add ' to escape strings for html (#1974) 2018-07-19 14:42:29 -04:00
Karl Burtram
0d60fe775f Update Readme and Changelog (#1968) 2018-07-19 01:21:06 -04:00
Leila Lali
6680be6a73 adding task integration with wizard and dialog framework (#1929)
* adding task integration with wizard and dialog framework
2018-07-18 16:28:36 -07:00
Karl Burtram
e026ab85a7 Escape the aria string for Edit Data grid (#1958) 2018-07-17 18:45:10 -07:00
Karl Burtram
e53c903205 Update tools service to 1.5.0-alpha.12 2018-07-17 18:44:45 -07:00
Matt Irvine
03dbe8565f Set element text instead of HTML where possible (#1956) 2018-07-17 16:48:38 -07:00
Karl Burtram
708793cb23 Update tools service to 1.5.0-alpha.11 2018-07-17 16:47:31 -07:00
Aditya Bist
43ae4fb0aa Fixed 2 bugs in Agent Steps page. (#1953)
* fixed bug where steps werent being shown

* fixed customer reported issue
2018-07-17 15:49:38 -07:00
Aditya Bist
6b1d552277 Agent/step finishes (#1948)
* misc fixes in dialogs

* removed unused import

* disabled more advanced options
2018-07-17 10:51:31 -07:00
Madeline MacDonald
f24f576b72 Profiler display fixes (#1949)
* Fixing details tab, window resizing, and having profiler options in object explorer

* Fixing displaying connection names

* spacing

* Removing unnecessary code
2018-07-16 17:20:13 -07:00
Matt Irvine
c23328564f Hide correct element when hiding buttons (#1945) 2018-07-16 17:09:44 -07:00
Karl Burtram
cd6dd3dafa Bump Tools Service to 1.5.0-alpha.10 (#1947) 2018-07-16 15:40:41 -07:00
Aditya Bist
4081e15bef misc fixes in dialogs (#1942)
* misc fixes in dialogs

* removed unused import
2018-07-16 15:25:11 -07:00
Karl Burtram
b05e3813d1 Bump product version to 0.31.4 from July Public Preview (#1944) 2018-07-16 15:13:10 -07:00
Karl Burtram
3048311f40 Bump agent extension version to 0.31.4 (#1943) 2018-07-16 15:12:54 -07:00
Madeline MacDonald
1045392d91 Escaping profiler text (#1940) 2018-07-16 13:20:56 -07:00
Madeline MacDonald
7b23ca8ee7 Improving profiler controls and toolbar (#1931)
* Profiler toolbar improvements

* Fixing formatting issues
2018-07-16 11:44:14 -07:00
Karl Burtram
4d67eca8bb Dashboard agent tab style updates (#1934) 2018-07-15 10:51:20 -07:00
Aditya Bist
74c4b7311e Agent/proxy ui (#1880)
* finished basic proxy ui

* finished proxy UI and logic

* made changes to accomodate toolsservice side changes
2018-07-14 10:43:31 -07:00
Karl Burtram
713c74adfd Update version to 0.31.3 2018-07-14 10:41:07 -07:00
Karl Burtram
408a8a6f19 Edit Agent Job dialog updates (#1925)
* Fill in job name and description on edit

* Hook up update job request call

* Clean up table styles
2018-07-13 22:57:09 -07:00
Karl Burtram
e1485e49d3 Pick up 1.5.0-alpha.9 2018-07-13 22:56:13 -07:00
Matt Irvine
30b66934cd Enable custom delimiters when saving as CSV (#1928)
* Support custom delimiters for csv

* Run tsfmt
2018-07-13 18:12:57 -07:00
Matt Irvine
fd49c081c2 Fix uses of innerHtml when we could just set element text (#1919) 2018-07-13 15:24:24 -07:00
Madeline MacDonald
1327120024 Profiler view templates (#1915)
* Initial view template framework

* Removing some templates, reordering drop down

* Fixing comments and formatting

* Adding issue reference for commented code
2018-07-13 14:24:49 -07:00
Matt Irvine
d2b5043972 Render column titles as text not html in query results (#1923) 2018-07-13 14:23:50 -07:00
Karl Burtram
a0e55ea3fd Update config.json 2018-07-13 12:12:08 -07:00
Matt Irvine
1f32de29c1 Style SQL input box correctly when enabled/disabled (#1920) 2018-07-13 09:03:22 -07:00
Aditya Bist
12be06d682 Agent: dialog finishes (#1913)
* fixed crashes from job dialog and new step dialog group options UI

* added placeholder for retry counters

* fixed alert general UI

* fixed misc dialog errors

* localized all strings

* fixed create operator UI
2018-07-12 19:43:59 -07:00
Kevin Cunnane
27ca9b13f8 Fix #1916 Object explorer context object doesn't include database name (#1917) 2018-07-12 17:21:57 -07:00
Matt Irvine
be45905830 Add wizard sidebar navigation (#1911) 2018-07-12 11:15:56 -07:00
Karl Burtram
05d0a89655 Fix Job History scroll and resize issues (#1912) 2018-07-12 09:32:43 -07:00
Aditya Bist
3ba575dcd0 Agent - dialog finishes (#1910)
* fixed crashes from job dialog and new step dialog group options UI

* added placeholder for retry counters

* fixed alert general UI

* fixed misc dialog errors

* localized all strings
2018-07-11 23:08:23 -07:00
Karl Burtram
3e200b7f0f Handle resize message in Agent dashboard tab (#1908)
* Resize related changes WIP

* Resize table control better on resize

* Update DashboardTab to use inherited Disposable

* Set forceFitColumns to false
2018-07-11 13:50:58 -07:00
Aditya Bist
cbce1f7008 added parse syntax params to sqlops (#1906) 2018-07-11 13:26:51 -07:00
Kevin Cunnane
e99101447e Fixes #1856 Object Explorer needs Icons field for nodes separate from… (#1901)
* Fixes #1856 Object Explorer needs Icons field for nodes separate from type/subtype
- Adds in the concept of a themeable icon path which matches VSCode's implementation. This should help support theme-based overrides in the future
2018-07-11 11:24:35 -07:00
Karl Burtram
0ddb326e44 Update SQL Tools Service to 1.5.0-alpha.6 (#1897) 2018-07-11 10:25:18 -07:00
Leila Lali
460446a15c updated the icon for form container help (#1892) 2018-07-10 15:53:01 -07:00
Aditya Bist
4eea24997f Agent: Updated Alerts dialog UI (#1874)
* finished alert dialog UI

* removed unused import
2018-07-10 14:21:58 -07:00
Matt Irvine
0b1e9c7c66 Update form layout defaults to match design (#1878) 2018-07-10 13:45:36 -07:00
Kevin Cunnane
d51a7a9eb7 Extensibility: Context menu support in Object Explorer (#1883)
- Fixes #1867 context menu should be extensible
- Added context keys to support "when" conditions on the new extensions
- Fixes issue where actions like New Query, scripting show up even if these are not valid for the provider type or object type
- Fixed node expansion bug where rapid connect / expand / disconnect could break the app (fix in ObjectExplorerService.onNodeExpanded)
- Major change to how internal actions work. These cannot assume the context has non-serializable objects. Opened up some APIs to make this easier to handle.
- Fixed a number of existing bugs in internal actions.
  - Notably, DisconnectAction was adding a listener on each right-click on an active connection and never getting it disposed. This wasn't needed at all due to design changes.
  - Another bug fix is that the Manage action now correctly navigates to the DB dashboard for database-level connections. Before this it went to the server-level dashboard.

* Define API for context info
2018-07-10 12:23:47 -07:00
Karl Burtram
0f0b959e14 Update config.json 2018-07-10 09:53:00 -07:00
Leila Lali
b2ceb09e4d fixed some issues in table component and added tests (#1873)
* fixed some issues in table component  and added tests

* fixed the enter key handling in text area component
2018-07-09 14:40:21 -07:00
Anthony Dresser
53953f5cda bump slickgrid to fix focus issue (#1875) 2018-07-09 13:30:16 -07:00
Karl Burtram
fbd5e819a2 Edit Agent Alert updates (#1872)
* Set database name and severity when editing alert

* Add Operator and Proxy edit

* Add edit job hookup

* Edit WIP

* Additional edit alert updates

* Remove unused method
2018-07-09 10:47:50 -07:00
Matt Irvine
bdc391d376 Fix enter button behavior for wizards and dialogs (#1868) 2018-07-06 17:35:28 -07:00
Madeline MacDonald
6b618fb121 Updating keybindings (#1839)
* Changing key combos, and behavior for starting/stopping

* Updating keybindings

* Fixing mac keybindings

* Clear data when starting profiler from keyboard shortcut
2018-07-06 14:53:31 -07:00
231 changed files with 6999 additions and 2012 deletions

View File

@@ -1,5 +1,26 @@
# Change Log # Change Log
## Version 0.31.4
* Release date: July 19, 2018
* Release status: Public Preview
## What's new in this version
* SQL Server Agent for SQL Operations Studio extension improvements
* Added view of Alerts, Operators, and Proxies and icons on left pane
* Added dialogs for New Job, New Job Step, New Alert, and New Operator
* Added Delete Job, Delete Alert, and Delete Operator (right-click)
* Added Previous Runs visualization
* Added Filters for each column name
* SQL Server Profiler for SQL Operations Studio extension improvements
* Added Hotkeys to quickly launch and start/stop Profiler
* Added 5 Default Templates to view Extended Events
* Added Server/Database connection name
* Added support for Azure SQL Database instances
* Added suggestion to exit Profiler when tab is closed when Profiler is still running
* Release of Combine Scripts Extension
* Wizard and Dialog Extensibility
* Fix GitHub Issues
## Version 0.30.6 ## Version 0.30.6
* Release date: June 20, 2018 * Release date: June 20, 2018
* Release status: Public Preview * Release status: Public Preview

View File

@@ -8,12 +8,12 @@ SQL Operations Studio is a data management tool that enables you to work with SQ
Platform | Link Platform | Link
-- | -- -- | --
Windows Setup Installer | https://go.microsoft.com/fwlink/?linkid=875602 Windows Setup Installer | https://go.microsoft.com/fwlink/?linkid=2005949
Windows ZIP | https://go.microsoft.com/fwlink/?linkid=875603 Windows ZIP | https://go.microsoft.com/fwlink/?linkid=2005950
macOS ZIP | https://go.microsoft.com/fwlink/?linkid=875604 macOS ZIP | https://go.microsoft.com/fwlink/?linkid=2005959
Linux TAR.GZ | https://go.microsoft.com/fwlink/?linkid=875605 Linux TAR.GZ | https://go.microsoft.com/fwlink/?linkid=2005960
Linux RPM | https://go.microsoft.com/fwlink/?linkid=875606 Linux RPM | https://go.microsoft.com/fwlink/?linkid=2006083
Linux DEB | https://go.microsoft.com/fwlink/?linkid=875607 Linux DEB | https://go.microsoft.com/fwlink/?linkid=2006084
Go to our [download page](https://aka.ms/sqlopsstudio) for more specific instructions. Go to our [download page](https://aka.ms/sqlopsstudio) for more specific instructions.
@@ -85,7 +85,7 @@ We would like to thank all our users who raised issues, and in particular the fo
* Chinese (Traditional): Bruce Chen, Chiayi Yen, Kevin Yang, Winnie Lin, 保哥 Will, 謝政廷 * Chinese (Traditional): Bruce Chen, Chiayi Yen, Kevin Yang, Winnie Lin, 保哥 Will, 謝政廷
* Korean: Do-Kyun Kim, Evelyn Kim, Helen Jung, Hong Jmee, jeongwoo choi, Jun Hyoung Lee, Jungsun Kim정선, Justin Yoo, Kavrith mucha, Kiwoong Youm, MinGyu Ju, MVP_JUNO BEA, Sejun Kim, SOONMAN KWON, sung man ko, Yeongrak Choi, younggun kim, Youngjae Kim, 소영 이 * Korean: Do-Kyun Kim, Evelyn Kim, Helen Jung, Hong Jmee, jeongwoo choi, Jun Hyoung Lee, Jungsun Kim정선, Justin Yoo, Kavrith mucha, Kiwoong Youm, MinGyu Ju, MVP_JUNO BEA, Sejun Kim, SOONMAN KWON, sung man ko, Yeongrak Choi, younggun kim, Youngjae Kim, 소영 이
* Russian: Andrey Veselov, Anton Fontanov, Anton Savin, Elena Ostrovskaia, Igor Babichev, Maxim Zelensky, Rodion Fedechkin, Tasha T, Vladimir Zyryanov * Russian: Andrey Veselov, Anton Fontanov, Anton Savin, Elena Ostrovskaia, Igor Babichev, Maxim Zelensky, Rodion Fedechkin, Tasha T, Vladimir Zyryanov
* Portuguese Brazil: Daniel de Sousa, Diogo Duarte, Douglas Correa, Douglas Eccker, José Emanuel Mendes, Marcelo Fernandes, Marcondes Alexandre, Roberto Fonseca, Rodrigo Crespi * Portuguese Brazil: Daniel de Sousa, Diogo Duarte, Douglas Correa, Douglas Eccker, José Emanuel Mendes, Marcelo Fernandes, Marcondes Alexandre, Roberto Fonseca, Rodrigo Crespi
And of course we'd like to thank the authors of all upstream dependencies. Please see a full list in the [ThirdPartyNotices.txt](https://raw.githubusercontent.com/Microsoft/sqlopsstudio/master/ThirdPartyNotices.txt) And of course we'd like to thank the authors of all upstream dependencies. Please see a full list in the [ThirdPartyNotices.txt](https://raw.githubusercontent.com/Microsoft/sqlopsstudio/master/ThirdPartyNotices.txt)

View File

@@ -61,6 +61,7 @@ Type: filesandordirs; Name: "{app}\_"
[Tasks] [Tasks]
Name: "quicklaunchicon"; Description: "{cm:CreateQuickLaunchIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked; OnlyBelowVersion: 0,6.1 Name: "quicklaunchicon"; Description: "{cm:CreateQuickLaunchIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked; OnlyBelowVersion: 0,6.1
Name: "associatewithfiles"; Description: "{cm:AssociateWithFiles,{#NameShort}}"; GroupDescription: "{cm:Other}"; Flags: unchecked
Name: "addtopath"; Description: "{cm:AddToPath}"; GroupDescription: "{cm:Other}" Name: "addtopath"; Description: "{cm:AddToPath}"; GroupDescription: "{cm:Other}"
Name: "runcode"; Description: "{cm:RunAfter,{#NameShort}}"; GroupDescription: "{cm:Other}"; Check: WizardSilent Name: "runcode"; Description: "{cm:RunAfter,{#NameShort}}"; GroupDescription: "{cm:Other}"; Check: WizardSilent
@@ -82,6 +83,13 @@ Root: HKCR; Subkey: "{#RegValueName}SourceFile\DefaultIcon"; ValueType: string;
Root: HKCR; Subkey: "{#RegValueName}SourceFile\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1""" Root: HKCR; Subkey: "{#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: HKCU; 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: HKCU; 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: HKCU; Subkey: "Software\Classes\{#RegValueName}.sql\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles
[Code] [Code]
// Don't allow installing conflicting architectures // Don't allow installing conflicting architectures
function InitializeSetup(): Boolean; function InitializeSetup(): Boolean;

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.31.1", "version": "0.31.4",
"publisher": "Microsoft", "publisher": "Microsoft",
"preview": true, "preview": true,
"license": "https://raw.githubusercontent.com/Microsoft/sqlopsstudio/master/LICENSE.txt", "license": "https://raw.githubusercontent.com/Microsoft/sqlopsstudio/master/LICENSE.txt",

View File

@@ -13,6 +13,11 @@ import { IAgentDialogData, AgentDialogMode } from '../interfaces';
const localize = nls.loadMessageBundle(); const localize = nls.loadMessageBundle();
export class AlertData implements IAgentDialogData { export class AlertData implements IAgentDialogData {
public static readonly AlertTypeSqlServerEventString: string = localize('alertData.DefaultAlertTypString', 'SQL Server event alert');
public static readonly AlertTypePerformanceConditionString: string = localize('alertDialog.PerformanceCondition', 'SQL Server performance condition alert');
public static readonly AlertTypeWmiEventString: string = localize('alertDialog.WmiEvent', 'WMI event alert');
public static readonly DefaultAlertTypeString: string = AlertData.AlertTypeSqlServerEventString;
ownerUri: string; ownerUri: string;
dialogMode: AgentDialogMode = AgentDialogMode.CREATE; dialogMode: AgentDialogMode = AgentDialogMode.CREATE;
id: number; id: number;
@@ -23,7 +28,7 @@ export class AlertData implements IAgentDialogData {
eventSource: string; eventSource: string;
hasNotification: number; hasNotification: number;
includeEventDescription: string; includeEventDescription: string;
isEnabled: boolean; isEnabled: boolean = true;
jobId: string; jobId: string;
jobName: string; jobName: string;
lastOccurrenceDate: string; lastOccurrenceDate: string;
@@ -36,7 +41,7 @@ export class AlertData implements IAgentDialogData {
databaseName: string; databaseName: string;
countResetDate: string; countResetDate: string;
categoryName: string; categoryName: string;
alertType: string; alertType: string = AlertData.DefaultAlertTypeString;
wmiEventNamespace: string; wmiEventNamespace: string;
wmiEventQuery: string; wmiEventQuery: string;
@@ -109,9 +114,19 @@ export class AlertData implements IAgentDialogData {
databaseName: this.databaseName, databaseName: this.databaseName,
countResetDate: this.countResetDate, countResetDate: this.countResetDate,
categoryName: this.categoryName, categoryName: this.categoryName,
alertType: sqlops.AlertType.sqlServerEvent, //this.alertType, alertType: AlertData.getAlertTypeFromString(this.alertType),
wmiEventNamespace: this.wmiEventNamespace, wmiEventNamespace: this.wmiEventNamespace,
wmiEventQuery: this.wmiEventQuery wmiEventQuery: this.wmiEventQuery
}; };
} }
private static getAlertTypeFromString(alertTypeString: string): sqlops.AlertType {
if (alertTypeString === AlertData.AlertTypePerformanceConditionString) {
return sqlops.AlertType.sqlServerPerformanceCondition;
} else if (alertTypeString === AlertData.AlertTypeWmiEventString) {
return sqlops.AlertType.wmiEvent;
} else {
return sqlops.AlertType.sqlServerEvent;
}
}
} }

View File

@@ -4,18 +4,22 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
'use strict'; 'use strict';
import * as nls from 'vscode-nls';
import * as sqlops from 'sqlops'; import * as sqlops from 'sqlops';
import * as vscode from 'vscode';
import { AgentUtils } from '../agentUtils'; import { AgentUtils } from '../agentUtils';
import { IAgentDialogData, AgentDialogMode } from '../interfaces'; import { IAgentDialogData, AgentDialogMode } from '../interfaces';
const localize = nls.loadMessageBundle();
export class JobData implements IAgentDialogData { export class JobData implements IAgentDialogData {
private readonly JobCompletionActionCondition_Always: string = 'When the job completes'; private readonly JobCompletionActionCondition_Always: string = localize('jobData.whenJobCompletes', 'When the job completes');
private readonly JobCompletionActionCondition_OnFailure: string = 'When the job fails'; private readonly JobCompletionActionCondition_OnFailure: string = localize('jobData.whenJobFails', 'When the job fails');
private readonly JobCompletionActionCondition_OnSuccess: string = 'When the job succeeds'; private readonly JobCompletionActionCondition_OnSuccess: string = localize('jobData.whenJobSucceeds', 'When the job succeeds');
// Error Messages // Error Messages
private readonly CreateJobErrorMessage_NameIsEmpty = 'Job name must be provided'; private readonly CreateJobErrorMessage_NameIsEmpty = localize('jobData.jobNameRequired', 'Job name must be provided');
private _ownerUri: string; private _ownerUri: string;
private _jobCategories: string[]; private _jobCategories: string[];
@@ -25,6 +29,7 @@ export class JobData implements IAgentDialogData {
public dialogMode: AgentDialogMode = AgentDialogMode.CREATE; public dialogMode: AgentDialogMode = AgentDialogMode.CREATE;
public name: string; public name: string;
public originalName: string;
public enabled: boolean = true; public enabled: boolean = true;
public description: string; public description: string;
public category: string; public category: string;
@@ -40,8 +45,21 @@ export class JobData implements IAgentDialogData {
public jobSchedules: sqlops.AgentJobScheduleInfo[]; public jobSchedules: sqlops.AgentJobScheduleInfo[];
public alerts: sqlops.AgentAlertInfo[]; public alerts: sqlops.AgentAlertInfo[];
constructor(ownerUri: string, private _agentService: sqlops.AgentServicesProvider = null) { constructor(
ownerUri: string,
jobInfo: sqlops.AgentJobInfo = undefined,
private _agentService: sqlops.AgentServicesProvider = undefined) {
this._ownerUri = ownerUri; this._ownerUri = ownerUri;
if (jobInfo) {
this.dialogMode = AgentDialogMode.EDIT;
this.name = jobInfo.name;
this.originalName = jobInfo.name;
this.owner = jobInfo.owner;
this.category = jobInfo.category;
this.description = jobInfo.description;
this.enabled = jobInfo.enabled;
}
} }
public get jobCategories(): string[] { public get jobCategories(): string[] {
@@ -92,7 +110,39 @@ export class JobData implements IAgentDialogData {
} }
public async save() { public async save() {
await this._agentService.createJob(this.ownerUri, { let jobInfo: sqlops.AgentJobInfo = this.toAgentJobInfo();
let result = this.dialogMode === AgentDialogMode.CREATE
? await this._agentService.createJob(this.ownerUri, jobInfo)
: await this._agentService.updateJob(this.ownerUri, this.originalName, jobInfo);
if (!result || !result.success) {
vscode.window.showErrorMessage(
localize('jobData.saveErrorMessage', "Job update failed '{0}'", result.errorMessage ? result.errorMessage : 'Unknown'));
}
}
public validate(): { valid: boolean, errorMessages: string[] } {
let validationErrors: string[] = [];
if (!(this.name && this.name.trim())) {
validationErrors.push(this.CreateJobErrorMessage_NameIsEmpty);
}
return {
valid: validationErrors.length === 0,
errorMessages: validationErrors
};
}
public addJobSchedule(schedule: sqlops.AgentJobScheduleInfo) {
let existingSchedule = this.jobSchedules.find(item => item.name === schedule.name);
if (!existingSchedule) {
this.jobSchedules.push(schedule);
}
}
public toAgentJobInfo(): sqlops.AgentJobInfo {
return {
name: this.name, name: this.name,
owner: this.owner, owner: this.owner,
description: this.description, description: this.description,
@@ -122,30 +172,6 @@ export class JobData implements IAgentDialogData {
lastRun: '', lastRun: '',
nextRun: '', nextRun: '',
jobId: '' jobId: ''
}).then(result => {
if (!result.success) {
console.info(result.errorMessage);
}
});
}
public validate(): { valid: boolean, errorMessages: string[] } {
let validationErrors: string[] = [];
if (!(this.name && this.name.trim())) {
validationErrors.push(this.CreateJobErrorMessage_NameIsEmpty);
}
return {
valid: validationErrors.length === 0,
errorMessages: validationErrors
}; };
} }
public addJobSchedule(schedule: sqlops.AgentJobScheduleInfo) {
let existingSchedule = this.jobSchedules.find(item => item.name === schedule.name);
if (!existingSchedule) {
this.jobSchedules.push(schedule);
}
}
} }

View File

@@ -29,8 +29,14 @@ export class OperatorData implements IAgentDialogData {
weekdayPagerStartTime: string; weekdayPagerStartTime: string;
weekdayPagerEndTime: string; weekdayPagerEndTime: string;
constructor(ownerUri:string) { constructor(ownerUri:string, operatorInfo: sqlops.AgentOperatorInfo) {
this.ownerUri = ownerUri; this.ownerUri = ownerUri;
if (operatorInfo) {
this.dialogMode = AgentDialogMode.EDIT;
this.name = operatorInfo.name;
this.enabled = operatorInfo.enabled;
}
} }
public async initialize() { public async initialize() {

View File

@@ -19,8 +19,14 @@ export class ProxyData implements IAgentDialogData {
credentialId: number; credentialId: number;
isEnabled: boolean; isEnabled: boolean;
constructor(ownerUri:string) { constructor(ownerUri:string, proxyInfo: sqlops.AgentProxyInfo) {
this.ownerUri = ownerUri; this.ownerUri = ownerUri;
if (proxyInfo) {
this.accountName = proxyInfo.accountName;
this.credentialName = proxyInfo.credentialName;
this.description = proxyInfo.description;
}
} }
public async initialize() { public async initialize() {

View File

@@ -10,6 +10,8 @@ import * as sqlops from 'sqlops';
import { AgentDialog } from './agentDialog'; import { AgentDialog } from './agentDialog';
import { AgentUtils } from '../agentUtils'; import { AgentUtils } from '../agentUtils';
import { AlertData } from '../data/alertData'; import { AlertData } from '../data/alertData';
import { OperatorDialog } from './operatorDialog';
import { JobDialog } from './jobDialog';
const localize = nls.loadMessageBundle(); const localize = nls.loadMessageBundle();
@@ -21,6 +23,7 @@ export class AlertDialog extends AgentDialog<AlertData> {
private static readonly GeneralTabText: string = localize('alertDialog.General', 'General'); private static readonly GeneralTabText: string = localize('alertDialog.General', 'General');
private static readonly ResponseTabText: string = localize('alertDialog.Response', 'Response'); private static readonly ResponseTabText: string = localize('alertDialog.Response', 'Response');
private static readonly OptionsTabText: string = localize('alertDialog.Options', 'Options'); private static readonly OptionsTabText: string = localize('alertDialog.Options', 'Options');
private static readonly EventAlertText: string = localize('alertDialog.eventAlert', 'Event alert definition');
// General tab strings // General tab strings
private static readonly NameLabel: string = localize('alertDialog.Name', 'Name'); private static readonly NameLabel: string = localize('alertDialog.Name', 'Name');
@@ -31,9 +34,6 @@ export class AlertDialog extends AgentDialog<AlertData> {
private static readonly SeverityLabel: string = localize('alertDialog.Severity', 'Severity'); private static readonly SeverityLabel: string = localize('alertDialog.Severity', 'Severity');
private static readonly RaiseIfMessageContainsLabel: string = localize('alertDialog.RaiseAlertContains', 'Raise alert when message contains'); private static readonly RaiseIfMessageContainsLabel: string = localize('alertDialog.RaiseAlertContains', 'Raise alert when message contains');
private static readonly MessageTextLabel: string = localize('alertDialog.MessageText', 'Message text'); private static readonly MessageTextLabel: string = localize('alertDialog.MessageText', 'Message text');
private static readonly AlertTypeSqlServerEventString: string = localize('alertDialog.SqlServerEventAlert', 'SQL Server event alert');
private static readonly AlertTypePerformanceConditionString: string = localize('alertDialog.PerformanceCondition', 'SQL Server performance condition alert');
private static readonly AlertTypeWmiEventString: string = localize('alertDialog.WmiEvent', 'WMI event alert');
private static readonly AlertSeverity001Label: string = localize('alertDialog.Severity001', '001 - Miscellaneous System Information'); private static readonly AlertSeverity001Label: string = localize('alertDialog.Severity001', '001 - Miscellaneous System Information');
private static readonly AlertSeverity002Label: string = localize('alertDialog.Severity002', '002 - Reserved'); private static readonly AlertSeverity002Label: string = localize('alertDialog.Severity002', '002 - Reserved');
private static readonly AlertSeverity003Label: string = localize('alertDialog.Severity003', '003 - Reserved'); private static readonly AlertSeverity003Label: string = localize('alertDialog.Severity003', '003 - Reserved');
@@ -59,11 +59,13 @@ export class AlertDialog extends AgentDialog<AlertData> {
private static readonly AlertSeverity023Label: string = localize('alertDialog.Severity023', '023 - Fatal Error: Database Integrity Suspect'); private static readonly AlertSeverity023Label: string = localize('alertDialog.Severity023', '023 - Fatal Error: Database Integrity Suspect');
private static readonly AlertSeverity024Label: string = localize('alertDialog.Severity024', '024 - Fatal Error: Hardware Error'); private static readonly AlertSeverity024Label: string = localize('alertDialog.Severity024', '024 - Fatal Error: Hardware Error');
private static readonly AlertSeverity025Label: string = localize('alertDialog.Severity025', '025 - Fatal Error'); private static readonly AlertSeverity025Label: string = localize('alertDialog.Severity025', '025 - Fatal Error');
private static readonly AllDatabases: string = localize('alertDialog.AllDatabases', '<all databases>');
private static readonly AlertTypes: string[] = [ private static readonly AlertTypes: string[] = [
AlertDialog.AlertTypeSqlServerEventString, AlertData.AlertTypeSqlServerEventString,
AlertDialog.AlertTypePerformanceConditionString, // Disabled until next release
AlertDialog.AlertTypeWmiEventString // AlertData.AlertTypePerformanceConditionString,
// AlertData.AlertTypeWmiEventString
]; ];
private static readonly AlertSeverities: string[] = [ private static readonly AlertSeverities: string[] = [
@@ -124,6 +126,10 @@ export class AlertDialog extends AgentDialog<AlertData> {
private severityDropDown: sqlops.DropDownComponent; private severityDropDown: sqlops.DropDownComponent;
private databaseDropDown: sqlops.DropDownComponent; private databaseDropDown: sqlops.DropDownComponent;
private enabledCheckBox: sqlops.CheckBoxComponent; private enabledCheckBox: sqlops.CheckBoxComponent;
private errorNumberRadioButton: sqlops.RadioButtonComponent;
private severityRadioButton: sqlops.RadioButtonComponent;
private errorNumberTextBox: sqlops.InputBoxComponent;
private raiseAlertMessageCheckBox: sqlops.CheckBoxComponent; private raiseAlertMessageCheckBox: sqlops.CheckBoxComponent;
private raiseAlertMessageTextBox: sqlops.InputBoxComponent; private raiseAlertMessageTextBox: sqlops.InputBoxComponent;
@@ -142,58 +148,115 @@ export class AlertDialog extends AgentDialog<AlertData> {
private delayMinutesTextBox: sqlops.InputBoxComponent; private delayMinutesTextBox: sqlops.InputBoxComponent;
private delaySecondsTextBox: sqlops.InputBoxComponent; private delaySecondsTextBox: sqlops.InputBoxComponent;
constructor(ownerUri: string, alertInfo: sqlops.AgentAlertInfo = null) { private jobs: string[];
private databases: string[];
constructor(ownerUri: string, alertInfo: sqlops.AgentAlertInfo = undefined, jobs: string[]) {
super(ownerUri, super(ownerUri,
new AlertData(ownerUri, alertInfo), new AlertData(ownerUri, alertInfo),
alertInfo ? AlertDialog.EditDialogTitle : AlertDialog.CreateDialogTitle); alertInfo ? AlertDialog.EditDialogTitle : AlertDialog.CreateDialogTitle);
this.jobs = jobs;
} }
protected async initializeDialog(dialog: sqlops.window.modelviewdialog.Dialog) { protected async initializeDialog(dialog: sqlops.window.modelviewdialog.Dialog) {
let databases = await AgentUtils.getDatabases(this.ownerUri); this.databases = await AgentUtils.getDatabases(this.ownerUri);
this.databases.unshift(AlertDialog.AllDatabases);
this.generalTab = sqlops.window.modelviewdialog.createTab(AlertDialog.GeneralTabText); this.generalTab = sqlops.window.modelviewdialog.createTab(AlertDialog.GeneralTabText);
this.responseTab = sqlops.window.modelviewdialog.createTab(AlertDialog.ResponseTabText); this.responseTab = sqlops.window.modelviewdialog.createTab(AlertDialog.ResponseTabText);
this.optionsTab = sqlops.window.modelviewdialog.createTab(AlertDialog.OptionsTabText); this.optionsTab = sqlops.window.modelviewdialog.createTab(AlertDialog.OptionsTabText);
this.initializeGeneralTab(databases); this.initializeGeneralTab(this.databases, dialog);
this.initializeResponseTab(); this.initializeResponseTab();
this.initializeOptionsTab(); this.initializeOptionsTab();
dialog.content = [this.generalTab, this.responseTab, this.optionsTab]; dialog.content = [this.generalTab, this.responseTab, this.optionsTab];
} }
private initializeGeneralTab(databases: string[]) { private initializeGeneralTab(databases: string[], dialog: sqlops.window.modelviewdialog.Dialog) {
this.generalTab.registerContent(async view => { this.generalTab.registerContent(async view => {
// create controls
this.nameTextBox = view.modelBuilder.inputBox().component(); this.nameTextBox = view.modelBuilder.inputBox().component();
this.nameTextBox.required = true;
this.nameTextBox.onTextChanged(() => {
if (this.nameTextBox.value.length > 0) {
dialog.okButton.enabled = true;
} else {
dialog.okButton.enabled = false;
}
});
this.enabledCheckBox = view.modelBuilder.checkBox() this.enabledCheckBox = view.modelBuilder.checkBox()
.withProperties({ .withProperties({
label: AlertDialog.EnabledCheckboxLabel label: AlertDialog.EnabledCheckboxLabel
}).component(); }).component();
this.enabledCheckBox.checked = true;
this.databaseDropDown = view.modelBuilder.dropDown() this.databaseDropDown = view.modelBuilder.dropDown()
.withProperties({ .withProperties({
value: databases[0], value: databases[0],
values: databases values: databases,
width: '100%'
}).component(); }).component();
this.typeDropDown = view.modelBuilder.dropDown() this.typeDropDown = view.modelBuilder.dropDown()
.withProperties({ .withProperties({
value: AlertDialog.AlertTypes[0], value: '',
values: AlertDialog.AlertTypes values: AlertDialog.AlertTypes,
width: '100%'
}).component(); }).component();
this.severityRadioButton = view.modelBuilder.radioButton()
.withProperties({
value: 'serverity',
name: 'alertTypeOptions',
label: AlertDialog.SeverityLabel,
checked: true
}).component();
this.severityRadioButton.checked = true;
this.severityDropDown = view.modelBuilder.dropDown() this.severityDropDown = view.modelBuilder.dropDown()
.withProperties({ .withProperties({
value: AlertDialog.AlertSeverities[0], value: AlertDialog.AlertSeverities[0],
values: AlertDialog.AlertSeverities values: AlertDialog.AlertSeverities,
width: '100%'
}).component(); }).component();
this.errorNumberRadioButton = view.modelBuilder.radioButton()
.withProperties({
value: 'errorNumber',
name: 'alertTypeOptions',
label: AlertDialog.ErrorNumberLabel
}).component();
this.errorNumberTextBox = view.modelBuilder.inputBox()
.withProperties({
width: '100%'
})
.component();
this.errorNumberTextBox.enabled = false;
this.errorNumberRadioButton.onDidClick(() => {
this.errorNumberTextBox.enabled = true;
this.severityDropDown.enabled = false;
});
this.severityRadioButton.onDidClick(() => {
this.errorNumberTextBox.enabled = false;
this.severityDropDown.enabled = true;
});
this.raiseAlertMessageCheckBox = view.modelBuilder.checkBox() this.raiseAlertMessageCheckBox = view.modelBuilder.checkBox()
.withProperties({ .withProperties({
label: AlertDialog.RaiseIfMessageContainsLabel label: AlertDialog.RaiseIfMessageContainsLabel
}).component(); }).component();
this.raiseAlertMessageTextBox = view.modelBuilder.inputBox().component(); this.raiseAlertMessageTextBox = view.modelBuilder.inputBox().component();
this.raiseAlertMessageTextBox.enabled = false;
this.raiseAlertMessageCheckBox.onChanged(() => {
this.raiseAlertMessageTextBox.enabled = this.raiseAlertMessageCheckBox.checked;
});
let formModel = view.modelBuilder.formContainer() let formModel = view.modelBuilder.formContainer()
.withFormItems([{ .withFormItems([{
@@ -206,24 +269,61 @@ export class AlertDialog extends AgentDialog<AlertData> {
component: this.typeDropDown, component: this.typeDropDown,
title: AlertDialog.TypeLabel title: AlertDialog.TypeLabel
}, { }, {
component: this.databaseDropDown, components: [{
title: AlertDialog.DatabaseLabel component: this.databaseDropDown,
}, { title: AlertDialog.DatabaseLabel
component: this.severityDropDown, },
title: AlertDialog.SeverityLabel {
}, { component: this.severityRadioButton,
component: this.raiseAlertMessageCheckBox, title: ''
title: '' },
}, { {
component: this.raiseAlertMessageTextBox, component: this.severityDropDown,
title: AlertDialog.MessageTextLabel title: ''
},
{
component: this.errorNumberRadioButton,
title: ''
},
{
component: this.errorNumberTextBox,
title: ''
},
{
component: this.raiseAlertMessageCheckBox,
title: ''
}, {
component: this.raiseAlertMessageTextBox,
title: AlertDialog.MessageTextLabel
}],
title: AlertDialog.EventAlertText
} }
]).withLayout({ width: '100%' }).component(); ]).withLayout({ width: '100%' }).component();
await view.initializeModel(formModel); await view.initializeModel(formModel);
// initialize control values
this.nameTextBox.value = this.model.name; this.nameTextBox.value = this.model.name;
this.raiseAlertMessageTextBox.value = this.model.eventDescriptionKeyword;
this.typeDropDown.value = this.model.alertType;
this.enabledCheckBox.checked = this.model.isEnabled; this.enabledCheckBox.checked = this.model.isEnabled;
if (this.model.messageId > 0) {
this.errorNumberRadioButton.checked = true;
this.errorNumberTextBox.value = this.model.messageId.toString();
}
if (this.model.severity > 0) {
this.severityRadioButton.checked = true;
this.severityDropDown.value = this.severityDropDown.values[this.model.severity-1];
}
if (this.model.databaseName) {
let idx = this.databases.indexOf(this.model.databaseName);
if (idx >= 0) {
this.databaseDropDown.value = this.databases[idx];
}
}
}); });
} }
@@ -234,12 +334,38 @@ export class AlertDialog extends AgentDialog<AlertData> {
label: AlertDialog.ExecuteJobCheckBoxLabel label: AlertDialog.ExecuteJobCheckBoxLabel
}).component(); }).component();
this.executeJobTextBox = view.modelBuilder.inputBox().component(); this.executeJobTextBox = view.modelBuilder.inputBox()
.withProperties({ width: 375 })
.component();
this.executeJobTextBox.enabled = false;
this.newJobButton = view.modelBuilder.button().withProperties({ this.newJobButton = view.modelBuilder.button().withProperties({
label: AlertDialog.NewJobButtonLabel, label: AlertDialog.NewJobButtonLabel,
width: 80 width: 80
}).component(); }).component();
this.newJobButton.enabled = false;
this.newJobButton.onDidClick(() => {
let jobDialog = new JobDialog(this.ownerUri);
jobDialog.openDialog();
});
this.executeJobCheckBox.onChanged(() => {
if (this.executeJobCheckBox.checked) {
this.executeJobTextBox.enabled = true;
this.newJobButton.enabled = true;
} else {
this.executeJobTextBox.enabled = false;
this.newJobButton.enabled = false;
}
});
let executeJobContainer = view.modelBuilder.formContainer()
.withFormItems([{
component: this.executeJobTextBox,
title: AlertDialog.ExecuteJobTextBoxLabel
}, {
component: this.newJobButton,
title: AlertDialog.NewJobButtonLabel
}], { componentWidth: '100%'}).component();
this.notifyOperatorsCheckBox = view.modelBuilder.checkBox() this.notifyOperatorsCheckBox = view.modelBuilder.checkBox()
.withProperties({ .withProperties({
@@ -254,32 +380,57 @@ export class AlertDialog extends AgentDialog<AlertData> {
AlertDialog.OperatorPagerColumnLabel AlertDialog.OperatorPagerColumnLabel
], ],
data: [], data: [],
height: 500 height: 500,
width: 375
}).component(); }).component();
this.newOperatorButton = view.modelBuilder.button().withProperties({ this.newOperatorButton = view.modelBuilder.button().withProperties({
label: this.newOperatorButton, label: AlertDialog.NewOperatorButtonLabel,
width: 80 width: 80
}).component(); }).component();
this.operatorsTable.enabled = false;
this.newOperatorButton.enabled = false;
this.newOperatorButton.onDidClick(() => {
let operatorDialog = new OperatorDialog(this.ownerUri);
operatorDialog.openDialog();
});
this.notifyOperatorsCheckBox.onChanged(() => {
if (this.notifyOperatorsCheckBox.checked) {
this.operatorsTable.enabled = true;
this.newOperatorButton.enabled = true;
} else {
this.operatorsTable.enabled = false;
this.newOperatorButton.enabled = false;
}
});
let notifyOperatorContainer = view.modelBuilder.formContainer()
.withFormItems([{
component: this.operatorsTable,
title: AlertDialog.OperatorListLabel
}, {
component: this.newOperatorButton,
title: ''
}], { componentWidth: '100%'}).component();
let formModel = view.modelBuilder.formContainer() let formModel = view.modelBuilder.formContainer()
.withFormItems([{ .withFormItems([{
component: this.executeJobCheckBox, component: this.executeJobCheckBox,
title: '' title: ''
}, { }, {
component: this.executeJobTextBox, component: executeJobContainer,
title: AlertDialog.ExecuteJobTextBoxLabel title: ''
}, {
component: this.newJobButton,
title: AlertDialog.NewJobButtonLabel
}, { }, {
component: this.notifyOperatorsCheckBox, component: this.notifyOperatorsCheckBox,
title: '' title: ''
}, { }, {
component: this.operatorsTable, component: notifyOperatorContainer,
title: AlertDialog.OperatorListLabel, title: ''
actions: [this.newOperatorButton] }])
}]).withLayout({ width: '100%' }).component(); .withLayout({ width: '100%' }).component();
await view.initializeModel(formModel); await view.initializeModel(formModel);
}); });
@@ -300,9 +451,19 @@ export class AlertDialog extends AgentDialog<AlertData> {
this.additionalMessageTextBox = view.modelBuilder.inputBox().component(); this.additionalMessageTextBox = view.modelBuilder.inputBox().component();
this.delayMinutesTextBox = view.modelBuilder.inputBox().component(); this.delayMinutesTextBox = view.modelBuilder.inputBox()
.withProperties({
inputType: 'number',
placeHolder: 0
})
.component();
this.delaySecondsTextBox = view.modelBuilder.inputBox().component(); this.delaySecondsTextBox = view.modelBuilder.inputBox()
.withProperties({
inputType: 'number',
placeHolder: 0
})
.component();
let formModel = view.modelBuilder.formContainer() let formModel = view.modelBuilder.formContainer()
.withFormItems([{ .withFormItems([{
@@ -343,13 +504,25 @@ export class AlertDialog extends AgentDialog<AlertData> {
this.model.isEnabled = this.enabledCheckBox.checked; this.model.isEnabled = this.enabledCheckBox.checked;
this.model.alertType = this.getDropdownValue(this.typeDropDown); this.model.alertType = this.getDropdownValue(this.typeDropDown);
this.model.databaseName = this.getDropdownValue(this.databaseDropDown); let databaseName = this.getDropdownValue(this.databaseDropDown);
this.model.severity = this.getSeverityNumber(); this.model.databaseName = (databaseName !== AlertDialog.AllDatabases) ? databaseName : undefined;
this.model.messageId = undefined;
let raiseIfError = this.raiseAlertMessageCheckBox.checked; if (this.severityRadioButton.checked) {
if (raiseIfError) { this.model.severity = this.getSeverityNumber();
let messageText = this.raiseAlertMessageTextBox.value; this.model.messageId = 0;
} else {
this.model.severity = 0;
this.model.messageId = +this.errorNumberTextBox.value;
} }
if (this.raiseAlertMessageCheckBox.checked) {
this.model.eventDescriptionKeyword = this.raiseAlertMessageTextBox.value;
} else {
this.model.eventDescriptionKeyword = '';
}
let minutes = this.delayMinutesTextBox.value ? +this.delayMinutesTextBox.value : 0;
let seconds = this.delaySecondsTextBox.value ? +this.delaySecondsTextBox : 0;
this.model.delayBetweenResponses = minutes + seconds;
} }
} }

View File

@@ -3,6 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
'use strict'; 'use strict';
import * as nls from 'vscode-nls';
import * as sqlops from 'sqlops'; import * as sqlops from 'sqlops';
import { JobData } from '../data/jobData'; import { JobData } from '../data/jobData';
import { JobStepDialog } from './jobStepDialog'; import { JobStepDialog } from './jobStepDialog';
@@ -10,50 +11,57 @@ import { PickScheduleDialog } from './pickScheduleDialog';
import { AlertDialog } from './alertDialog'; import { AlertDialog } from './alertDialog';
import { AgentDialog } from './agentDialog'; import { AgentDialog } from './agentDialog';
const localize = nls.loadMessageBundle();
export class JobDialog extends AgentDialog<JobData> { export class JobDialog extends AgentDialog<JobData> {
// TODO: localize // TODO: localize
// Top level // Top level
private static readonly DialogTitle: string = 'New Job'; private static readonly CreateDialogTitle: string = localize('jobDialog.newJob', 'New Job');
private readonly GeneralTabText: string = 'General'; private static readonly EditDialogTitle: string = localize('jobDialog.editJob', 'Edit Job');
private readonly StepsTabText: string = 'Steps'; private readonly GeneralTabText: string = localize('jobDialog.general', 'General');
private readonly SchedulesTabText: string = 'Schedules'; private readonly StepsTabText: string = localize('jobDialog.steps', 'Steps');
private readonly AlertsTabText: string = 'Alerts'; private readonly SchedulesTabText: string = localize('jobDialog.schedules', 'Schedules');
private readonly NotificationsTabText: string = 'Notifications'; private readonly AlertsTabText: string = localize('jobDialog.alerts', 'Alerts');
private readonly NotificationsTabText: string = localize('jobDialog.notifications', 'Notifications');
private readonly BlankJobNameErrorText: string = localize('jobDialog.blankJobNameError', 'The name of the job cannot be blank.');
// General tab strings // General tab strings
private readonly NameTextBoxLabel: string = 'Name'; private readonly NameTextBoxLabel: string = localize('jobDialog.name', 'Name');
private readonly OwnerTextBoxLabel: string = 'Owner'; private readonly OwnerTextBoxLabel: string = localize('jobDialog.owner', 'Owner');
private readonly CategoryDropdownLabel: string = 'Category'; private readonly CategoryDropdownLabel: string = localize('jobDialog.category', 'Category');
private readonly DescriptionTextBoxLabel: string = 'Description'; private readonly DescriptionTextBoxLabel: string = localize('jobDialog.description', 'Description');
private readonly EnabledCheckboxLabel: string = 'Enabled'; private readonly EnabledCheckboxLabel: string = localize('jobDialog.enabled', 'Enabled');
// Steps tab strings // Steps tab strings
private readonly JobStepsTopLabelString: string = 'Job step list'; private readonly JobStepsTopLabelString: string = localize('jobDialog.jobStepList', 'Job step list');
private readonly StepsTable_StepColumnString: string = 'Step'; private readonly StepsTable_StepColumnString: string = localize('jobDialog.step', 'Step');
private readonly StepsTable_NameColumnString: string = 'Name'; private readonly StepsTable_NameColumnString: string = localize('jobDialog.name', 'Name');
private readonly StepsTable_TypeColumnString: string = 'Type'; private readonly StepsTable_TypeColumnString: string = localize('jobDialog.type', 'Type');
private readonly StepsTable_SuccessColumnString: string = 'On Success'; private readonly StepsTable_SuccessColumnString: string = localize('jobDialog.onSuccess', 'On Success');
private readonly StepsTable_FailureColumnString: string = 'On Failure'; private readonly StepsTable_FailureColumnString: string = localize('jobDialog.onFailure', 'On Failure');
private readonly NewStepButtonString: string = 'New...'; private readonly NewStepButtonString: string = localize('jobDialog.new', 'New...');
private readonly InsertStepButtonString: string = 'Insert...'; private readonly EditStepButtonString: string = localize('jobDialog.edit', 'Edit');
private readonly EditStepButtonString: string = 'Edit'; private readonly DeleteStepButtonString: string = localize('jobDialog.delete', 'Delete');
private readonly DeleteStepButtonString: string = 'Delete'; private readonly MoveStepUpButtonString: string = localize('jobDialog.moveUp', 'Move Step Up');
private readonly MoveStepDownButtonString: string = localize('jobDialog.moveDown', 'Move Step Up');
// Notifications tab strings // Notifications tab strings
private readonly NotificationsTabTopLabelString: string = 'Actions to perform when the job completes'; private readonly NotificationsTabTopLabelString: string = localize('jobDialog.notificationsTabTop', 'Actions to perform when the job completes');
private readonly EmailCheckBoxString: string = 'Email'; private readonly EmailCheckBoxString: string = localize('jobDialog.email', 'Email');
private readonly PagerCheckBoxString: string = 'Page'; private readonly PagerCheckBoxString: string = localize('jobDialog.page', 'Page');
private readonly EventLogCheckBoxString: string = 'Write to the Windows Application event log'; private readonly EventLogCheckBoxString: string = localize('jobDialog.eventLogCheckBoxLabel', 'Write to the Windows Application event log');
private readonly DeleteJobCheckBoxString: string = 'Automatically delete job'; private readonly DeleteJobCheckBoxString: string = localize('jobDialog.deleteJobLabel', 'Automatically delete job');
// Schedules tab strings // Schedules tab strings
private readonly SchedulesTopLabelString: string = 'Schedules list'; private readonly SchedulesTopLabelString: string = localize('jobDialog.schedulesaLabel', 'Schedules list');
private readonly PickScheduleButtonString: string = 'Pick Schedule'; private readonly PickScheduleButtonString: string = localize('jobDialog.pickSchedule', 'Pick Schedule');
private readonly ScheduleNameLabelString: string = localize('jobDialog.scheduleNameLabel', 'Schedule Name');
// Alerts tab strings // Alerts tab strings
private readonly AlertsTopLabelString: string = 'Alerts list'; private readonly AlertsTopLabelString: string = localize('jobDialog.alertsList', 'Alerts list');
private readonly NewAlertButtonString: string = 'New Alert'; private readonly NewAlertButtonString: string = localize('jobDialog.newAlert', 'New Alert');
private readonly AlertNameLabelString: string = localize('jobDialog.alertNameLabel', 'Alert Name');
// UI Components // UI Components
private generalTab: sqlops.window.modelviewdialog.DialogTab; private generalTab: sqlops.window.modelviewdialog.DialogTab;
@@ -72,7 +80,8 @@ export class JobDialog extends AgentDialog<JobData> {
// Steps tab controls // Steps tab controls
private stepsTable: sqlops.TableComponent; private stepsTable: sqlops.TableComponent;
private newStepButton: sqlops.ButtonComponent; private newStepButton: sqlops.ButtonComponent;
private insertStepButton: sqlops.ButtonComponent; private moveStepUpButton: sqlops.ButtonComponent;
private moveStepDownButton: sqlops.ButtonComponent;
private editStepButton: sqlops.ButtonComponent; private editStepButton: sqlops.ButtonComponent;
private deleteStepButton: sqlops.ButtonComponent; private deleteStepButton: sqlops.ButtonComponent;
@@ -97,8 +106,11 @@ export class JobDialog extends AgentDialog<JobData> {
private alertsTable: sqlops.TableComponent; private alertsTable: sqlops.TableComponent;
private newAlertButton: sqlops.ButtonComponent; private newAlertButton: sqlops.ButtonComponent;
constructor(ownerUri: string) { constructor(ownerUri: string, jobInfo: sqlops.AgentJobInfo = undefined) {
super(ownerUri, new JobData(ownerUri), JobDialog.DialogTitle); super(
ownerUri,
new JobData(ownerUri, jobInfo),
jobInfo ? JobDialog.EditDialogTitle : JobDialog.CreateDialogTitle);
} }
protected async initializeDialog() { protected async initializeDialog() {
@@ -129,6 +141,12 @@ export class JobDialog extends AgentDialog<JobData> {
private initializeGeneralTab() { private initializeGeneralTab() {
this.generalTab.registerContent(async view => { this.generalTab.registerContent(async view => {
this.nameTextBox = view.modelBuilder.inputBox().component(); this.nameTextBox = view.modelBuilder.inputBox().component();
this.nameTextBox.required = true;
this.nameTextBox.onTextChanged(() => {
if (this.nameTextBox.value && this.nameTextBox.value.length > 0) {
this.dialog.message = null;
}
});
this.ownerTextBox = view.modelBuilder.inputBox().component(); this.ownerTextBox = view.modelBuilder.inputBox().component();
this.categoryDropdown = view.modelBuilder.dropDown().component(); this.categoryDropdown = view.modelBuilder.dropDown().component();
this.descriptionTextBox = view.modelBuilder.inputBox().withProperties({ this.descriptionTextBox = view.modelBuilder.inputBox().withProperties({
@@ -159,11 +177,18 @@ export class JobDialog extends AgentDialog<JobData> {
await view.initializeModel(formModel); await view.initializeModel(formModel);
this.nameTextBox.value = this.model.name;
this.ownerTextBox.value = this.model.defaultOwner; this.ownerTextBox.value = this.model.defaultOwner;
this.categoryDropdown.values = this.model.jobCategories; this.categoryDropdown.values = this.model.jobCategories;
this.categoryDropdown.value = this.model.jobCategories[0];
let idx: number = undefined;
if (this.model.category && this.model.category !== '') {
idx = this.model.jobCategories.indexOf(this.model.category);
}
this.categoryDropdown.value = this.model.jobCategories[idx > 0 ? idx : 0];
this.enabledCheckBox.checked = this.model.enabled; this.enabledCheckBox.checked = this.model.enabled;
this.descriptionTextBox.value = ''; this.descriptionTextBox.value = this.model.description;
}); });
} }
@@ -179,24 +204,38 @@ export class JobDialog extends AgentDialog<JobData> {
this.StepsTable_FailureColumnString this.StepsTable_FailureColumnString
], ],
data: [], data: [],
height: 800 height: 430
}).component(); }).component();
this.moveStepUpButton = view.modelBuilder.button()
.withProperties({
label: this.MoveStepUpButtonString,
width: 80
}).component();
this.moveStepDownButton = view.modelBuilder.button()
.withProperties({
label: this.MoveStepDownButtonString,
width: 80
}).component();
this.moveStepUpButton.enabled = false;
this.moveStepDownButton.enabled = false;
this.newStepButton = view.modelBuilder.button().withProperties({ this.newStepButton = view.modelBuilder.button().withProperties({
label: this.NewStepButtonString, label: this.NewStepButtonString,
width: 80 width: 80
}).component(); }).component();
this.newStepButton.onDidClick((e)=>{ this.newStepButton.onDidClick((e)=>{
let stepDialog = new JobStepDialog(this.model.ownerUri, '', '', 1, this.model); if (this.nameTextBox.value && this.nameTextBox.value.length > 0) {
stepDialog.openNewStepDialog(); let stepDialog = new JobStepDialog(this.model.ownerUri, this.nameTextBox.value, '' , 1, this.model);
stepDialog.openNewStepDialog();
} else {
this.dialog.message = { text: this.BlankJobNameErrorText };
}
}); });
this.insertStepButton = view.modelBuilder.button().withProperties({
label: this.InsertStepButtonString,
width: 80
}).component();
this.editStepButton = view.modelBuilder.button().withProperties({ this.editStepButton = view.modelBuilder.button().withProperties({
label: this.EditStepButtonString, label: this.EditStepButtonString,
width: 80 width: 80
@@ -211,7 +250,7 @@ export class JobDialog extends AgentDialog<JobData> {
.withFormItems([{ .withFormItems([{
component: this.stepsTable, component: this.stepsTable,
title: this.JobStepsTopLabelString, title: this.JobStepsTopLabelString,
actions: [this.newStepButton, this.insertStepButton, this.editStepButton, this.deleteStepButton] actions: [this.moveStepUpButton, this.moveStepDownButton, this.newStepButton, this.editStepButton, this.deleteStepButton]
}]).withLayout({ width: '100%' }).component(); }]).withLayout({ width: '100%' }).component();
await view.initializeModel(formModel); await view.initializeModel(formModel);
}); });
@@ -222,10 +261,10 @@ export class JobDialog extends AgentDialog<JobData> {
this.alertsTable = view.modelBuilder.table() this.alertsTable = view.modelBuilder.table()
.withProperties({ .withProperties({
columns: [ columns: [
'Alert Name' this.AlertNameLabelString
], ],
data: [], data: [],
height: 600, height: 430,
width: 400 width: 400
}).component(); }).component();
@@ -235,7 +274,7 @@ export class JobDialog extends AgentDialog<JobData> {
}).component(); }).component();
this.newAlertButton.onDidClick((e)=>{ this.newAlertButton.onDidClick((e)=>{
let alertDialog = new AlertDialog(this.model.ownerUri); let alertDialog = new AlertDialog(this.model.ownerUri, null, []);
alertDialog.onSuccess((dialogModel) => { alertDialog.onSuccess((dialogModel) => {
}); });
alertDialog.openDialog(); alertDialog.openDialog();
@@ -257,11 +296,11 @@ export class JobDialog extends AgentDialog<JobData> {
this.schedulesTable = view.modelBuilder.table() this.schedulesTable = view.modelBuilder.table()
.withProperties({ .withProperties({
columns: [ columns: [
'Schedule Name' this.ScheduleNameLabelString
], ],
data: [], data: [],
height: 600, height: 430,
width: 400 width: 420
}).component(); }).component();
this.pickScheduleButton = view.modelBuilder.button().withProperties({ this.pickScheduleButton = view.modelBuilder.button().withProperties({
@@ -361,21 +400,23 @@ export class JobDialog extends AgentDialog<JobData> {
let formModel = view.modelBuilder.formContainer().withFormItems([ let formModel = view.modelBuilder.formContainer().withFormItems([
{ {
component: this.notificationsTabTopLabel, components:
title: '' [{
}, { component: emailContainer,
component: emailContainer, title: ''
title: '' },
}, { {
component: pagerContainer, component: pagerContainer,
title: '' title: ''
}, { },
component: eventLogContainer, {
title: '' component: eventLogContainer,
}, { title: ''
component: deleteJobContainer, },
title: '' {
}]).withLayout({ width: '100%' }).component(); component: deleteJobContainer,
title: ''
}], title: this.NotificationsTabTopLabelString}]).withLayout({ width: '100%' }).component();
await view.initializeModel(formModel); await view.initializeModel(formModel);
this.emailConditionDropdown.values = this.model.JobCompletionActionConditions; this.emailConditionDropdown.values = this.model.JobCompletionActionConditions;

View File

@@ -3,6 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
'use strict'; 'use strict';
import * as nls from 'vscode-nls';
import * as sqlops from 'sqlops'; import * as sqlops from 'sqlops';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import { JobStepData } from '../data/jobStepData'; import { JobStepData } from '../data/jobStepData';
@@ -10,31 +11,58 @@ import { AgentUtils } from '../agentUtils';
import { JobData } from '../data/jobData'; import { JobData } from '../data/jobData';
const path = require('path'); const path = require('path');
const localize = nls.loadMessageBundle();
export class JobStepDialog { export class JobStepDialog {
// TODO: localize // TODO: localize
// Top level // Top level
// //
private static readonly DialogTitle: string = 'New Job Step'; private readonly DialogTitle: string = localize('jobStepDialog.newJobStep', 'New Job Step');
private static readonly FileBrowserDialogTitle: string = 'Locate Database Files - '; private readonly FileBrowserDialogTitle: string = localize('jobStepDialog.fileBrowserTitle', 'Locate Database Files - ');
private static readonly OkButtonText: string = 'OK'; private readonly OkButtonText: string = localize('jobStepDialog.ok', 'OK');
private static readonly CancelButtonText: string = 'Cancel'; private readonly CancelButtonText: string = localize('jobStepDialog.cancel', 'Cancel');
private static readonly GeneralTabText: string = 'General'; private readonly GeneralTabText: string = localize('jobStepDialog.general', 'General');
private static readonly AdvancedTabText: string = 'Advanced'; private readonly AdvancedTabText: string = localize('jobStepDialog.advanced', 'Advanced');
private static readonly OpenCommandText: string = 'Open...'; private readonly OpenCommandText: string = localize('jobStepDialog.open', 'Open...');
private static readonly ParseCommandText: string = 'Parse'; private readonly ParseCommandText: string = localize('jobStepDialog.parse','Parse');
private static readonly NextButtonText: string = 'Next'; private readonly NextButtonText: string = localize('jobStepDialog.next', 'Next');
private static readonly PreviousButtonText: string = 'Previous'; private readonly PreviousButtonText: string = localize('jobStepDialog.previous','Previous');
private static readonly SuccessAction: string = 'On success action'; private readonly SuccessfulParseText: string = localize('jobStepDialog.successParse', 'The command was successfully parsed.');
private static readonly FailureAction: string = 'On failure action'; private readonly FailureParseText: string = localize('jobStepDialog.failParse', 'The command failed.');
private readonly BlankStepNameErrorText: string = localize('jobStepDialog.blankStepName', 'The step name cannot be left blank');
// General Control Titles
private readonly StepNameLabelString: string = localize('jobStepDialog.stepNameLabel', 'Step Name');
private readonly TypeLabelString: string = localize('jobStepDialog.typeLabel', 'Type');
private readonly RunAsLabelString: string = localize('jobStepDialog.runAsLabel', 'Run as');
private readonly DatabaseLabelString: string = localize('jobStepDialog.databaseLabel', 'Database');
private readonly CommandLabelString: string = localize('jobStepDialog.commandLabel', 'Command');
// Advanced Control Titles
private readonly SuccessActionLabel: string = localize('jobStepDialog.successAction', 'On success action');
private readonly FailureActionLabel: string = localize('jobStepDialog.failureAction', 'On failure action');
private readonly RunAsUserLabel: string = localize('jobStepDialog.runAsUser', 'Run as user');
private readonly RetryAttemptsLabel: string = localize('jobStepDialog.retryAttempts', 'Retry Attempts');
private readonly RetryIntervalLabel: string = localize('jobStepDialog.retryInterval', 'Retry Interval (minutes)');
private readonly LogToTableLabel: string = localize('jobStepDialog.logToTable', 'Log to table');
private readonly AppendExistingTableEntryLabel: string = localize('jobStepDialog.appendExistingTableEntry', 'Append output to exisiting entry in table');
private readonly IncludeStepOutputHistoryLabel: string = localize('jobStepDialog.includeStepOutputHistory', 'Include step output in history');
private readonly OutputFileNameLabel: string = localize('jobStepDialog.outputFile', 'Output File');
private readonly AppendOutputToFileLabel: string = localize('jobStepDialog.appendOutputToFile', 'Append output to existing file');
// File Browser Control Titles
private readonly SelectedPathLabelString: string = localize('jobStepDialog.selectedPath', 'Selected path');
private readonly FilesOfTypeLabelString: string = localize('jobStepDialog.filesOfType', 'Files of type');
private readonly FileNameLabelString: string = localize('jobStepDialog.fileName', 'File name');
private readonly AllFilesLabelString: string = localize('jobStepDialog.allFiles', 'All Files (*)');
// Dropdown options // Dropdown options
private static readonly TSQLScript: string = 'Transact-SQL script (T-SQL)'; private readonly TSQLScript: string = localize('jobStepDialog.TSQL', 'Transact-SQL script (T-SQL)');
private static readonly AgentServiceAccount: string = 'SQL Server Agent Service Account'; private readonly AgentServiceAccount: string = localize('jobStepDialog.agentServiceAccount', 'SQL Server Agent Service Account');
private static readonly NextStep: string = 'Go to the next step'; private readonly NextStep: string = localize('jobStepDialog.nextStep', 'Go to the next step');
private static readonly QuitJobReportingSuccess: string = 'Quit the job reporting success'; private readonly QuitJobReportingSuccess: string = localize('jobStepDialog.quitJobSuccess', 'Quit the job reporting success');
private static readonly QuitJobReportingFailure: string = 'Quit the job reporting failure'; private readonly QuitJobReportingFailure: string = localize('jobStepDialog.quitJobFailure', 'Quit the job reporting failure');
// UI Components // UI Components
@@ -54,6 +82,7 @@ export class JobStepDialog {
private retryIntervalBox: sqlops.InputBoxComponent; private retryIntervalBox: sqlops.InputBoxComponent;
private outputFileNameBox: sqlops.InputBoxComponent; private outputFileNameBox: sqlops.InputBoxComponent;
private fileBrowserNameBox: sqlops.InputBoxComponent; private fileBrowserNameBox: sqlops.InputBoxComponent;
private userInputBox: sqlops.InputBoxComponent;
// Dropdowns // Dropdowns
private typeDropdown: sqlops.DropDownComponent; private typeDropdown: sqlops.DropDownComponent;
@@ -98,33 +127,39 @@ export class JobStepDialog {
} }
private initializeUIComponents() { private initializeUIComponents() {
this.dialog = sqlops.window.modelviewdialog.createDialog(JobStepDialog.DialogTitle); this.dialog = sqlops.window.modelviewdialog.createDialog(this.DialogTitle);
this.generalTab = sqlops.window.modelviewdialog.createTab(JobStepDialog.GeneralTabText); this.generalTab = sqlops.window.modelviewdialog.createTab(this.GeneralTabText);
this.advancedTab = sqlops.window.modelviewdialog.createTab(JobStepDialog.AdvancedTabText); this.advancedTab = sqlops.window.modelviewdialog.createTab(this.AdvancedTabText);
this.dialog.content = [this.generalTab, this.advancedTab]; this.dialog.content = [this.generalTab, this.advancedTab];
this.dialog.okButton.onClick(async () => await this.execute()); this.dialog.okButton.onClick(async () => await this.execute());
this.dialog.okButton.label = JobStepDialog.OkButtonText; this.dialog.okButton.label = this.OkButtonText;
this.dialog.cancelButton.label = JobStepDialog.CancelButtonText; this.dialog.cancelButton.label = this.CancelButtonText;
} }
private createCommands(view, queryProvider: sqlops.QueryProvider) { private createCommands(view, queryProvider: sqlops.QueryProvider) {
this.openButton = view.modelBuilder.button() this.openButton = view.modelBuilder.button()
.withProperties({ .withProperties({
label: JobStepDialog.OpenCommandText, label: this.OpenCommandText,
width: '80px' width: '80px',
isFile: true
}).component(); }).component();
this.parseButton = view.modelBuilder.button() this.parseButton = view.modelBuilder.button()
.withProperties({ .withProperties({
label: JobStepDialog.ParseCommandText, label: this.ParseCommandText,
width: '80px' width: '80px',
isFile: false
}).component(); }).component();
this.openButton.onDidClick(e => {
let queryContent = e;
this.commandTextBox.value = queryContent;
});
this.parseButton.onDidClick(e => { this.parseButton.onDidClick(e => {
if (this.commandTextBox.value) { if (this.commandTextBox.value) {
queryProvider.parseSyntax(this.ownerUri, this.commandTextBox.value).then(result => { queryProvider.parseSyntax(this.ownerUri, this.commandTextBox.value).then(result => {
if (result && result.parseable) { if (result && result.parseable) {
this.dialog.message = { text: 'The command was successfully parsed.', level: 2}; this.dialog.message = { text: this.SuccessfulParseText, level: 2};
} else if (result && !result.parseable) { } else if (result && !result.parseable) {
this.dialog.message = { text: 'The command failed' }; this.dialog.message = { text: this.FailureParseText };
} }
}); });
} }
@@ -139,13 +174,13 @@ export class JobStepDialog {
.component(); .component();
this.nextButton = view.modelBuilder.button() this.nextButton = view.modelBuilder.button()
.withProperties({ .withProperties({
label: JobStepDialog.NextButtonText, label: this.NextButtonText,
enabled: false, enabled: false,
width: '80px' width: '80px'
}).component(); }).component();
this.previousButton = view.modelBuilder.button() this.previousButton = view.modelBuilder.button()
.withProperties({ .withProperties({
label: JobStepDialog.PreviousButtonText, label: this.PreviousButtonText,
enabled: false, enabled: false,
width: '80px' width: '80px'
}).component(); }).component();
@@ -157,10 +192,16 @@ export class JobStepDialog {
.withProperties({ .withProperties({
}).component(); }).component();
this.nameTextBox.required = true; this.nameTextBox.required = true;
this.nameTextBox.onTextChanged(() => {
if (this.nameTextBox.value.length > 0) {
this.dialog.message = null;
}
});
this.typeDropdown = view.modelBuilder.dropDown() this.typeDropdown = view.modelBuilder.dropDown()
.withProperties({ .withProperties({
value: JobStepDialog.TSQLScript, value: this.TSQLScript,
values: [JobStepDialog.TSQLScript] values: [this.TSQLScript]
}) })
.component(); .component();
this.runAsDropdown = view.modelBuilder.dropDown() this.runAsDropdown = view.modelBuilder.dropDown()
@@ -171,8 +212,8 @@ export class JobStepDialog {
.component(); .component();
this.runAsDropdown.enabled = false; this.runAsDropdown.enabled = false;
this.typeDropdown.onValueChanged((type) => { this.typeDropdown.onValueChanged((type) => {
if (type.selected !== JobStepDialog.TSQLScript) { if (type.selected !== this.TSQLScript) {
this.runAsDropdown.value = JobStepDialog.AgentServiceAccount; this.runAsDropdown.value = this.AgentServiceAccount;
this.runAsDropdown.values = [this.runAsDropdown.value]; this.runAsDropdown.values = [this.runAsDropdown.value];
} else { } else {
this.runAsDropdown.value = ''; this.runAsDropdown.value = '';
@@ -200,19 +241,19 @@ export class JobStepDialog {
let formModel = view.modelBuilder.formContainer() let formModel = view.modelBuilder.formContainer()
.withFormItems([{ .withFormItems([{
component: this.nameTextBox, component: this.nameTextBox,
title: 'Step name' title: this.StepNameLabelString
}, { }, {
component: this.typeDropdown, component: this.typeDropdown,
title: 'Type' title: this.TypeLabelString
}, { }, {
component: this.runAsDropdown, component: this.runAsDropdown,
title: 'Run as' title: this.RunAsLabelString
}, { }, {
component: this.databaseDropdown, component: this.databaseDropdown,
title: 'Database' title: this.DatabaseLabelString
}, { }, {
component: this.commandTextBox, component: this.commandTextBox,
title: 'Command', title: this.CommandLabelString,
actions: [buttonContainer] actions: [buttonContainer]
}], { }], {
horizontal: false, horizontal: false,
@@ -224,81 +265,57 @@ export class JobStepDialog {
}); });
} }
private createRunAsUserOptions(view) {
let userInputBox = view.modelBuilder.inputBox()
.withProperties({ inputType: 'text', width: '100px' }).component();
let viewButton = view.modelBuilder.button()
.withProperties({ label: '...', width: '20px' }).component();
let viewButtonContainer = view.modelBuilder.flexContainer()
.withLayout({ width: 100, textAlign: 'right' })
.withItems([viewButton], { flex: '1 1 50%' }).component();
let userInputBoxContainer = view.modelBuilder.flexContainer()
.withLayout({ width: 200, textAlign: 'left' })
.withItems([userInputBox], { flex: '1 1 50%' }).component();
let runAsUserContainer = view.modelBuilder.flexContainer()
.withLayout({ width: 200 })
.withItems([userInputBoxContainer, viewButtonContainer], { flex: '1 1 50%' })
.component();
let runAsUserForm = view.modelBuilder.formContainer()
.withFormItems([{
component: runAsUserContainer,
title: 'Run as user'
}], { horizontal: true, componentWidth: 200 }).component();
return runAsUserForm;
}
private createAdvancedTab() { private createAdvancedTab() {
this.advancedTab.registerContent(async (view) => { this.advancedTab.registerContent(async (view) => {
this.successActionDropdown = view.modelBuilder.dropDown() this.successActionDropdown = view.modelBuilder.dropDown()
.withProperties({ .withProperties({
value: JobStepDialog.NextStep, width: '100%',
values: [JobStepDialog.NextStep, JobStepDialog.QuitJobReportingSuccess, JobStepDialog.QuitJobReportingFailure] value: this.NextStep,
values: [this.NextStep, this.QuitJobReportingSuccess, this.QuitJobReportingFailure]
}) })
.component(); .component();
let retryFlexContainer = this.createRetryCounters(view); let retryFlexContainer = this.createRetryCounters(view);
this.failureActionDropdown = view.modelBuilder.dropDown() this.failureActionDropdown = view.modelBuilder.dropDown()
.withProperties({ .withProperties({
value: JobStepDialog.QuitJobReportingFailure, value: this.QuitJobReportingFailure,
values: [JobStepDialog.QuitJobReportingFailure, JobStepDialog.NextStep, JobStepDialog.QuitJobReportingSuccess] values: [this.QuitJobReportingFailure, this.NextStep, this.QuitJobReportingSuccess]
}) })
.component(); .component();
let optionsGroup = this.createTSQLOptions(view); let optionsGroup = this.createTSQLOptions(view);
let viewButton = view.modelBuilder.button()
.withProperties({ label: 'View', width: '50px' }).component();
viewButton.enabled = false;
this.logToTableCheckbox = view.modelBuilder.checkBox() this.logToTableCheckbox = view.modelBuilder.checkBox()
.withProperties({ .withProperties({
label: 'Log to table' label: this.LogToTableLabel
}).component(); }).component();
let appendToExistingEntryInTableCheckbox = view.modelBuilder.checkBox() let appendToExistingEntryInTableCheckbox = view.modelBuilder.checkBox()
.withProperties({ label: 'Append output to existing entry in table' }).component(); .withProperties({ label: this.AppendExistingTableEntryLabel }).component();
appendToExistingEntryInTableCheckbox.enabled = false; appendToExistingEntryInTableCheckbox.enabled = false;
this.logToTableCheckbox.onChanged(e => { this.logToTableCheckbox.onChanged(e => {
viewButton.enabled = e;
appendToExistingEntryInTableCheckbox.enabled = e; appendToExistingEntryInTableCheckbox.enabled = e;
}); });
let appendCheckboxContainer = view.modelBuilder.groupContainer() let appendCheckboxContainer = view.modelBuilder.groupContainer()
.withItems([appendToExistingEntryInTableCheckbox]).component(); .withItems([appendToExistingEntryInTableCheckbox]).component();
let logToTableContainer = view.modelBuilder.flexContainer() let logToTableContainer = view.modelBuilder.flexContainer()
.withLayout({ flexFlow: 'row', justifyContent: 'space-between', width: 300 }) .withLayout({ flexFlow: 'row', justifyContent: 'space-between', width: 300 })
.withItems([this.logToTableCheckbox, viewButton]).component(); .withItems([this.logToTableCheckbox]).component();
let logStepOutputHistoryCheckbox = view.modelBuilder.checkBox() let logStepOutputHistoryCheckbox = view.modelBuilder.checkBox()
.withProperties({ label: 'Include step output in history' }).component(); .withProperties({ label: this.IncludeStepOutputHistoryLabel }).component();
let runAsUserOptions = this.createRunAsUserOptions(view); this.userInputBox = view.modelBuilder.inputBox()
.withProperties({ inputType: 'text', width: '100%' }).component();
let formModel = view.modelBuilder.formContainer() let formModel = view.modelBuilder.formContainer()
.withFormItems( .withFormItems(
[{ [{
component: this.successActionDropdown, component: this.successActionDropdown,
title: JobStepDialog.SuccessAction title: this.SuccessActionLabel
}, { }, {
component: retryFlexContainer, component: retryFlexContainer,
title: '' title: ''
}, { }, {
component: this.failureActionDropdown, component: this.failureActionDropdown,
title: JobStepDialog.FailureAction title: this.FailureActionLabel
}, { }, {
component: optionsGroup, component: optionsGroup,
title: 'Transact-SQL script (T-SQL)' title: this.TSQLScript
}, { }, {
component: logToTableContainer, component: logToTableContainer,
title: '' title: ''
@@ -309,8 +326,8 @@ export class JobStepDialog {
component: logStepOutputHistoryCheckbox, component: logStepOutputHistoryCheckbox,
title: '' title: ''
}, { }, {
component: runAsUserOptions, component: this.userInputBox,
title: '' title: this.RunAsUserLabel
}], { }], {
componentWidth: 400 componentWidth: 400
}).component(); }).component();
@@ -323,32 +340,37 @@ export class JobStepDialog {
private createRetryCounters(view) { private createRetryCounters(view) {
this.retryAttemptsBox = view.modelBuilder.inputBox() this.retryAttemptsBox = view.modelBuilder.inputBox()
.withValidation(component => component.value >= 0) .withValidation(component => component.value >= 0)
.withProperties({ .withProperties({
inputType: 'number' inputType: 'number',
}) width: '100%',
.component(); placeHolder: '0'
})
.component();
this.retryIntervalBox = view.modelBuilder.inputBox() this.retryIntervalBox = view.modelBuilder.inputBox()
.withValidation(component => component.value >= 0) .withValidation(component => component.value >= 0)
.withProperties({ .withProperties({
inputType: 'number' inputType: 'number',
width: '100%',
placeHolder: '0'
}).component(); }).component();
let retryAttemptsContainer = view.modelBuilder.formContainer() let retryAttemptsContainer = view.modelBuilder.formContainer()
.withFormItems( .withFormItems(
[{ [{
component: this.retryAttemptsBox, component: this.retryAttemptsBox,
title: 'Retry Attempts' title: this.RetryAttemptsLabel
}], { }], {
horizontal: false horizontal: false,
}) componentWidth: '100%'
})
.component(); .component();
let retryIntervalContainer = view.modelBuilder.formContainer() let retryIntervalContainer = view.modelBuilder.formContainer()
.withFormItems( .withFormItems(
[{ [{
component: this.retryIntervalBox, component: this.retryIntervalBox,
title: 'Retry Interval (minutes)' title: this.RetryIntervalLabel
}], { }], {
horizontal: false horizontal: false
}) })
@@ -362,7 +384,7 @@ export class JobStepDialog {
} }
private openFileBrowserDialog() { private openFileBrowserDialog() {
let fileBrowserTitle = JobStepDialog.FileBrowserDialogTitle + `${this.server}`; let fileBrowserTitle = this.FileBrowserDialogTitle + `${this.server}`;
this.fileBrowserDialog = sqlops.window.modelviewdialog.createDialog(fileBrowserTitle); this.fileBrowserDialog = sqlops.window.modelviewdialog.createDialog(fileBrowserTitle);
let fileBrowserTab = sqlops.window.modelviewdialog.createTab('File Browser'); let fileBrowserTab = sqlops.window.modelviewdialog.createTab('File Browser');
this.fileBrowserDialog.content = [fileBrowserTab]; this.fileBrowserDialog.content = [fileBrowserTab];
@@ -379,8 +401,8 @@ export class JobStepDialog {
}); });
this.fileTypeDropdown = view.modelBuilder.dropDown() this.fileTypeDropdown = view.modelBuilder.dropDown()
.withProperties({ .withProperties({
value: 'All Files (*)', value: this.AllFilesLabelString,
values: ['All Files (*)'] values: [this.AllFilesLabelString]
}) })
.component(); .component();
this.fileBrowserNameBox = view.modelBuilder.inputBox() this.fileBrowserNameBox = view.modelBuilder.inputBox()
@@ -392,13 +414,13 @@ export class JobStepDialog {
title: '' title: ''
}, { }, {
component: this.selectedPathTextBox, component: this.selectedPathTextBox,
title: 'Selected path:' title: this.SelectedPathLabelString
}, { }, {
component: this.fileTypeDropdown, component: this.fileTypeDropdown,
title: 'Files of type:' title: this.FilesOfTypeLabelString
}, { }, {
component: this.fileBrowserNameBox, component: this.fileBrowserNameBox,
title: 'File name:' title: this.FileNameLabelString
} }
]).component(); ]).component();
view.initializeModel(fileBrowserContainer); view.initializeModel(fileBrowserContainer);
@@ -406,8 +428,8 @@ export class JobStepDialog {
this.fileBrowserDialog.okButton.onClick(() => { this.fileBrowserDialog.okButton.onClick(() => {
this.outputFileNameBox.value = path.join(path.dirname(this.selectedPathTextBox.value), this.fileBrowserNameBox.value); this.outputFileNameBox.value = path.join(path.dirname(this.selectedPathTextBox.value), this.fileBrowserNameBox.value);
}); });
this.fileBrowserDialog.okButton.label = JobStepDialog.OkButtonText; this.fileBrowserDialog.okButton.label = this.OkButtonText;
this.fileBrowserDialog.cancelButton.label = JobStepDialog.CancelButtonText; this.fileBrowserDialog.cancelButton.label = this.CancelButtonText;
sqlops.window.modelviewdialog.openDialog(this.fileBrowserDialog); sqlops.window.modelviewdialog.openDialog(this.fileBrowserDialog);
} }
@@ -417,21 +439,15 @@ export class JobStepDialog {
this.outputFileBrowserButton.onDidClick(() => this.openFileBrowserDialog()); this.outputFileBrowserButton.onDidClick(() => this.openFileBrowserDialog());
this.outputFileNameBox = view.modelBuilder.inputBox() this.outputFileNameBox = view.modelBuilder.inputBox()
.withProperties({ .withProperties({
width: '150px', width: 250,
inputType: 'text' inputType: 'text'
}).component(); }).component();
let outputViewButton = view.modelBuilder.button()
.withProperties({
width: '50px',
label: 'View'
}).component();
outputViewButton.enabled = false;
let outputButtonContainer = view.modelBuilder.flexContainer() let outputButtonContainer = view.modelBuilder.flexContainer()
.withLayout({ .withLayout({
flexFlow: 'row', flexFlow: 'row',
textAlign: 'right', textAlign: 'right',
width: 120 width: '100%'
}).withItems([this.outputFileBrowserButton, outputViewButton], { flex: '1 1 50%' }).component(); }).withItems([this.outputFileBrowserButton], { flex: '1 1 50%' }).component();
let outputFlexBox = view.modelBuilder.flexContainer() let outputFlexBox = view.modelBuilder.flexContainer()
.withLayout({ .withLayout({
flexFlow: 'row', flexFlow: 'row',
@@ -441,7 +457,7 @@ export class JobStepDialog {
}).component(); }).component();
this.appendToExistingFileCheckbox = view.modelBuilder.checkBox() this.appendToExistingFileCheckbox = view.modelBuilder.checkBox()
.withProperties({ .withProperties({
label: 'Append output to existing file' label: this.AppendOutputToFileLabel
}).component(); }).component();
this.appendToExistingFileCheckbox.enabled = false; this.appendToExistingFileCheckbox.enabled = false;
this.outputFileNameBox.onTextChanged((input) => { this.outputFileNameBox.onTextChanged((input) => {
@@ -454,15 +470,20 @@ export class JobStepDialog {
let outputFileForm = view.modelBuilder.formContainer() let outputFileForm = view.modelBuilder.formContainer()
.withFormItems([{ .withFormItems([{
component: outputFlexBox, component: outputFlexBox,
title: 'Output file' title: this.OutputFileNameLabel
}, { }, {
component: this.appendToExistingFileCheckbox, component: this.appendToExistingFileCheckbox,
title: '' title: ''
}], { horizontal: true, componentWidth: 200 }).component(); }], { horizontal: false, componentWidth: 200 }).component();
return outputFileForm; return outputFileForm;
} }
private async execute() { protected execute() {
this.model.stepName = this.nameTextBox.value;
if (!this.model.stepName || this.model.stepName.length === 0) {
this.dialog.message = this.dialog.message = { text: this.BlankStepNameErrorText };
return;
}
this.model.jobName = this.jobName; this.model.jobName = this.jobName;
this.model.id = this.stepId; this.model.id = this.stepId;
this.model.server = this.server; this.model.server = this.server;
@@ -471,12 +492,11 @@ export class JobStepDialog {
this.model.databaseName = this.databaseDropdown.value as string; this.model.databaseName = this.databaseDropdown.value as string;
this.model.script = this.commandTextBox.value; this.model.script = this.commandTextBox.value;
this.model.successAction = this.successActionDropdown.value as string; this.model.successAction = this.successActionDropdown.value as string;
this.model.retryAttempts = +this.retryAttemptsBox.value; this.model.retryAttempts = this.retryAttemptsBox.value ? +this.retryAttemptsBox.value : 0;
this.model.retryInterval = +this.retryIntervalBox.value; this.model.retryInterval = +this.retryIntervalBox.value ? +this.retryIntervalBox.value : 0;
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;
await this.model.save();
} }
public async openNewStepDialog() { public async openNewStepDialog() {

View File

@@ -16,7 +16,8 @@ const localize = nls.loadMessageBundle();
export class OperatorDialog extends AgentDialog<OperatorData> { export class OperatorDialog extends AgentDialog<OperatorData> {
// Top level // Top level
private static readonly DialogTitle: string = localize('createOperator.createOperator', 'Create Operator'); private static readonly CreateDialogTitle: string = localize('createOperator.createOperator', 'Create Operator');
private static readonly EditDialogTitle: string = localize('createOperator.editOperator', 'Edit Operator');
private static readonly GeneralTabText: string = localize('createOperator.General', 'General'); private static readonly GeneralTabText: string = localize('createOperator.General', 'General');
private static readonly NotificationsTabText: string = localize('createOperator.Notifications', 'Notifications'); private static readonly NotificationsTabText: string = localize('createOperator.Notifications', 'Notifications');
@@ -32,6 +33,9 @@ export class OperatorDialog extends AgentDialog<OperatorData> {
private static readonly PagerFridayCheckBoxLabel: string = localize('createOperator.PagerFridayCheckBox', 'Friday '); private static readonly PagerFridayCheckBoxLabel: string = localize('createOperator.PagerFridayCheckBox', 'Friday ');
private static readonly PagerSaturdayCheckBoxLabel: string = localize('createOperator.PagerSaturdayCheckBox', 'Saturday'); private static readonly PagerSaturdayCheckBoxLabel: string = localize('createOperator.PagerSaturdayCheckBox', 'Saturday');
private static readonly PagerSundayCheckBoxLabel: string = localize('createOperator.PagerSundayCheckBox', 'Sunday'); private static readonly PagerSundayCheckBoxLabel: string = localize('createOperator.PagerSundayCheckBox', 'Sunday');
private static readonly WorkdayBeginLabel: string = localize('createOperator.workdayBegin', 'Workday begin');
private static readonly WorkdayEndLabel: string = localize('createOperator.workdayEnd', 'Workday end');
private static readonly PagerDutyScheduleLabel: string = localize('createOperator.PagerDutySchedule', 'Pager on duty schdule');
// Notifications tab strings // Notifications tab strings
private static readonly AlertsTableLabel: string = localize('createOperator.AlertListHeading', 'Alert list'); private static readonly AlertsTableLabel: string = localize('createOperator.AlertListHeading', 'Alert list');
@@ -65,8 +69,11 @@ export class OperatorDialog extends AgentDialog<OperatorData> {
// Notification tab controls // Notification tab controls
private alertsTable: sqlops.TableComponent; private alertsTable: sqlops.TableComponent;
constructor(ownerUri: string) { constructor(ownerUri: string, operatorInfo: sqlops.AgentOperatorInfo = undefined) {
super(ownerUri, new OperatorData(ownerUri), OperatorDialog.DialogTitle); super(
ownerUri,
new OperatorData(ownerUri, operatorInfo),
operatorInfo ? OperatorDialog.EditDialogTitle : OperatorDialog.CreateDialogTitle);
} }
protected async initializeDialog(dialog: sqlops.window.modelviewdialog.Dialog) { protected async initializeDialog(dialog: sqlops.window.modelviewdialog.Dialog) {
@@ -174,13 +181,13 @@ export class OperatorDialog extends AgentDialog<OperatorData> {
this.weekdayPagerStartTimeInput = view.modelBuilder.inputBox() this.weekdayPagerStartTimeInput = view.modelBuilder.inputBox()
.withProperties({ .withProperties({
inputType: 'time', inputType: 'time',
placeHolder: '08:00:00' placeHolder: '08:00:00',
}).component(); }).component();
this.weekdayPagerStartTimeInput.enabled = false; this.weekdayPagerStartTimeInput.enabled = false;
let weekdayStartInputContainer = view.modelBuilder.formContainer() let weekdayStartInputContainer = view.modelBuilder.formContainer()
.withFormItems([{ .withFormItems([{
component: this.weekdayPagerStartTimeInput, component: this.weekdayPagerStartTimeInput,
title: 'Workday begin' title: OperatorDialog.WorkdayBeginLabel
}]).component(); }]).component();
this.weekdayPagerEndTimeInput = view.modelBuilder.inputBox() this.weekdayPagerEndTimeInput = view.modelBuilder.inputBox()
@@ -192,7 +199,7 @@ export class OperatorDialog extends AgentDialog<OperatorData> {
let weekdayEndInputContainer = view.modelBuilder.formContainer() let weekdayEndInputContainer = view.modelBuilder.formContainer()
.withFormItems([{ .withFormItems([{
component: this.weekdayPagerEndTimeInput, component: this.weekdayPagerEndTimeInput,
title: 'Workday end' title: OperatorDialog.WorkdayEndLabel
}]).component(); }]).component();
this.pagerFridayCheckBox = view.modelBuilder.checkBox() this.pagerFridayCheckBox = view.modelBuilder.checkBox()
@@ -216,7 +223,8 @@ export class OperatorDialog extends AgentDialog<OperatorData> {
let pagerFridayCheckboxContainer = view.modelBuilder.flexContainer() let pagerFridayCheckboxContainer = view.modelBuilder.flexContainer()
.withLayout({ .withLayout({
flexFlow: 'row', flexFlow: 'row',
alignItems: 'baseline' alignItems: 'baseline',
width: '100%'
}).withItems([this.pagerFridayCheckBox, weekdayStartInputContainer, weekdayEndInputContainer]) }).withItems([this.pagerFridayCheckBox, weekdayStartInputContainer, weekdayEndInputContainer])
.component(); .component();
@@ -245,7 +253,7 @@ export class OperatorDialog extends AgentDialog<OperatorData> {
let saturdayStartInputContainer = view.modelBuilder.formContainer() let saturdayStartInputContainer = view.modelBuilder.formContainer()
.withFormItems([{ .withFormItems([{
component: this.saturdayPagerStartTimeInput, component: this.saturdayPagerStartTimeInput,
title: 'Workday begin' title: OperatorDialog.WorkdayBeginLabel
}]).component(); }]).component();
this.saturdayPagerEndTimeInput = view.modelBuilder.inputBox() this.saturdayPagerEndTimeInput = view.modelBuilder.inputBox()
@@ -257,7 +265,7 @@ export class OperatorDialog extends AgentDialog<OperatorData> {
let saturdayEndInputContainer = view.modelBuilder.formContainer() let saturdayEndInputContainer = view.modelBuilder.formContainer()
.withFormItems([{ .withFormItems([{
component: this.saturdayPagerEndTimeInput, component: this.saturdayPagerEndTimeInput,
title: 'Workday end' title: OperatorDialog.WorkdayEndLabel
}]).component(); }]).component();
let pagerSaturdayCheckboxContainer = view.modelBuilder.flexContainer() let pagerSaturdayCheckboxContainer = view.modelBuilder.flexContainer()
@@ -292,7 +300,7 @@ export class OperatorDialog extends AgentDialog<OperatorData> {
let sundayStartInputContainer = view.modelBuilder.formContainer() let sundayStartInputContainer = view.modelBuilder.formContainer()
.withFormItems([{ .withFormItems([{
component: this.sundayPagerStartTimeInput, component: this.sundayPagerStartTimeInput,
title: 'Workday begin' title: OperatorDialog.WorkdayBeginLabel
}]).component(); }]).component();
this.sundayPagerEndTimeInput = view.modelBuilder.inputBox() this.sundayPagerEndTimeInput = view.modelBuilder.inputBox()
@@ -304,7 +312,7 @@ export class OperatorDialog extends AgentDialog<OperatorData> {
let sundayEndInputContainer = view.modelBuilder.formContainer() let sundayEndInputContainer = view.modelBuilder.formContainer()
.withFormItems([{ .withFormItems([{
component: this.sundayPagerEndTimeInput, component: this.sundayPagerEndTimeInput,
title: 'Workday end' title: OperatorDialog.WorkdayEndLabel
}]).component(); }]).component();
let pagerSundayCheckboxContainer = view.modelBuilder.flexContainer() let pagerSundayCheckboxContainer = view.modelBuilder.flexContainer()
@@ -314,36 +322,6 @@ export class OperatorDialog extends AgentDialog<OperatorData> {
}).withItems([this.pagerSundayCheckBox, sundayStartInputContainer, sundayEndInputContainer]) }).withItems([this.pagerSundayCheckBox, sundayStartInputContainer, sundayEndInputContainer])
.component(); .component();
let checkBoxContainer = view.modelBuilder.formContainer()
.withFormItems([{
component: this.pagerMondayCheckBox,
title: ''
}, {
component: this.pagerTuesdayCheckBox,
title: ''
}, {
component: this.pagerWednesdayCheckBox,
title: ''
}, {
component: this.pagerThursdayCheckBox,
title: ''
}, {
component: pagerFridayCheckboxContainer,
title: ''
}, {
component: pagerSaturdayCheckboxContainer,
title: ''
}, {
component: pagerSundayCheckboxContainer,
title: ''
}]).component();
let pagerContainer = view.modelBuilder.flexContainer()
.withLayout({
flexFlow: 'row'
}).withItems([checkBoxContainer])
.component();
let formModel = view.modelBuilder.formContainer() let formModel = view.modelBuilder.formContainer()
.withFormItems([{ .withFormItems([{
component: this.nameTextBox, component: this.nameTextBox,
@@ -358,8 +336,29 @@ export class OperatorDialog extends AgentDialog<OperatorData> {
component: this.pagerEmailNameTextBox, component: this.pagerEmailNameTextBox,
title: OperatorDialog.PagerEmailNameTextLabel title: OperatorDialog.PagerEmailNameTextLabel
}, { }, {
component: pagerContainer, components: [{
title: '' component: this.pagerMondayCheckBox,
title: ''
}, {
component: this.pagerTuesdayCheckBox,
title: ''
}, {
component: this.pagerWednesdayCheckBox,
title: ''
}, {
component: this.pagerThursdayCheckBox,
title: ''
}, {
component: pagerFridayCheckboxContainer,
title: ''
}, {
component: pagerSaturdayCheckboxContainer,
title: ''
}, {
component: pagerSundayCheckboxContainer,
title: ''
}] ,
title: OperatorDialog.PagerDutyScheduleLabel
}]).withLayout({ width: '100%' }).component(); }]).withLayout({ width: '100%' }).component();
await view.initializeModel(formModel); await view.initializeModel(formModel);

View File

@@ -4,18 +4,22 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
'use strict'; 'use strict';
import * as nls from 'vscode-nls';
import * as sqlops from 'sqlops'; import * as sqlops from 'sqlops';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import { PickScheduleData } from '../data/pickScheduleData'; import { PickScheduleData } from '../data/pickScheduleData';
const localize = nls.loadMessageBundle();
export class PickScheduleDialog { export class PickScheduleDialog {
// TODO: localize // TODO: localize
// Top level // Top level
private readonly DialogTitle: string = 'Job Schedules'; private readonly DialogTitle: string = localize('pickSchedule.jobSchedules', 'Job Schedules');
private readonly OkButtonText: string = 'OK'; private readonly OkButtonText: string = localize('pickSchedule.ok', 'OK');
private readonly CancelButtonText: string = 'Cancel'; private readonly CancelButtonText: string = localize('pickSchedule.cancel', 'Cancel');
private readonly SchedulesTabText: string = 'Schedules'; private readonly ScheduleNameLabelText: string = localize('pickSchedule.scheduleName', 'Schedule Name');
private readonly SchedulesLabelText: string = localize('pickSchedule.schedules', 'Schedules');
// UI Components // UI Components
private dialog: sqlops.window.modelviewdialog.Dialog; private dialog: sqlops.window.modelviewdialog.Dialog;
@@ -38,7 +42,6 @@ export class PickScheduleDialog {
this.dialog.cancelButton.onClick(async () => await this.cancel()); this.dialog.cancelButton.onClick(async () => await this.cancel());
this.dialog.okButton.label = this.OkButtonText; this.dialog.okButton.label = this.OkButtonText;
this.dialog.cancelButton.label = this.CancelButtonText; this.dialog.cancelButton.label = this.CancelButtonText;
sqlops.window.modelviewdialog.openDialog(this.dialog); sqlops.window.modelviewdialog.openDialog(this.dialog);
} }
@@ -47,17 +50,17 @@ export class PickScheduleDialog {
this.schedulesTable = view.modelBuilder.table() this.schedulesTable = view.modelBuilder.table()
.withProperties({ .withProperties({
columns: [ columns: [
'Schedule Name' this.ScheduleNameLabelText
], ],
data: [], data: [],
height: 600, height: '80em',
width: 400 width: '40em'
}).component(); }).component();
let formModel = view.modelBuilder.formContainer() let formModel = view.modelBuilder.formContainer()
.withFormItems([{ .withFormItems([{
component: this.schedulesTable, component: this.schedulesTable,
title: 'Schedules' title: this.SchedulesLabelText
}]).withLayout({ width: '100%' }).component(); }]).withLayout({ width: '100%' }).component();
await view.initializeModel(formModel); await view.initializeModel(formModel);

View File

@@ -15,32 +15,60 @@ const localize = nls.loadMessageBundle();
export class ProxyDialog extends AgentDialog<ProxyData> { export class ProxyDialog extends AgentDialog<ProxyData> {
// Top level // Top level
private static readonly DialogTitle: string = localize('createProxy.createAlert', 'Create Alert'); private static readonly CreateDialogTitle: string = localize('createProxy.createProxy', 'Create Proxy');
private static readonly EditDialogTitle: string = localize('createProxy.editProxy', 'Edit Proxy');
private static readonly GeneralTabText: string = localize('createProxy.General', 'General'); private static readonly GeneralTabText: string = localize('createProxy.General', 'General');
// General tab strings // General tab strings
private static readonly ProxyNameTextBoxLabel: string = localize('createProxy.ProxyName', 'Proxy name'); private static readonly ProxyNameTextBoxLabel: string = localize('createProxy.ProxyName', 'Proxy name');
private static readonly CredentialNameTextBoxLabel: string = localize('createProxy.CredentialName', 'Credential name'); private static readonly CredentialNameTextBoxLabel: string = localize('createProxy.CredentialName', 'Credential name');
private static readonly DescriptionTextBoxLabel: string = localize('createProxy.Description', 'Description'); private static readonly DescriptionTextBoxLabel: string = localize('createProxy.Description', 'Description');
private static readonly SubsystemsTableLabel: string = localize('createProxy.Subsystems', 'Subsystems'); private static readonly SubsystemLabel: string = localize('createProxy.SubsystemName', 'Subsystem');
private static readonly SubsystemNameColumnLabel: string = localize('createProxy.SubsystemName', 'Subsystem'); private static readonly OperatingSystemLabel: string = localize('createProxy.OperatingSystem', 'Operating system (CmdExec)');
private static readonly ReplicationSnapshotLabel: string = localize('createProxy.ReplicationSnapshot', 'Replication Snapshot');
private static readonly ReplicationTransactionLogLabel: string = localize('createProxy.ReplicationTransactionLog', 'Replication Transaction-Log Reader');
private static readonly ReplicationDistributorLabel: string = localize('createProxy.ReplicationDistributor', 'Replication Distributor');
private static readonly ReplicationMergeLabel: string = localize('createProxy.ReplicationMerge', 'Replication Merge');
private static readonly ReplicationQueueReaderLabel: string = localize('createProxy.ReplicationQueueReader', 'Replication Queue Reader');
private static readonly SSASQueryLabel: string = localize('createProxy.SSASQueryLabel', 'SQL Server Analysis Services Query');
private static readonly SSASCommandLabel: string = localize('createProxy.SSASCommandLabel', 'SQL Server Analysis Services Command');
private static readonly SSISPackageLabel: string = localize('createProxy.SSISPackage', 'SQL Server Integration Services Package');
private static readonly PowerShellLabel: string = localize('createProxy.PowerShell', 'PowerShell');
private static readonly SubSystemHeadingLabel: string = localize('createProxy.subSystemHeading', 'Active to the following subsytems');
// UI Components // UI Components
private generalTab: sqlops.window.modelviewdialog.DialogTab; private generalTab: sqlops.window.modelviewdialog.DialogTab;
// General tab controls // General tab controls
private proxyNameTextBox: sqlops.InputBoxComponent; private proxyNameTextBox: sqlops.InputBoxComponent;
private credentialNameTextBox: sqlops.InputBoxComponent; private credentialNameDropDown: sqlops.DropDownComponent;
private descriptionTextBox: sqlops.InputBoxComponent; private descriptionTextBox: sqlops.InputBoxComponent;
private subsystemsTable: sqlops.TableComponent; private subsystemCheckBox: sqlops.CheckBoxComponent;
private operatingSystemCheckBox: sqlops.CheckBoxComponent;
private replicationSnapshotCheckBox: sqlops.CheckBoxComponent;
private replicationTransactionLogCheckBox: sqlops.CheckBoxComponent;
private replicationDistributorCheckBox: sqlops.CheckBoxComponent;
private replicationMergeCheckbox: sqlops.CheckBoxComponent;
private replicationQueueReaderCheckbox: sqlops.CheckBoxComponent;
private sqlQueryCheckBox: sqlops.CheckBoxComponent;
private sqlCommandCheckBox: sqlops.CheckBoxComponent;
private sqlIntegrationServicesPackageCheckbox: sqlops.CheckBoxComponent;
private powershellCheckBox: sqlops.CheckBoxComponent;
constructor(ownerUri: string) { private credentials: sqlops.CredentialInfo[];
super(ownerUri, new ProxyData(ownerUri), ProxyDialog.DialogTitle);
constructor(ownerUri: string, proxyInfo: sqlops.AgentProxyInfo = undefined, credentials: sqlops.CredentialInfo[]) {
super(
ownerUri,
new ProxyData(ownerUri, proxyInfo),
proxyInfo ? ProxyDialog.EditDialogTitle : ProxyDialog.CreateDialogTitle);
this.credentials = credentials;
} }
protected async initializeDialog(dialog: sqlops.window.modelviewdialog.Dialog) { protected async initializeDialog(dialog: sqlops.window.modelviewdialog.Dialog) {
this.generalTab = sqlops.window.modelviewdialog.createTab(ProxyDialog.GeneralTabText); this.generalTab = sqlops.window.modelviewdialog.createTab(ProxyDialog.GeneralTabText);
this.initializeGeneralTab(); this.initializeGeneralTab();
this.dialog.content = [this.generalTab]; this.dialog.content = [this.generalTab];
@@ -49,43 +77,143 @@ export class ProxyDialog extends AgentDialog<ProxyData> {
private initializeGeneralTab() { private initializeGeneralTab() {
this.generalTab.registerContent(async view => { this.generalTab.registerContent(async view => {
this.proxyNameTextBox = view.modelBuilder.inputBox().component(); this.proxyNameTextBox = view.modelBuilder.inputBox()
.withProperties({width: 420})
.component();
this.credentialNameTextBox = view.modelBuilder.inputBox().component(); this.credentialNameDropDown = view.modelBuilder.dropDown()
this.descriptionTextBox = view.modelBuilder.inputBox().component();
this.subsystemsTable = view.modelBuilder.table()
.withProperties({ .withProperties({
columns: [ width: 432,
ProxyDialog.SubsystemNameColumnLabel value: '',
], editable: true,
data: [], values: this.credentials.length > 0 ? this.credentials.map(c => c.name) : ['']
height: 500 })
.component();
this.descriptionTextBox = view.modelBuilder.inputBox()
.withProperties({
width: 420,
multiline: true,
height: 300
})
.component();
this.subsystemCheckBox = view.modelBuilder.checkBox()
.withProperties({
label: ProxyDialog.SubsystemLabel
}).component(); }).component();
this.subsystemCheckBox.onChanged(() => {
if (this.subsystemCheckBox.checked) {
this.operatingSystemCheckBox.checked = true;
this.replicationSnapshotCheckBox.checked = true;
this.replicationTransactionLogCheckBox.checked = true;
this.replicationDistributorCheckBox.checked = true;
this.replicationMergeCheckbox.checked = true;
this.replicationQueueReaderCheckbox.checked = true;
this.sqlQueryCheckBox.checked = true;
this.sqlCommandCheckBox.checked = true;
this.sqlIntegrationServicesPackageCheckbox.checked = true;
this.powershellCheckBox.checked = true;
} else {
this.operatingSystemCheckBox.checked = false;
this.replicationSnapshotCheckBox.checked = false;
this.replicationTransactionLogCheckBox.checked = false;
this.replicationDistributorCheckBox.checked = false;
this.replicationMergeCheckbox.checked = false;
this.replicationQueueReaderCheckbox.checked = false;
this.sqlQueryCheckBox.checked = false;
this.sqlCommandCheckBox.checked = false;
this.sqlIntegrationServicesPackageCheckbox.checked = false;
this.powershellCheckBox.checked = false;
}
});
this.operatingSystemCheckBox = view.modelBuilder.checkBox()
.withProperties({
label: ProxyDialog.OperatingSystemLabel
}).component();
this.replicationSnapshotCheckBox = view.modelBuilder.checkBox()
.withProperties({
label: ProxyDialog.ReplicationSnapshotLabel
}).component();
this.replicationTransactionLogCheckBox = view.modelBuilder.checkBox()
.withProperties({
label: ProxyDialog.ReplicationTransactionLogLabel
}).component();
this.replicationDistributorCheckBox = view.modelBuilder.checkBox()
.withProperties({
label: ProxyDialog.ReplicationDistributorLabel
}).component();
this.replicationMergeCheckbox = view.modelBuilder.checkBox()
.withProperties({
label: ProxyDialog.ReplicationMergeLabel
}).component();
this.replicationQueueReaderCheckbox = view.modelBuilder.checkBox()
.withProperties({
label: ProxyDialog.ReplicationQueueReaderLabel
}).component();
this.sqlQueryCheckBox = view.modelBuilder.checkBox()
.withProperties({
label: ProxyDialog.SSASQueryLabel
}).component();
this.sqlCommandCheckBox = view.modelBuilder.checkBox()
.withProperties({
label: ProxyDialog.SSASCommandLabel
}).component();
this.sqlIntegrationServicesPackageCheckbox = view.modelBuilder.checkBox()
.withProperties({
label: ProxyDialog.SSISPackageLabel
}).component();
this.powershellCheckBox = view.modelBuilder.checkBox()
.withProperties({
label: ProxyDialog.PowerShellLabel
}).component();
let checkBoxContainer = view.modelBuilder.groupContainer()
.withItems([this.operatingSystemCheckBox, this.replicationSnapshotCheckBox,
this.replicationTransactionLogCheckBox, this.replicationDistributorCheckBox, this.replicationMergeCheckbox,
this.replicationQueueReaderCheckbox, this.sqlQueryCheckBox, this.sqlCommandCheckBox, this.sqlIntegrationServicesPackageCheckbox,
this.powershellCheckBox])
.component();
let formModel = view.modelBuilder.formContainer() let formModel = view.modelBuilder.formContainer()
.withFormItems([{ .withFormItems([{
component: this.proxyNameTextBox, component: this.proxyNameTextBox,
title: ProxyDialog.ProxyNameTextBoxLabel title: ProxyDialog.ProxyNameTextBoxLabel
}, { }, {
component: this.credentialNameTextBox, component: this.credentialNameDropDown,
title: ProxyDialog.CredentialNameTextBoxLabel title: ProxyDialog.CredentialNameTextBoxLabel
}, { }, {
component: this.descriptionTextBox, component: this.descriptionTextBox,
title: ProxyDialog.DescriptionTextBoxLabel title: ProxyDialog.DescriptionTextBoxLabel
}, { }]).withLayout({ width: 420 }).component();
component: this.subsystemsTable,
title: ProxyDialog.SubsystemsTableLabel
}]).withLayout({ width: '100%' }).component();
await view.initializeModel(formModel); await view.initializeModel(formModel);
this.proxyNameTextBox.value = this.model.accountName;
this.credentialNameDropDown.value = this.model.credentialName;
this.descriptionTextBox.value = this.model.description;
}); });
} }
protected updateModel() { protected updateModel() {
this.model.accountName = this.proxyNameTextBox.value; this.model.accountName = this.proxyNameTextBox.value;
this.model.credentialName = this.credentialNameTextBox.value; this.model.credentialName = this.credentialNameDropDown.value as string;
this.model.credentialId = this.credentials.find(
c => c.name === this.model.credentialName).id;
this.model.credentialIdentity = this.credentials.find(
c => c.name === this.model.credentialName).identity;
this.model.description = this.descriptionTextBox.value; this.model.description = this.descriptionTextBox.value;
} }
} }

View File

@@ -4,17 +4,21 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
'use strict'; 'use strict';
import * as nls from 'vscode-nls';
import * as sqlops from 'sqlops'; import * as sqlops from 'sqlops';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import { ScheduleData } from '../data/scheduleData'; import { ScheduleData } from '../data/scheduleData';
const localize = nls.loadMessageBundle();
export class ScheduleDialog { export class ScheduleDialog {
// Top level // Top level
private readonly DialogTitle: string = 'New Schedule'; private readonly DialogTitle: string = localize('scheduleDialog.newSchedule', 'New Schedule');
private readonly OkButtonText: string = 'OK'; private readonly OkButtonText: string = localize('scheduleDialog.ok', 'OK');
private readonly CancelButtonText: string = 'Cancel'; private readonly CancelButtonText: string = localize('scheduleDialog.cancel', 'Cancel');
private readonly ScheduleNameText: string = localize('scheduleDialog.scheduleName', 'Schedule Name');
private readonly SchedulesLabelText: string = localize('scheduleDialog.schedules', 'Schedules');
// UI Components // UI Components
private dialog: sqlops.window.modelviewdialog.Dialog; private dialog: sqlops.window.modelviewdialog.Dialog;
@@ -46,7 +50,7 @@ export class ScheduleDialog {
this.schedulesTable = view.modelBuilder.table() this.schedulesTable = view.modelBuilder.table()
.withProperties({ .withProperties({
columns: [ columns: [
'Schedule Name' this.ScheduleNameText
], ],
data: [], data: [],
height: 600, height: 600,
@@ -56,7 +60,7 @@ export class ScheduleDialog {
let formModel = view.modelBuilder.formContainer() let formModel = view.modelBuilder.formContainer()
.withFormItems([{ .withFormItems([{
component: this.schedulesTable, component: this.schedulesTable,
title: 'Schedules' title: this.SchedulesLabelText
}]).withLayout({ width: '100%' }).component(); }]).withLayout({ width: '100%' }).component();
await view.initializeModel(formModel); await view.initializeModel(formModel);

View File

@@ -3,6 +3,8 @@
* 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.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
'use strict'; 'use strict';
import * as nls from 'vscode-nls';
import * as sqlops from 'sqlops'; import * as sqlops from 'sqlops';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import { AlertDialog } from './dialogs/alertDialog'; import { AlertDialog } from './dialogs/alertDialog';
@@ -12,6 +14,8 @@ import { ProxyDialog } from './dialogs/proxyDialog';
import { JobStepDialog } from './dialogs/jobStepDialog'; import { JobStepDialog } from './dialogs/jobStepDialog';
import { PickScheduleDialog } from './dialogs/pickScheduleDialog'; import { PickScheduleDialog } from './dialogs/pickScheduleDialog';
const localize = nls.loadMessageBundle();
/** /**
* The main controller class that initializes the extension * The main controller class that initializes the extension
*/ */
@@ -23,12 +27,17 @@ export class MainController {
this._context = context; this._context = context;
} }
public static showNotYetImplemented(): void {
vscode.window.showInformationMessage(
localize('mainController.notImplemented', "This feature is under development. Check-out the latest insiders build if you'd like to try out the most recent changes!"));
}
/** /**
* Activates the extension * Activates the extension
*/ */
public activate(): void { public activate(): void {
vscode.commands.registerCommand('agent.openCreateJobDialog', (ownerUri: string) => { vscode.commands.registerCommand('agent.openJobDialog', (ownerUri: string, jobInfo: sqlops.AgentJobInfo) => {
let dialog = new JobDialog(ownerUri); let dialog = new JobDialog(ownerUri, jobInfo);
dialog.openDialog(); dialog.openDialog();
}); });
vscode.commands.registerCommand('agent.openNewStepDialog', (ownerUri: string, jobId: string, server: string, stepId: number) => { vscode.commands.registerCommand('agent.openNewStepDialog', (ownerUri: string, jobId: string, server: string, stepId: number) => {
@@ -39,17 +48,19 @@ export class MainController {
let dialog = new PickScheduleDialog(ownerUri); let dialog = new PickScheduleDialog(ownerUri);
dialog.showDialog(); dialog.showDialog();
}); });
vscode.commands.registerCommand('agent.openAlertDialog', (ownerUri: string, alertInfo: sqlops.AgentAlertInfo) => { vscode.commands.registerCommand('agent.openAlertDialog', (ownerUri: string, alertInfo: sqlops.AgentAlertInfo, jobs: string[]) => {
let dialog = new AlertDialog(ownerUri, alertInfo); let dialog = new AlertDialog(ownerUri, alertInfo, jobs);
dialog.openDialog(); dialog.openDialog();
}); });
vscode.commands.registerCommand('agent.openCreateOperatorDialog', (ownerUri: string) => { vscode.commands.registerCommand('agent.openOperatorDialog', (ownerUri: string, operatorInfo: sqlops.AgentOperatorInfo) => {
let dialog = new OperatorDialog(ownerUri); let dialog = new OperatorDialog(ownerUri, operatorInfo);
dialog.openDialog(); dialog.openDialog();
}); });
vscode.commands.registerCommand('agent.openCreateProxyDialog', (ownerUri: string) => { vscode.commands.registerCommand('agent.openProxyDialog', (ownerUri: string, proxyInfo: sqlops.AgentProxyInfo, credentials: sqlops.CredentialInfo[]) => {
let dialog = new ProxyDialog(ownerUri); //@TODO: reenable create proxy after snapping July release (7/14/18)
dialog.openDialog(); // let dialog = new ProxyDialog(ownerUri, proxyInfo, credentials);
// dialog.openDialog();
MainController.showNotYetImplemented();
}); });
} }

View File

@@ -1,6 +1,6 @@
/*--------------------------------------------------------------------------------------------- /*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved. * Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import * as assert from 'assert'; import * as assert from 'assert';
@@ -15,7 +15,7 @@ const testOwnerUri = 'agent://testuri';
suite('Agent extension', () => { suite('Agent extension', () => {
test('Create Job Data', async () => { test('Create Job Data', async () => {
let testAgentService = new TestAgentService(); let testAgentService = new TestAgentService();
let data = new JobData(testOwnerUri, testAgentService); let data = new JobData(testOwnerUri, undefined, testAgentService);
data.save(); data.save();
}); });
}); });

View File

@@ -1,6 +1,6 @@
/*--------------------------------------------------------------------------------------------- /*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved. * Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. 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.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
@@ -86,6 +86,11 @@ export class TestAgentService implements sqlops.AgentServicesProvider {
return undefined; return undefined;
} }
// Agent Credential method
getCredentials(ownerUri: string): Thenable<sqlops.GetCredentialsResult> {
return undefined;
}
// Job Schedule management methods // Job Schedule management methods
getJobSchedules(ownerUri: string): Thenable<sqlops.AgentJobSchedulesResult> { getJobSchedules(ownerUri: string): Thenable<sqlops.AgentJobSchedulesResult> {
return undefined; return undefined;

View File

@@ -658,9 +658,9 @@
} }
}, },
"dependencies": { "dependencies": {
"dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#0.1.9", "dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#0.2.4",
"opener": "^1.4.3", "opener": "^1.4.3",
"service-downloader": "github:anthonydresser/service-downloader#0.1.2", "service-downloader": "github:anthonydresser/service-downloader#0.1.4",
"vscode-extension-telemetry": "^0.0.15" "vscode-extension-telemetry": "^0.0.15"
}, },
"devDependencies": { "devDependencies": {

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.4", "version": "1.5.0-alpha.22",
"downloadFileNames": { "downloadFileNames": {
"Windows_86": "win-x86-netcoreapp2.1.zip", "Windows_86": "win-x86-netcoreapp2.1.zip",
"Windows_64": "win-x64-netcoreapp2.1.zip", "Windows_64": "win-x64-netcoreapp2.1.zip",

View File

@@ -148,6 +148,11 @@ export interface DeleteAgentProxyParams {
proxy: sqlops.AgentProxyInfo; proxy: sqlops.AgentProxyInfo;
} }
// Agent Credentials parameters
export interface GetCredentialsParams {
ownerUri: string;
}
// Job Schedule management parameters // Job Schedule management parameters
export interface AgentJobScheduleParams { export interface AgentJobScheduleParams {
ownerUri: string; ownerUri: string;
@@ -262,6 +267,11 @@ export namespace DeleteAgentProxyRequest {
export const type = new RequestType<DeleteAgentProxyParams, sqlops.ResultStatus, void, void>('agent/deleteproxy'); export const type = new RequestType<DeleteAgentProxyParams, sqlops.ResultStatus, void, void>('agent/deleteproxy');
} }
// Agent Credentials request
export namespace AgentCredentialsRequest {
export const type = new RequestType<GetCredentialsParams, sqlops.GetCredentialsResult, void, void>('security/credentials');
}
// Job Schedules requests // Job Schedules requests
export namespace AgentJobSchedulesRequest { export namespace AgentJobSchedulesRequest {
export const type = new RequestType<AgentJobScheduleParams, sqlops.AgentJobSchedulesResult, void, void>('agent/schedules'); export const type = new RequestType<AgentJobScheduleParams, sqlops.AgentJobSchedulesResult, void, void>('agent/schedules');

View File

@@ -438,6 +438,22 @@ export class AgentServicesFeature extends SqlOpsFeature<undefined> {
); );
}; };
// Agent Credential Method
let getCredentials = (ownerUri: string): Thenable<sqlops.GetCredentialsResult> => {
let params: contracts.GetCredentialsParams = {
ownerUri: ownerUri
};
let requestType = contracts.AgentCredentialsRequest.type;
return client.sendRequest(requestType, params).then(
r => r,
e => {
client.logFailedRequest(requestType, e);
return Promise.resolve(undefined);
}
);
};
// Job Schedule management methods // Job Schedule management methods
let getJobSchedules = (ownerUri: string): Thenable<sqlops.AgentJobSchedulesResult> => { let getJobSchedules = (ownerUri: string): Thenable<sqlops.AgentJobSchedulesResult> => {
let params: contracts.AgentJobScheduleParams = { let params: contracts.AgentJobScheduleParams = {
@@ -532,6 +548,7 @@ export class AgentServicesFeature extends SqlOpsFeature<undefined> {
createProxy, createProxy,
updateProxy, updateProxy,
deleteProxy, deleteProxy,
getCredentials,
getJobSchedules, getJobSchedules,
createJobSchedule, createJobSchedule,
updateJobSchedule, updateJobSchedule,

View File

@@ -44,22 +44,6 @@ export class Telemetry {
private static platformInformation: PlatformInformation; private static platformInformation: PlatformInformation;
private static disabled: boolean; private static disabled: boolean;
// Get the unique ID for the current user of the extension
public static getUserId(): Promise<string> {
return new Promise<string>(resolve => {
// Generate the user id if it has not been created already
if (typeof this.userId === 'undefined') {
let id = Utils.generateUserId();
id.then(newId => {
this.userId = newId;
resolve(this.userId);
});
} else {
resolve(this.userId);
}
});
}
public static getPlatformInformation(): Promise<PlatformInformation> { public static getPlatformInformation(): Promise<PlatformInformation> {
if (this.platformInformation) { if (this.platformInformation) {
return Promise.resolve(this.platformInformation); return Promise.resolve(this.platformInformation);
@@ -143,8 +127,7 @@ export class Telemetry {
} }
// Augment the properties structure with additional common properties before sending // Augment the properties structure with additional common properties before sending
Promise.all([this.getUserId(), this.getPlatformInformation()]).then(() => { Promise.all([this.getPlatformInformation()]).then(() => {
properties['userId'] = this.userId;
properties['distribution'] = (this.platformInformation && this.platformInformation.distribution) ? properties['distribution'] = (this.platformInformation && this.platformInformation.distribution) ?
`${this.platformInformation.distribution.name}, ${this.platformInformation.distribution.version}` : ''; `${this.platformInformation.distribution.name}, ${this.platformInformation.distribution.version}` : '';

View File

@@ -49,9 +49,9 @@ core-util-is@~1.0.0:
version "1.0.2" version "1.0.2"
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"
"dataprotocol-client@github:Microsoft/sqlops-dataprotocolclient#0.1.9": "dataprotocol-client@github:Microsoft/sqlops-dataprotocolclient#0.2.4":
version "0.1.9" version "0.2.4"
resolved "https://codeload.github.com/Microsoft/sqlops-dataprotocolclient/tar.gz/a1a79895cb79658b75d78aa5cfd745855019148d" resolved "https://codeload.github.com/Microsoft/sqlops-dataprotocolclient/tar.gz/b4e2696563d0075e1b674ac8b179b3b8c441f59f"
dependencies: dependencies:
vscode-languageclient "3.5.0" vscode-languageclient "3.5.0"
@@ -296,9 +296,9 @@ semver@^5.3.0:
version "5.5.0" version "5.5.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab" resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab"
"service-downloader@github:anthonydresser/service-downloader#0.1.2": "service-downloader@github:anthonydresser/service-downloader#0.1.4":
version "0.1.2" version "0.1.4"
resolved "https://codeload.github.com/anthonydresser/service-downloader/tar.gz/2aa9b336b6442e17e24693ddc907030575539798" resolved "https://codeload.github.com/anthonydresser/service-downloader/tar.gz/3c0abdf8603aca85d2eacfac3c547173e41bf0c7"
dependencies: dependencies:
decompress "^4.2.0" decompress "^4.2.0"
eventemitter2 "^5.0.1" eventemitter2 "^5.0.1"

View File

@@ -0,0 +1,26 @@
/*---------------------------------------------------------------------------------------------
* 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 sqlops from 'sqlops';
export class CreateSessionData {
public ownerUri: string;
public sessionName: string;
public templates: Array<sqlops.ProfilerSessionTemplate> = new Array<sqlops.ProfilerSessionTemplate>();
constructor(ownerUri: string, templates: Array<sqlops.ProfilerSessionTemplate>) {
this.ownerUri = ownerUri;
this.templates = templates;
}
public getTemplateNames(): string[] {
return this.templates.map(e => e.name);
}
public selectTemplate(name: string): sqlops.ProfilerSessionTemplate {
return this.templates.find((t) => { return t.name === name; });
}
}

View File

@@ -0,0 +1,108 @@
/*---------------------------------------------------------------------------------------------
* 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 nls from 'vscode-nls';
import * as sqlops from 'sqlops';
import * as vscode from 'vscode';
import { CreateSessionData } from '../data/createSessionData';
const localize = nls.loadMessageBundle();
export class CreateSessionDialog {
// Top level
private readonly DialogTitle: string = localize('createSessionDialog.newSession', 'New Session');
private readonly CancelButtonText: string = localize('createSessionDialog.cancel', 'Cancel');
private readonly CreateButtonText: string = localize('createSessionDialog.create', 'Create');
private readonly DialogTitleText: string = localize('createSessionDialog.title', 'Create New Profiler Session');
// UI Components
private dialog: sqlops.window.modelviewdialog.Dialog;
private templatesBox: sqlops.DropDownComponent;
private sessionNameBox: sqlops.InputBoxComponent;
private model: CreateSessionData;
private _onSuccess: vscode.EventEmitter<CreateSessionData> = new vscode.EventEmitter<CreateSessionData>();
public readonly onSuccess: vscode.Event<CreateSessionData> = this._onSuccess.event;
constructor(ownerUri: string, templates: Array<sqlops.ProfilerSessionTemplate>) {
if (typeof (templates) === 'undefined' || templates === null) {
throw new Error(localize('createSessionDialog.templatesInvalid', "Invalid templates list, cannot open dialog"));
}
if (typeof (ownerUri) === 'undefined' || ownerUri === null) {
throw new Error(localize('createSessionDialog.dialogOwnerInvalid', "Invalid dialog owner, cannot open dialog"));
}
this.model = new CreateSessionData(ownerUri, templates);
}
public async showDialog(): Promise<void> {
this.dialog = sqlops.window.modelviewdialog.createDialog(this.DialogTitle);
this.initializeContent();
this.dialog.okButton.onClick(() => this.execute());
this.dialog.cancelButton.onClick(() => { });
this.dialog.okButton.label = this.CreateButtonText;
this.dialog.cancelButton.label = this.CancelButtonText;
sqlops.window.modelviewdialog.openDialog(this.dialog);
}
private initializeContent(): void {
this.dialog.registerContent(async view => {
this.templatesBox = view.modelBuilder.dropDown()
.withProperties({
values: []
}).component();
this.sessionNameBox = view.modelBuilder.inputBox()
.withProperties({
required: true,
multiline: false,
value: ''
}).component();
let formModel = view.modelBuilder.formContainer()
.withFormItems([{
components: [{
component: this.templatesBox,
title: localize('createSessionDialog.selectTemplates', "Select session template:")
},
{
component: this.sessionNameBox,
title: localize('createSessionDialog.enterSessionName', "Enter session name:")
}],
title: this.DialogTitleText
}]).withLayout({ width: '100%' }).component();
await view.initializeModel(formModel);
if (this.model.templates) {
this.templatesBox.values = this.model.getTemplateNames();
}
this.sessionNameBox.onTextChanged(() => {
if (this.sessionNameBox.value.length > 0) {
this.model.sessionName = this.sessionNameBox.value;
this.dialog.okButton.enabled = true;
} else {
this.dialog.okButton.enabled = false;
}
});
});
}
private async execute(): Promise<void> {
let currentConnection = await sqlops.connection.getCurrentConnection();
let profilerService = sqlops.dataprotocol.getProvider<sqlops.ProfilerProvider>(currentConnection.providerName, sqlops.DataProviderType.ProfilerProvider);
let name = this.sessionNameBox.value;
let selected = this.templatesBox.value.toString();
let temp = this.model.selectTemplate(selected);
profilerService.createSession(this.model.ownerUri, this.sessionNameBox.value, temp);
}
}

View File

@@ -5,28 +5,33 @@
'use strict'; 'use strict';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import * as data from 'sqlops'; import * as sqlops from 'sqlops';
import { ApiWrapper } from './apiWrapper'; import { ApiWrapper } from './apiWrapper';
import { CreateSessionDialog } from './dialogs/profilerCreateSessionDialog';
/** /**
* The main controller class that initializes the extension * The main controller class that initializes the extension
*/ */
export class MainController { export class MainController {
protected _apiWrapper: ApiWrapper; protected _apiWrapper: ApiWrapper;
protected _context: vscode.ExtensionContext; protected _context: vscode.ExtensionContext;
// PUBLIC METHODS ////////////////////////////////////////////////////// // PUBLIC METHODS
public constructor(context: vscode.ExtensionContext, apiWrapper?: ApiWrapper) { public constructor(context: vscode.ExtensionContext, apiWrapper?: ApiWrapper) {
this._apiWrapper = apiWrapper || new ApiWrapper(); this._apiWrapper = apiWrapper || new ApiWrapper();
this._context = context; this._context = context;
} }
/** /**
* Deactivates the extension * Deactivates the extension
*/ */
public deactivate(): void { public deactivate(): void {
} }
public activate(): void { public activate(): void {
} vscode.commands.registerCommand('profiler.openCreateSessionDialog', (ownerUri: string, templates: Array<sqlops.ProfilerSessionTemplate>) => {
let dialog = new CreateSessionDialog(ownerUri, templates);
dialog.showDialog();
});
}
} }

View File

@@ -5,4 +5,5 @@
/// <reference path='../../../../../src/vs/vscode.d.ts'/> /// <reference path='../../../../../src/vs/vscode.d.ts'/>
/// <reference path='../../../../../src/sql/sqlops.d.ts'/> /// <reference path='../../../../../src/sql/sqlops.d.ts'/>
/// <reference path='../../../../../src/sql/sqlops.proposed.d.ts'/>
/// <reference types='@types/node'/> /// <reference types='@types/node'/>

View File

@@ -27,7 +27,7 @@
], ],
"contributes": { "contributes": {
"commands": [ "commands": [
{ {
"command": "profiler.newProfiler", "command": "profiler.newProfiler",
"title": "New Profiler", "title": "New Profiler",
@@ -42,12 +42,19 @@
"command": "profiler.stop", "command": "profiler.stop",
"title": "Stop", "title": "Stop",
"category": "Profiler" "category": "Profiler"
},
{
"command": "profiler.openCreateSessionDialog",
"category": "Profiler"
} }
], ],
"outputChannels": [ "outputChannels": [
"sqlprofiler" "sqlprofiler"
] ]
}, },
"dependencies": {
"vscode-nls": "^3.2.1"
},
"devDependencies": { "devDependencies": {
"vscode": "1.0.1" "vscode": "1.0.1"
} }

View File

@@ -2035,6 +2035,10 @@ vinyl@~2.0.1:
remove-trailing-separator "^1.0.1" remove-trailing-separator "^1.0.1"
replace-ext "^1.0.0" replace-ext "^1.0.0"
vscode-nls@^3.2.1:
version "3.2.4"
resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.4.tgz#2166b4183c8aea884d20727f5449e62be69fd398"
vscode@1.0.1: vscode@1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/vscode/-/vscode-1.0.1.tgz#3d161200615fe2af1d92ddc650751159411a513b" resolved "https://registry.yarnpkg.com/vscode/-/vscode-1.0.1.tgz#3d161200615fe2af1d92ddc650751159411a513b"

View File

@@ -12,8 +12,9 @@
["{", "}"], ["{", "}"],
["[", "]"], ["[", "]"],
["(", ")"], ["(", ")"],
["\"", "\""], { "open": "\"", "close": "\"", "notIn": ["string"] },
["'", "'"] { "open": "N'", "close": "'", "notIn": ["string", "comment"] },
{ "open": "'", "close": "'", "notIn": ["string", "comment"] }
], ],
"surroundingPairs": [ "surroundingPairs": [
["{", "}"], ["{", "}"],

View File

@@ -936,11 +936,11 @@
}, },
"_shell_light": { "_shell_light": {
"fontCharacter": "\\E073", "fontCharacter": "\\E073",
"fontColor": "#455155" "fontColor": "#bfc2c1"
}, },
"_shell": { "_shell": {
"fontCharacter": "\\E073", "fontCharacter": "\\E073",
"fontColor": "#4d5a5e" "fontColor": "#d4d7d6"
}, },
"_slim_light": { "_slim_light": {
"fontCharacter": "\\E074", "fontCharacter": "\\E074",
@@ -1374,7 +1374,9 @@
"cmakelists.txt": "_makefile_3", "cmakelists.txt": "_makefile_3",
"procfile": "_heroku", "procfile": "_heroku",
"todo": "_todo", "todo": "_todo",
"npm-debug.log": "_npm_ignored" "npm-debug.log": "_npm_ignored",
"dashboard": "_shell",
"profiler": "_csv"
}, },
"languageIds": { "languageIds": {
"bat": "_windows", "bat": "_windows",
@@ -1409,7 +1411,7 @@
"rust": "_rust", "rust": "_rust",
"scss": "_sass", "scss": "_sass",
"shellscript": "_shell", "shellscript": "_shell",
"sql": "_db", "sql": "_default",
"swift": "_swift", "swift": "_swift",
"typescript": "_typescript", "typescript": "_typescript",
"typescriptreact": "_react", "typescriptreact": "_react",
@@ -1614,7 +1616,7 @@
"rust": "_rust_light", "rust": "_rust_light",
"scss": "_sass_light", "scss": "_sass_light",
"shellscript": "_shell_light", "shellscript": "_shell_light",
"sql": "_db_light", "sql": "_default_light",
"swift": "_swift_light", "swift": "_swift_light",
"typescript": "_typescript_light", "typescript": "_typescript_light",
"typescriptreact": "_react_light", "typescriptreact": "_react_light",
@@ -1660,7 +1662,9 @@
"omakefile": "_makefile_2_light", "omakefile": "_makefile_2_light",
"cmakelists.txt": "_makefile_3_light", "cmakelists.txt": "_makefile_3_light",
"procfile": "_heroku_light", "procfile": "_heroku_light",
"npm-debug.log": "_npm_ignored_light" "npm-debug.log": "_npm_ignored_light",
"dashboard": "_shell_light",
"profiler": "_csv_light"
} }
}, },
"version": "https://github.com/jesseweed/seti-ui/commit/188dda34a56b9555c7d363771264c24f4693983d" "version": "https://github.com/jesseweed/seti-ui/commit/188dda34a56b9555c7d363771264c24f4693983d"

View File

@@ -1,6 +1,6 @@
{ {
"name": "sqlops", "name": "sqlops",
"version": "0.31.2", "version": "0.32.3",
"distro": "8c3e97e3425cc9814496472ab73e076de2ba99ee", "distro": "8c3e97e3425cc9814496472ab73e076de2ba99ee",
"author": { "author": {
"name": "Microsoft Corporation" "name": "Microsoft Corporation"
@@ -34,7 +34,7 @@
"@angular/router": "~4.1.3", "@angular/router": "~4.1.3",
"@angular/upgrade": "~4.1.3", "@angular/upgrade": "~4.1.3",
"angular2-grid": "2.0.6", "angular2-grid": "2.0.6",
"angular2-slickgrid": "git://github.com/Microsoft/angular2-slickgrid.git#1.3.11", "angular2-slickgrid": "github:Microsoft/angular2-slickgrid#1.3.12",
"applicationinsights": "0.18.0", "applicationinsights": "0.18.0",
"chart.js": "^2.6.0", "chart.js": "^2.6.0",
"fast-plist": "0.1.2", "fast-plist": "0.1.2",
@@ -43,8 +43,8 @@
"getmac": "1.0.7", "getmac": "1.0.7",
"graceful-fs": "4.1.11", "graceful-fs": "4.1.11",
"html-query-plan": "git://github.com/anthonydresser/html-query-plan.git#2.4", "html-query-plan": "git://github.com/anthonydresser/html-query-plan.git#2.4",
"http-proxy-agent": "0.2.7", "http-proxy-agent": "^2.1.0",
"https-proxy-agent": "0.3.6", "https-proxy-agent": "^2.2.1",
"iconv-lite": "0.4.19", "iconv-lite": "0.4.19",
"jquery": "3.1.0", "jquery": "3.1.0",
"jschardet": "1.6.0", "jschardet": "1.6.0",
@@ -60,7 +60,7 @@
"reflect-metadata": "^0.1.8", "reflect-metadata": "^0.1.8",
"rxjs": "5.4.0", "rxjs": "5.4.0",
"semver": "4.3.6", "semver": "4.3.6",
"slickgrid": "github:anthonydresser/SlickGrid#2.3.22", "slickgrid": "github:anthonydresser/SlickGrid#2.3.25",
"spdlog": "0.6.0", "spdlog": "0.6.0",
"sudo-prompt": "^8.0.0", "sudo-prompt": "^8.0.0",
"svg.js": "^2.2.5", "svg.js": "^2.2.5",
@@ -127,7 +127,7 @@
"istanbul": "^0.3.17", "istanbul": "^0.3.17",
"jsdom-no-contextify": "^3.1.0", "jsdom-no-contextify": "^3.1.0",
"lazy.js": "^0.4.2", "lazy.js": "^0.4.2",
"mime": "1.2.11", "mime": "^1.4.1",
"minimatch": "^2.0.10", "minimatch": "^2.0.10",
"mkdirp": "^0.5.0", "mkdirp": "^0.5.0",
"mocha": "^2.2.5", "mocha": "^2.2.5",

View File

@@ -30,6 +30,7 @@
"gettingStartedUrl": "https://go.microsoft.com/fwlink/?linkid=862039", "gettingStartedUrl": "https://go.microsoft.com/fwlink/?linkid=862039",
"releaseNotesUrl": "https://go.microsoft.com/fwlink/?linkid=875578", "releaseNotesUrl": "https://go.microsoft.com/fwlink/?linkid=875578",
"documentationUrl": "https://go.microsoft.com/fwlink/?linkid=862277", "documentationUrl": "https://go.microsoft.com/fwlink/?linkid=862277",
"vscodeVersion": "1.23.1",
"commit": "9ca6200018fc206d67a47229f991901a8a453781", "commit": "9ca6200018fc206d67a47229f991901a8a453781",
"date": "2017-12-15T12:00:00.000Z", "date": "2017-12-15T12:00:00.000Z",
"recommendedExtensions": [ "recommendedExtensions": [
@@ -42,4 +43,4 @@
"extensionsGallery": { "extensionsGallery": {
"serviceUrl": "https://sqlopsextensions.blob.core.windows.net/marketplace/v1/extensionsGallery.json" "serviceUrl": "https://sqlopsextensions.blob.core.windows.net/marketplace/v1/extensionsGallery.json"
} }
} }

View File

@@ -37,6 +37,10 @@ See [Paul Randal's wait types library] for more information about each wait type
We would like to thank all our users who raised issues, and in particular the following users who helped contribute fixes: We would like to thank all our users who raised issues, and in particular the following users who helped contribute fixes:
* flyfishingdba for Add square brackets for ms_foreachdb call (#1023) * flyfishingdba for Add square brackets for ms_foreachdb call (#1023)
* Peter-Schneider for Changed the stored procedure call to work on case sensitive instances (#1809)
## What's new in Server Reports v1.3?
* Changed the stored procedure call to work on case sensitive instances
## What's new in Server Reports v1.2? ## What's new in Server Reports v1.2?
* Created left nav bar and added 2 categories for insight widgets: monitor and performance * Created left nav bar and added 2 categories for insight widgets: monitor and performance

View File

@@ -2,7 +2,7 @@
"name": "server-report", "name": "server-report",
"displayName": "Server Reports", "displayName": "Server Reports",
"description": "Server Reports", "description": "Server Reports",
"version": "0.1.2", "version": "0.1.3",
"publisher": "Microsoft", "publisher": "Microsoft",
"preview": true, "preview": true,
"engines": { "engines": {

View File

@@ -100,7 +100,7 @@
"sqlops": "github:anthonydresser/sqlops-extension-sqlops", "sqlops": "github:anthonydresser/sqlops-extension-sqlops",
"tslint": "^3.14.0", "tslint": "^3.14.0",
"typescript": "^2.6.1", "typescript": "^2.6.1",
"vscode": "^1.1.14", "vscode": "^1.1.18",
"@types/handlebars": "^4.0.11", "@types/handlebars": "^4.0.11",
"vsce": "1.36.2" "vsce": "1.36.2"
}, },

View File

@@ -11,6 +11,7 @@ import * as vscode from 'vscode';
import SplitPropertiesPanel from './splitPropertiesPanel'; import SplitPropertiesPanel from './splitPropertiesPanel';
import * as fs from 'fs'; import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
import {TreeNode, TreeDataProvider} from './treeDataProvider';
/** /**
* The main controller class that initializes the extension * The main controller class that initializes the extension
@@ -67,12 +68,83 @@ export default class MainController implements vscode.Disposable {
return Promise.resolve(true); return Promise.resolve(true);
} }
private async getTab3Content(view: sqlops.ModelView): Promise<void> {
let treeData = {
label: '1',
children: [
{
label: '11',
id: '11',
children: [
{
label: '111',
id: '111',
checked: false
},
{
label: '112',
id: '112',
children: [
{
label: '1121',
id: '1121',
checked: true
},
{
label: '1122',
id: '1122',
checked: false
}
]
}
]
},
{
label: '12',
id: '12',
checked: true
}
],
id: '1'
};
let root = TreeNode.createTree(treeData);
let treeDataProvider = new TreeDataProvider(root);
let tree: sqlops.TreeComponent<TreeNode> = view.modelBuilder.tree<TreeNode>().withProperties({
'withCheckbox': true
}).component();
let treeView = tree.registerDataProvider(treeDataProvider);
treeView.onNodeCheckedChanged(item => {
if (item && item.element) {
item.element.changeNodeCheckedState(item.checked);
}
});
treeView.onDidChangeSelection(selectedNodes => {
selectedNodes.forEach(node => {
console.info('tree node selected: ' + node.label);
});
});
let formModel = view.modelBuilder.formContainer()
.withFormItems([{
component: tree,
title: 'Tree'
}], {
horizontal: false,
componentWidth: 800,
componentHeight: 800
}).component();
let formWrapper = view.modelBuilder.loadingComponent().withItem(formModel).component();
formWrapper.loading = false;
await view.initializeModel(formWrapper);
}
private async getTabContent(view: sqlops.ModelView, customButton1: sqlops.window.modelviewdialog.Button, customButton2: sqlops.window.modelviewdialog.Button, componentWidth: number | string): Promise<void> { private async getTabContent(view: sqlops.ModelView, customButton1: sqlops.window.modelviewdialog.Button, customButton2: sqlops.window.modelviewdialog.Button, componentWidth: number | string): Promise<void> {
let inputBox = view.modelBuilder.inputBox() let inputBox = view.modelBuilder.inputBox()
.withProperties({ .withProperties({
multiline: true, multiline: true,
height: 100 height: 100
}).component(); }).component();
let inputBoxWrapper = view.modelBuilder.loadingComponent().withItem(inputBox).component(); let inputBoxWrapper = view.modelBuilder.loadingComponent().withItem(inputBox).component();
inputBoxWrapper.loading = false; inputBoxWrapper.loading = false;
customButton1.onClick(() => { customButton1.onClick(() => {
@@ -88,7 +160,7 @@ export default class MainController implements vscode.Disposable {
}) })
.component(); .component();
checkbox.onChanged(e => { checkbox.onChanged(e => {
console.info("inputBox.enabled " + inputBox.enabled); console.info('inputBox.enabled ' + inputBox.enabled);
inputBox.enabled = !inputBox.enabled; inputBox.enabled = !inputBox.enabled;
}); });
let button = view.modelBuilder.button() let button = view.modelBuilder.button()
@@ -145,8 +217,8 @@ export default class MainController implements vscode.Disposable {
component: inputBox4, component: inputBox4,
title: 'inputBox4' title: 'inputBox4'
}], { }], {
horizontal: true horizontal: true
}).component(); }).component();
let groupModel1 = view.modelBuilder.groupContainer() let groupModel1 = view.modelBuilder.groupContainer()
.withLayout({ .withLayout({
}).withItems([ }).withItems([
@@ -178,8 +250,8 @@ export default class MainController implements vscode.Disposable {
}).component(); }).component();
let declarativeTable = view.modelBuilder.declarativeTable() let declarativeTable = view.modelBuilder.declarativeTable()
.withProperties({ .withProperties({
columns: [{ columns: [{
displayName: 'Column 1', displayName: 'Column 1',
valueType: sqlops.DeclarativeDataType.string, valueType: sqlops.DeclarativeDataType.string,
width: '20px', width: '20px',
@@ -204,12 +276,12 @@ export default class MainController implements vscode.Disposable {
{ name: 'options2', displayName: 'option 2' } { name: 'options2', displayName: 'option 2' }
] ]
} }
], ],
data: [ data: [
['Data00', 'Data01', false, 'options2'], ['Data00', 'Data01', false, 'options2'],
['Data10', 'Data11', true, 'options1'] ['Data10', 'Data11', true, 'options1']
] ]
}).component(); }).component();
declarativeTable.onDataChanged(e => { declarativeTable.onDataChanged(e => {
inputBox2.value = e.row.toString() + ' ' + e.column.toString() + ' ' + e.value.toString(); inputBox2.value = e.row.toString() + ' ' + e.column.toString() + ' ' + e.value.toString();
@@ -223,7 +295,7 @@ export default class MainController implements vscode.Disposable {
height: 150 height: 150
}).withItems([ }).withItems([
radioButton, groupModel1, radioButton2] radioButton, groupModel1, radioButton2]
, { flex: '1 1 50%' }).component(); , { flex: '1 1 50%' }).component();
let formModel = view.modelBuilder.formContainer() let formModel = view.modelBuilder.formContainer()
.withFormItems([{ .withFormItems([{
component: inputBoxWrapper, component: inputBoxWrapper,
@@ -254,9 +326,9 @@ export default class MainController implements vscode.Disposable {
component: listBox, component: listBox,
title: 'List Box' title: 'List Box'
}], { }], {
horizontal: false, horizontal: false,
componentWidth: componentWidth componentWidth: componentWidth
}).component(); }).component();
let formWrapper = view.modelBuilder.loadingComponent().withItem(formModel).component(); let formWrapper = view.modelBuilder.loadingComponent().withItem(formModel).component();
formWrapper.loading = false; formWrapper.loading = false;
customButton2.onClick(() => { customButton2.onClick(() => {
@@ -271,8 +343,9 @@ export default class MainController implements vscode.Disposable {
let tab1 = sqlops.window.modelviewdialog.createTab('Test tab 1'); let tab1 = sqlops.window.modelviewdialog.createTab('Test tab 1');
let tab2 = sqlops.window.modelviewdialog.createTab('Test tab 2'); let tab2 = sqlops.window.modelviewdialog.createTab('Test tab 2');
let tab3 = sqlops.window.modelviewdialog.createTab('Test tab 3');
tab2.content = 'sqlservices'; tab2.content = 'sqlservices';
dialog.content = [tab1, tab2]; dialog.content = [tab1, tab2, tab3];
dialog.okButton.onClick(() => console.log('ok clicked!')); dialog.okButton.onClick(() => console.log('ok clicked!'));
dialog.cancelButton.onClick(() => console.log('cancel clicked!')); dialog.cancelButton.onClick(() => console.log('cancel clicked!'));
dialog.okButton.label = 'ok'; dialog.okButton.label = 'ok';
@@ -286,6 +359,9 @@ export default class MainController implements vscode.Disposable {
await this.getTabContent(view, customButton1, customButton2, 400); await this.getTabContent(view, customButton1, customButton2, 400);
}); });
tab3.registerContent(async (view) => {
await this.getTab3Content(view);
});
sqlops.window.modelviewdialog.openDialog(dialog); sqlops.window.modelviewdialog.openDialog(dialog);
} }
@@ -302,6 +378,18 @@ export default class MainController implements vscode.Disposable {
page1.registerContent(async (view) => { page1.registerContent(async (view) => {
await this.getTabContent(view, customButton1, customButton2, 800); await this.getTabContent(view, customButton1, customButton2, 800);
}); });
/*
wizard.registerOperation({
displayName: 'test task',
description: 'task description',
isCancelable: true
}, op => {
op.updateStatus(sqlops.TaskStatus.InProgress);
op.updateStatus(sqlops.TaskStatus.InProgress, 'Task is running');
setTimeout(() => {
op.updateStatus(sqlops.TaskStatus.Succeeded);
}, 5000);
});*/
wizard.pages = [page1, page2]; wizard.pages = [page1, page2];
wizard.open(); wizard.open();
} }
@@ -344,15 +432,32 @@ export default class MainController implements vscode.Disposable {
webview2.message = count; webview2.message = count;
}); });
let flexModel = view.modelBuilder.flexContainer() let editor1 = view.modelBuilder.editor()
.withLayout({ .withProperties({
flexFlow: 'column', content: 'select * from sys.tables'
alignItems: 'flex-start', })
height: 500
}).withItems([
webview1, webview2
], { flex: '1 1 50%' })
.component(); .component();
let editor2 = view.modelBuilder.editor()
.withProperties({
content: 'print("Hello World !")',
languageMode: 'python'
})
.component();
let flexModel = view.modelBuilder.flexContainer().component();
flexModel.addItem(editor1, { flex: '1' });
flexModel.addItem(editor2, { flex: '1' });
flexModel.setLayout({
flexFlow: 'column',
alignItems: 'stretch',
height: '100%'
});
view.onClosed((params) => {
vscode.window.showInformationMessage('editor1: language: ' + editor1.languageMode + ' Content1: ' + editor1.content);
vscode.window.showInformationMessage('editor2: language: ' + editor2.languageMode + ' Content2: ' + editor2.content);
});
await view.initializeModel(flexModel); await view.initializeModel(flexModel);
}); });
editor.openEditor(); editor.openEditor();
@@ -379,13 +484,14 @@ export default class MainController implements vscode.Disposable {
let monitorLightPath = vscode.Uri.file(path.join(__dirname, '..', 'media', 'monitor.svg')); let monitorLightPath = vscode.Uri.file(path.join(__dirname, '..', 'media', 'monitor.svg'));
let monitorIcon = { let monitorIcon = {
light: monitorLightPath, light: monitorLightPath,
dark: path.join(__dirname, '..', 'media', 'monitor_inverse.svg') }; dark: path.join(__dirname, '..', 'media', 'monitor_inverse.svg')
};
let monitorButton = view.modelBuilder.button() let monitorButton = view.modelBuilder.button()
.withProperties({ .withProperties({
label: 'Monitor', label: 'Monitor',
iconPath: monitorIcon iconPath: monitorIcon
}).component(); }).component();
let toolbarModel = view.modelBuilder.toolbarContainer() let toolbarModel = view.modelBuilder.toolbarContainer()
.withToolbarItems([{ .withToolbarItems([{
component: inputBox, component: inputBox,

View File

@@ -0,0 +1,294 @@
/*---------------------------------------------------------------------------------------------
* 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 path from 'path';
export enum TreeCheckboxState {
Intermediate = 0,
Checked = 1,
Unchecked = 2
}
export interface TreeComponentDataModel {
label?: string;
children?: TreeComponentDataModel[];
id?: string;
checked?: boolean;
}
export class TreeNode implements sqlops.TreeComponentItem {
private _onNodeChange = new vscode.EventEmitter<void>();
private _onTreeChange = new vscode.EventEmitter<TreeNode>();
private _data: TreeComponentDataModel;
private _parent?: TreeNode;
private _root: TreeNode;
private _isAlwaysLeaf: boolean;
private _nodeMap: Map<string, TreeNode>;
private _children: TreeNode[];
public readonly onNodeChange: vscode.Event<void> = this._onNodeChange.event;
public readonly onTreeChange: vscode.Event<TreeNode> = this._onTreeChange.event;
/**
* Creates new instance of tree node
* @param data the underlining data that's bind to the tree node, any change in the tree will affect the same node in data
* @param root the root node of the tree. If passed null, the current node will be the root
*/
constructor(data: TreeComponentDataModel, root: TreeNode) {
if (!data) {
throw new Error(`Invalid tree node data`);
}
if (root === undefined) {
root = this;
root._nodeMap = new Map<string, TreeNode>();
}
this._root = root;
if (this.findNode(data.id)) {
throw new Error(`tree node with id: '${data.id}' already exists`);
}
this._data = data;
}
/**
* id for TreeNode
*/
public get id(): string {
return this.data.id;
}
public set id(value: string) {
this.data.id = value;
}
/**
* Label to display to the user, describing this node
*/
public set label(value: string) {
this.data.label = value;
}
public get label(): string {
return this.data.label;
}
/**
* Is this a leaf node (in which case no children can be generated) or is it expandable?
*/
public get isAlwaysLeaf(): boolean {
return this._isAlwaysLeaf;
}
/**
* Parent of this node
*/
public get parent(): TreeNode {
return this._parent;
}
public get root(): TreeNode {
return this._root;
}
/**
* Path identifying this node
*/
public get nodePath(): string {
return `${this.parent ? this.parent.nodePath + '-' : ''}${this.id}`;
}
public get data(): TreeComponentDataModel {
if (this._data === undefined) {
this._data = {
label: undefined
};
}
return this._data;
}
public changeNodeCheckedState(value: boolean, fromParent?: boolean): void {
if (value !== this.checked) {
if (value !== undefined && this.children) {
this.children.forEach(child => {
child.changeNodeCheckedState(value, true);
});
}
this.checked = value;
if (!fromParent && this.parent) {
this.parent.refreshState();
}
this.onValueChanged();
}
}
public set checked(value: boolean) {
this.data.checked = value;
}
public refreshState(): void {
if (this.hasChildren) {
if (this.children.every(c => c.checked)) {
this.changeNodeCheckedState(true);
} else if (this.children.every(c => c.checked !== undefined && !c.checked)) {
this.changeNodeCheckedState(false);
} else {
this.changeNodeCheckedState(undefined);
}
}
}
public get hasChildren(): boolean {
return this.children !== undefined && this.children.length > 0;
}
public get checked(): boolean {
return this.data.checked;
}
private onValueChanged(): void {
this._onNodeChange.fire();
if (this.root) {
this.root._onTreeChange.fire(this);
}
}
public get checkboxState(): TreeCheckboxState {
if (this.checked === undefined) {
return TreeCheckboxState.Intermediate;
} else {
return this.checked ? TreeCheckboxState.Checked : TreeCheckboxState.Unchecked;
}
}
public findNode(id: string): TreeNode {
if (this.id === id) {
return this;
} else if (this.root) {
return this.root._nodeMap.has(id) ? this.root._nodeMap.get(id) : undefined;
} else {
let node: TreeNode;
if (this.children) {
this.children.forEach(child => {
node = child.findNode(id);
if (node) {
return;
}
});
}
return node;
}
}
/**
* Children of this node
*/
public get children(): TreeNode[] {
return this._children;
}
public addChildNode(node: TreeNode): void {
if (node) {
if (!node.root) {
node._root = this.root;
}
if (!node.parent) {
node._parent = this;
}
if (node.root) {
node.root._nodeMap.set(node.id, node);
}
this._children.push(node);
}
}
public static createNode(nodeData: TreeComponentDataModel, parent?: TreeNode, root?: TreeNode): TreeNode {
let rootNode = root || (parent !== undefined ? parent.root : undefined);
let treeNode = new TreeNode(nodeData, rootNode);
treeNode._parent = parent;
return treeNode;
}
public static createTree(nodeData: TreeComponentDataModel, parent?: TreeNode, root?: TreeNode): TreeNode {
if (nodeData) {
let treeNode = TreeNode.createNode(nodeData, parent, root);
if (nodeData.children && nodeData.children.length > 0) {
treeNode._isAlwaysLeaf = false;
treeNode._children = [];
nodeData.children.forEach(childNode => {
if (childNode) {
let childTreeNode = TreeNode.createTree(childNode, treeNode, root || treeNode.root);
treeNode.addChildNode(childTreeNode);
}
});
treeNode.refreshState();
} else {
treeNode._isAlwaysLeaf = true;
}
return treeNode;
} else {
return undefined;
}
}
}
export class TreeDataProvider implements sqlops.TreeComponentDataProvider<TreeNode> {
private _onDidChangeTreeData = new vscode.EventEmitter<TreeNode>();
constructor(private _root: TreeNode) {
if(this._root) {
this._root.onTreeChange(node => {
this._onDidChangeTreeData.fire(node);
});
}
}
onDidChangeTreeData?: vscode.Event<TreeNode | undefined | null> = this._onDidChangeTreeData.event ;
/**
* Get [TreeItem](#TreeItem) representation of the `element`
*
* @param element The element for which [TreeItem](#TreeItem) representation is asked for.
* @return [TreeItem](#TreeItem) representation of the element
*/
getTreeItem(element: TreeNode): sqlops.TreeComponentItem | Thenable<sqlops.TreeComponentItem> {
let item: sqlops.TreeComponentItem = {};
item.label = element.label;
item.checked = element.checked;
item.iconPath = vscode.Uri.file(path.join(__dirname, '..', 'media', 'monitor.svg'));
return item;
}
/**
* Get the children of `element` or root if no element is passed.
*
* @param element The element from which the provider gets children. Can be `undefined`.
* @return Children of `element` or root if no element is passed.
*/
getChildren(element?: TreeNode): vscode.ProviderResult<TreeNode[]> {
if (element) {
return Promise.resolve(element.children);
} else {
return Promise.resolve(this._root.children);
}
}
getParent(element?: TreeNode): vscode.ProviderResult<TreeNode> {
if (element) {
return Promise.resolve(element.parent);
} else {
return Promise.resolve(this._root);
}
}
}

View File

@@ -233,8 +233,8 @@ export class Dropdown extends Disposable {
this._layoutTree(); this._layoutTree();
return { dispose: () => { } }; return { dispose: () => { } };
}, },
onDOMEvent: (e, activeElement) => { onDOMEvent: e => {
if (!DOM.isAncestor(activeElement, this.$el.getHTMLElement()) && !DOM.isAncestor(activeElement, this.$treeContainer.getHTMLElement())) { if (!DOM.isAncestor(e.srcElement, this.$el.getHTMLElement()) && !DOM.isAncestor(e.srcElement, this.$treeContainer.getHTMLElement())) {
this._input.validate(); this._input.validate();
this._onBlur.fire(); this._onBlur.fire();
this._contextView.hide(); this._contextView.hide();

View File

@@ -0,0 +1,75 @@
/*---------------------------------------------------------------------------------------------
* 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 {
Component, Inject, forwardRef, ElementRef, OnInit, Input,
Output, OnChanges, SimpleChanges, EventEmitter
} from '@angular/core';
import { Dropdown, IDropdownOptions } from 'sql/base/browser/ui/editableDropdown/dropdown';
import { AngularDisposable } from 'sql/base/common/lifecycle';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { attachEditableDropdownStyler } from 'sql/common/theme/styler';
import { IThemeService } from 'vs/platform/theme/common/themeService';
@Component({
selector: 'editable-select-box',
template: ''
})
export class EditableDropDown extends AngularDisposable implements OnInit, OnChanges {
private _selectbox: Dropdown;
@Input() options: string[];
@Input() selectedOption: string;
@Input() onlyEmitOnChange = false;
@Output() onDidSelect = new EventEmitter<string>();
private _previousVal: string;
constructor(
@Inject(forwardRef(() => ElementRef)) private _el: ElementRef,
@Inject(IThemeService) private themeService: IThemeService,
@Inject(IContextViewService) private contextViewService: IContextViewService
) {
super();
}
ngOnInit(): void {
let dropdownOptions: IDropdownOptions = {
values: [],
strictSelection: false,
placeholder: '',
maxHeight: 125,
ariaLabel: '',
actionLabel: ''
};
this._selectbox = new Dropdown(this._el.nativeElement, this.contextViewService, this.themeService, dropdownOptions);
this._selectbox.values = this.options;
this._selectbox.value = this.selectedOption;
this._selectbox.onValueChange(e => {
if (this.onlyEmitOnChange) {
if (this._previousVal !== e) {
this.onDidSelect.emit(e);
this._previousVal = e;
}
} else {
this.onDidSelect.emit(e);
}
});
this._register(attachEditableDropdownStyler(this._selectbox, this.themeService));
}
ngOnChanges(changes: SimpleChanges): void {
}
public get value(): string {
return this._selectbox.value;
}
}

View File

@@ -63,13 +63,13 @@ export class InputBox extends vsInputBox {
this.enabledInputBorder = this.inputBorder; this.enabledInputBorder = this.inputBorder;
this.disabledInputBackground = styles.disabledInputBackground; this.disabledInputBackground = styles.disabledInputBackground;
this.disabledInputForeground = styles.disabledInputForeground; this.disabledInputForeground = styles.disabledInputForeground;
this.updateInputEnabledDisabledColors();
this.applyStyles();
} }
public enable(): void { public enable(): void {
super.enable(); super.enable();
this.inputBackground = this.enabledInputBackground; this.updateInputEnabledDisabledColors();
this.inputForeground = this.enabledInputForeground;
this.inputBorder = this.enabledInputBorder;
this.applyStyles(); this.applyStyles();
} }
@@ -89,9 +89,7 @@ export class InputBox extends vsInputBox {
public disable(): void { public disable(): void {
super.disable(); super.disable();
this.inputBackground = this.disabledInputBackground; this.updateInputEnabledDisabledColors();
this.inputForeground = this.disabledInputForeground;
this.inputBorder = this.disabledInputBorder;
this.applyStyles(); this.applyStyles();
} }
@@ -121,4 +119,11 @@ export class InputBox extends vsInputBox {
super.showMessage(message, force); super.showMessage(message, force);
} }
} }
private updateInputEnabledDisabledColors(): void {
let enabled = this.isEnabled();
this.inputBackground = enabled ? this.enabledInputBackground : this.disabledInputBackground;
this.inputForeground = enabled ? this.enabledInputForeground : this.disabledInputForeground;
this.inputBorder = enabled ? this.enabledInputBorder : this.disabledInputBorder;
}
} }

View File

@@ -17,7 +17,7 @@ export function appendRow(container: Builder, label: string, labelClass: string,
container.element('tr', {}, (rowContainer) => { container.element('tr', {}, (rowContainer) => {
rowContainer.element('td', { class: labelClass }, (labelCellContainer) => { rowContainer.element('td', { class: labelClass }, (labelCellContainer) => {
labelCellContainer.div({}, (labelContainer) => { labelCellContainer.div({}, (labelContainer) => {
labelContainer.innerHtml(label); labelContainer.text(label);
}); });
}); });
rowContainer.element('td', { class: cellContainerClass }, (inputCellContainer) => { rowContainer.element('td', { class: cellContainerClass }, (inputCellContainer) => {
@@ -33,7 +33,7 @@ export function appendRowLink(container: Builder, label: string, labelClass: str
container.element('tr', {}, (rowContainer) => { container.element('tr', {}, (rowContainer) => {
rowContainer.element('td', { class: labelClass }, (labelCellContainer) => { rowContainer.element('td', { class: labelClass }, (labelCellContainer) => {
labelCellContainer.div({}, (labelContainer) => { labelCellContainer.div({}, (labelContainer) => {
labelContainer.innerHtml(label); labelContainer.text(label);
}); });
}); });
rowContainer.element('td', { class: cellContainerClass }, (inputCellContainer) => { rowContainer.element('td', { class: cellContainerClass }, (inputCellContainer) => {

View File

@@ -170,7 +170,7 @@ export abstract class Modal extends Disposable implements IThemable {
} }
modalHeader.div({ class: 'modal-title' }, (modalTitle) => { modalHeader.div({ class: 'modal-title' }, (modalTitle) => {
this._modalTitle = modalTitle; this._modalTitle = modalTitle;
modalTitle.innerHtml(this._title); modalTitle.text(this._title);
}); });
}); });
parts.push(this._modalHeaderSection.getHTMLElement()); parts.push(this._modalHeaderSection.getHTMLElement());
@@ -357,6 +357,21 @@ export abstract class Modal extends Disposable implements IThemable {
return button; return button;
} }
/**
* Returns a footer button matching the provided label
* @param label Label to show on the button
* @param onSelect The callback to call when the button is selected
*/
protected findFooterButton(label: string): Button {
return this._footerButtons.find(e => {
try {
return e && e.element.innerText === label;
} catch {
return false;
}
});
}
/** /**
* Show an error in the error message element * Show an error in the error message element
* @param err Text to show in the error message * @param err Text to show in the error message
@@ -433,7 +448,7 @@ export abstract class Modal extends Disposable implements IThemable {
*/ */
protected set title(title: string) { protected set title(title: string) {
if (this._title !== undefined) { if (this._title !== undefined) {
this._modalTitle.innerHtml(title); this._modalTitle.text(title);
} }
} }

View File

@@ -150,8 +150,8 @@ export class OptionsDialog extends Modal {
private onOptionLinkClicked(optionName: string): void { private onOptionLinkClicked(optionName: string): void {
var option = this._optionElements[optionName].option; var option = this._optionElements[optionName].option;
this._optionTitle.innerHtml(option.displayName); this._optionTitle.text(option.displayName);
this._optionDescription.innerHtml(option.description); this._optionDescription.text(option.description);
} }
private fillInOptions(container: Builder, options: sqlops.ServiceOption[]): void { private fillInOptions(container: Builder, options: sqlops.ServiceOption[]): void {

View File

@@ -5,8 +5,9 @@
import { Component, Input, ContentChild, OnDestroy, TemplateRef, ChangeDetectorRef, forwardRef, Inject } from '@angular/core'; import { Component, Input, ContentChild, OnDestroy, TemplateRef, ChangeDetectorRef, forwardRef, Inject } from '@angular/core';
import { Action } from 'vs/base/common/actions'; import { Action } from 'vs/base/common/actions';
import { Disposable } from 'vs/base/common/lifecycle';
export abstract class TabChild { export abstract class TabChild extends Disposable {
public abstract layout(): void; public abstract layout(): void;
} }
@@ -19,7 +20,7 @@ export abstract class TabChild {
` `
}) })
export class TabComponent implements OnDestroy { export class TabComponent implements OnDestroy {
@ContentChild(TabChild) private _child: TabChild; private _child: TabChild;
@ContentChild(TemplateRef) templateRef; @ContentChild(TemplateRef) templateRef;
@Input() public title: string; @Input() public title: string;
@Input() public canClose: boolean; @Input() public canClose: boolean;
@@ -29,19 +30,30 @@ export class TabComponent implements OnDestroy {
@Input() public identifier: string; @Input() public identifier: string;
@Input() private visibilityType: 'if' | 'visibility' = 'if'; @Input() private visibilityType: 'if' | 'visibility' = 'if';
private rendered = false; private rendered = false;
private destroyed: boolean = false;
@ContentChild(TabChild) private set child(tab: TabChild) {
this._child = tab;
if (this.active && this._child) {
this._child.layout();
}
}
constructor( constructor(
@Inject(forwardRef(() => ChangeDetectorRef)) private _cd: ChangeDetectorRef @Inject(forwardRef(() => ChangeDetectorRef)) private _cd: ChangeDetectorRef
) { } ) { }
public set active(val: boolean) { public set active(val: boolean) {
this._active = val; if (!this.destroyed) {
if (this.active) { this._active = val;
this.rendered = true; if (this.active) {
} this.rendered = true;
this._cd.detectChanges(); }
if (this.active && this._child) { this._cd.detectChanges();
this._child.layout(); if (this.active && this._child) {
this._child.layout();
}
} }
} }
@@ -50,6 +62,7 @@ export class TabComponent implements OnDestroy {
} }
ngOnDestroy() { ngOnDestroy() {
this.destroyed = true;
if (this.actions && this.actions.length > 0) { if (this.actions && this.actions.length > 0) {
this.actions.forEach((action) => action.dispose()); this.actions.forEach((action) => action.dispose());
} }
@@ -74,6 +87,8 @@ export class TabComponent implements OnDestroy {
} }
public layout() { public layout() {
this._child.layout(); if (this._child) {
this._child.layout();
}
} }
} }

View File

@@ -62,7 +62,7 @@ export class TabHeaderComponent extends Disposable implements AfterContentInit,
tabLabelcontainer.classList.add(this.tab.iconClass); tabLabelcontainer.classList.add(this.tab.iconClass);
} else { } else {
tabLabelcontainer.className = 'tabLabel'; tabLabelcontainer.className = 'tabLabel';
tabLabelcontainer.innerHTML = this.tab.title; tabLabelcontainer.textContent = this.tab.title;
} }
tabLabelcontainer.title = this.tab.title; tabLabelcontainer.title = this.tab.title;
} }

View File

@@ -84,7 +84,7 @@
{ {
border: 1px solid #BFBDBD; border: 1px solid #BFBDBD;
font-size: 8pt; font-size: 8pt;
height: 400px; height: 250px;
margin-top: 6px; margin-top: 6px;
overflow: scroll; overflow: scroll;
padding: 4px; padding: 4px;

View File

@@ -0,0 +1,64 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
/**
* Implements the various additional navigation keybindings we want out of slickgrid
*/
export class AdditionalKeyBindings<T> implements Slick.Plugin<T> {
private grid: Slick.Grid<T>;
private handler = new Slick.EventHandler();
public init(grid: Slick.Grid<T>) {
this.grid = grid;
this.handler.subscribe(this.grid.onKeyDown, (e, args) => this.handleKeyDown(e, args));
}
public destroy() {
this.handler.unsubscribeAll();
}
private handleKeyDown(e: KeyboardEvent, args: Slick.OnKeyDownEventArgs<T>): void {
let event = new StandardKeyboardEvent(e);
let handled = true;
if (event.equals(KeyCode.RightArrow | KeyMod.CtrlCmd)) {
this.grid.setActiveCell(args.row, this.grid.getColumns().length - 1);
} else if (event.equals(KeyCode.LeftArrow | KeyMod.CtrlCmd)) {
// account for row column
if (this.grid.canCellBeActive(args.row, 0)) {
this.grid.setActiveCell(args.row, 0);
} else {
this.grid.setActiveCell(args.row, 1);
}
} else if (event.equals(KeyCode.UpArrow | KeyMod.CtrlCmd)) {
this.grid.setActiveCell(0, args.cell);
} else if (event.equals(KeyCode.DownArrow | KeyMod.CtrlCmd)) {
this.grid.setActiveCell(this.grid.getDataLength() - 1, args.cell);
} else if (event.equals(KeyCode.Home | KeyMod.CtrlCmd)) {
// account for row column
if (this.grid.canCellBeActive(0, 0)) {
this.grid.setActiveCell(0, 0);
} else {
this.grid.setActiveCell(0, 1);
}
} else if (event.equals(KeyCode.End | KeyMod.CtrlCmd)) {
this.grid.setActiveCell(this.grid.getDataLength() - 1, this.grid.getColumns().length - 1);
} else {
handled = false;
}
if (handled) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
}
}
}

View File

@@ -16,7 +16,7 @@ export class AutoColumnSize<T> implements Slick.Plugin<T> {
private _context: CanvasRenderingContext2D; private _context: CanvasRenderingContext2D;
private _options: IAutoColumnSizeOptions; private _options: IAutoColumnSizeOptions;
constructor(options: IAutoColumnSizeOptions) { constructor(options: IAutoColumnSizeOptions = defaultOptions) {
this._options = mixin(options, defaultOptions, false); this._options = mixin(options, defaultOptions, false);
} }

View File

@@ -0,0 +1,192 @@
// Drag select selection model gist taken from https://gist.github.com/skoon/5312536
// heavily modified
import { mixin } from 'vs/base/common/objects';
import { isUndefinedOrNull } from 'vs/base/common/types';
import { IThemeService } from 'vs/platform/theme/common/themeService';
require.__$__nodeRequire('slickgrid/plugins/slick.cellrangedecorator');
require.__$__nodeRequire('slickgrid/plugins/slick.cellrangeselector');
export interface ICellRangeSelector<T> extends Slick.Plugin<T> {
onCellRangeSelected: Slick.Event<{ range: Slick.Range }>;
onBeforeCellRangeSelected: Slick.Event<Slick.Cell>;
}
export interface ICellSelectionModelOptions {
cellRangeSelector?: any;
selectActiveCell?: boolean;
}
const defaults: ICellSelectionModelOptions = {
selectActiveCell: true
};
export class CellSelectionModel<T> implements Slick.SelectionModel<T, Array<Slick.Range>> {
private grid: Slick.Grid<T>;
private selector: ICellRangeSelector<T>;
private ranges: Array<Slick.Range> = [];
public onSelectedRangesChanged = new Slick.Event<Array<Slick.Range>>();
constructor(private options: ICellSelectionModelOptions = defaults) {
this.options = mixin(this.options, defaults, false);
if (this.options.cellRangeSelector) {
this.selector = this.options.cellRangeSelector;
} else {
// this is added by the noderequires above
this.selector = new (<any>Slick).CellRangeSelector({ selectionCss: { 'border': '2px dashed grey' } });
}
}
public init(grid: Slick.Grid<T>) {
this.grid = grid;
this.grid.onActiveCellChanged.subscribe((e, args) => this.handleActiveCellChange(e, args));
this.grid.onKeyDown.subscribe(e => this.handleKeyDown(e));
this.grid.onHeaderClick.subscribe((e: MouseEvent, args) => this.handleHeaderClick(e, args));
this.grid.registerPlugin(this.selector);
this.selector.onCellRangeSelected.subscribe((e, args) => this.handleCellRangeSelected(e, args));
this.selector.onBeforeCellRangeSelected.subscribe((e, args) => this.handleBeforeCellRangeSelected(e, args));
}
public destroy() {
this.grid.onActiveCellChanged.unsubscribe((e, args) => this.handleActiveCellChange(e, args));
this.grid.onKeyDown.unsubscribe(e => this.handleKeyDown(e));
this.selector.onCellRangeSelected.unsubscribe((e, args) => this.handleCellRangeSelected(e, args));
this.selector.onBeforeCellRangeSelected.unsubscribe((e, args) => this.handleBeforeCellRangeSelected(e, args));
this.grid.unregisterPlugin(this.selector);
}
private removeInvalidRanges(ranges: Array<Slick.Range>): Array<Slick.Range> {
let result: Array<Slick.Range> = [];
for (let i = 0; i < ranges.length; i++) {
let r = ranges[i];
if (this.grid.canCellBeSelected(r.fromRow, r.fromCell) && this.grid.canCellBeSelected(r.toRow, r.toCell)) {
result.push(r);
} else if (this.grid.canCellBeSelected(r.fromRow, r.fromCell + 1) && this.grid.canCellBeSelected(r.toRow, r.toCell)) {
// account for number row
result.push(new Slick.Range(r.fromRow, r.fromCell + 1, r.toRow, r.toCell));
}
}
return result;
}
public setSelectedRanges(ranges: Array<Slick.Range>): void {
// simple check for: empty selection didn't change, prevent firing onSelectedRangesChanged
if ((!this.ranges || this.ranges.length === 0) && (!ranges || ranges.length === 0)) {
return;
}
this.ranges = this.removeInvalidRanges(ranges);
this.onSelectedRangesChanged.notify(this.ranges);
}
public getSelectedRanges() {
return this.ranges;
}
private handleBeforeCellRangeSelected(e, args: Slick.Cell) {
if (this.grid.getEditorLock().isActive()) {
e.stopPropagation();
return false;
}
return true;
}
private handleCellRangeSelected(e, args: { range: Slick.Range }) {
this.grid.setActiveCell(args.range.fromRow, args.range.fromCell, false, false, true);
this.setSelectedRanges([args.range]);
}
private handleActiveCellChange(e, args) {
if (this.options.selectActiveCell && !isUndefinedOrNull(args.row) && !isUndefinedOrNull(args.cell)) {
this.setSelectedRanges([new Slick.Range(args.row, args.cell)]);
} else if (!this.options.selectActiveCell) {
// clear the previous selection once the cell changes
this.setSelectedRanges([]);
}
}
private handleHeaderClick(e: MouseEvent, args: Slick.OnHeaderClickEventArgs<T>) {
if (!isUndefinedOrNull(args.column)) {
let columnIndex = this.grid.getColumnIndex(args.column.id);
if (this.grid.canCellBeSelected(0, columnIndex)) {
let ranges: Array<Slick.Range>;
if (e.shiftKey) {
ranges = this.getSelectedRanges();
ranges.push(new Slick.Range(0, columnIndex, this.grid.getDataLength() - 1, columnIndex));
} else {
ranges = [new Slick.Range(0, columnIndex, this.grid.getDataLength() - 1, columnIndex)];
}
this.grid.setActiveCell(0, columnIndex);
this.setSelectedRanges(ranges);
}
}
}
private handleKeyDown(e) {
/***
* Кey codes
* 37 left
* 38 up
* 39 right
* 40 down
*/
let ranges, last;
let active = this.grid.getActiveCell();
let metaKey = e.ctrlKey || e.metaKey;
if (active && e.shiftKey && !metaKey && !e.altKey &&
(e.which === 37 || e.which === 39 || e.which === 38 || e.which === 40)) {
ranges = this.getSelectedRanges();
if (!ranges.length) {
ranges.push(new Slick.Range(active.row, active.cell));
}
// keyboard can work with last range only
last = ranges.pop();
// can't handle selection out of active cell
if (!last.contains(active.row, active.cell)) {
last = new Slick.Range(active.row, active.cell);
}
let dRow = last.toRow - last.fromRow,
dCell = last.toCell - last.fromCell,
// walking direction
dirRow = active.row === last.fromRow ? 1 : -1,
dirCell = active.cell === last.fromCell ? 1 : -1;
if (e.which === 37) {
dCell -= dirCell;
} else if (e.which === 39) {
dCell += dirCell;
} else if (e.which === 38) {
dRow -= dirRow;
} else if (e.which === 40) {
dRow += dirRow;
}
// define new selection range
let new_last = new Slick.Range(active.row, active.cell, active.row + dirRow * dRow, active.cell + dirCell * dCell);
if (this.removeInvalidRanges([new_last]).length) {
ranges.push(new_last);
let viewRow = dirRow > 0 ? new_last.toRow : new_last.fromRow;
let viewCell = dirCell > 0 ? new_last.toCell : new_last.fromCell;
this.grid.scrollRowIntoView(viewRow, false);
this.grid.scrollCellIntoView(viewRow, viewCell, false);
} else {
ranges.push(last);
}
this.setSelectedRanges(ranges);
e.preventDefault();
e.stopPropagation();
}
}
}

View File

@@ -1,364 +0,0 @@
// Drag select selection model gist taken from https://gist.github.com/skoon/5312536
// heavily modified
import { clone } from 'sql/base/common/objects';
export class DragCellSelectionModel<T> implements Slick.SelectionModel<T, Array<Slick.Range>> {
private readonly keyColResizeIncr = 5;
private _grid: Slick.Grid<T>;
private _ranges: Array<Slick.Range> = [];
private _dragging = false;
private _handler = new Slick.EventHandler();
public onSelectedRangesChanged = new Slick.Event<Slick.Range[]>();
public init(grid: Slick.Grid<T>): void {
this._grid = grid;
this._handler.subscribe(this._grid.onActiveCellChanged, (e: Event, data: Slick.OnActiveCellChangedEventArgs<T>) => this.handleActiveCellChange(e, data));
this._handler.subscribe(this._grid.onKeyDown, (e: JQueryInputEventObject) => this.handleKeyDown(e));
this._handler.subscribe(this._grid.onClick, (e: MouseEvent) => this.handleClick(e));
this._handler.subscribe(this._grid.onDrag, (e: MouseEvent) => this.handleDrag(e));
this._handler.subscribe(this._grid.onDragInit, (e: MouseEvent) => this.handleDragInit(e));
this._handler.subscribe(this._grid.onDragStart, (e: MouseEvent) => this.handleDragStart(e));
this._handler.subscribe(this._grid.onDragEnd, (e: MouseEvent) => this.handleDragEnd(e));
this._handler.subscribe(this._grid.onHeaderClick, (e: MouseEvent, args: Slick.OnHeaderClickEventArgs<T>) => this.handleHeaderClick(e, args));
}
public destroy(): void {
this._handler.unsubscribeAll();
}
private rangesToRows(ranges: Array<Slick.Range>): Array<number> {
let rows = [];
for (let i = 0; i < ranges.length; i++) {
for (let j = ranges[i].fromRow; j <= ranges[i].toRow; j++) {
rows.push(j);
}
}
return rows;
}
private rowsToRanges(rows: Array<number>): Array<Slick.Range> {
let ranges = [];
let lastCell = this._grid.getColumns().length - 1;
for (let i = 0; i < rows.length; i++) {
ranges.push(new Slick.Range(rows[i], 0, rows[i], lastCell));
}
return ranges;
}
public getSelectedRows(): Array<number> {
return this.rangesToRows(this._ranges);
}
public setSelectedRows(rows: Array<number>) {
this.setSelectedRanges(this.rowsToRanges(rows));
}
public setSelectedRanges(ranges: Array<Slick.Range>) {
this._ranges = ranges;
this.onSelectedRangesChanged.notify(this._ranges);
}
public getSelectedRanges(): Array<Slick.Range> {
return this._ranges;
}
private handleActiveCellChange(e: Event, data: Slick.OnActiveCellChangedEventArgs<T>) { }
private isNavigationKey(e: BaseJQueryEventObject) {
// Nave keys (home, end, arrows) are all in sequential order so use a
switch (e.which) {
case $.ui.keyCode.HOME:
case $.ui.keyCode.END:
case $.ui.keyCode.LEFT:
case $.ui.keyCode.UP:
case $.ui.keyCode.RIGHT:
case $.ui.keyCode.DOWN:
return true;
default:
return false;
}
}
private navigateLeft(e: JQueryInputEventObject, activeCell: Slick.Cell) {
if (activeCell.cell > 1) {
let isHome = e.which === $.ui.keyCode.HOME;
let newActiveCellColumn = isHome ? 1 : activeCell.cell - 1;
// Unsure why but for range, must record 1 index less than expected
let newRangeColumn = newActiveCellColumn - 1;
if (e.shiftKey) {
let last = this._ranges.pop();
// If we are on the rightmost edge of the range and we navigate left,
// we want to deselect the rightmost cell
if (last.fromCell <= newRangeColumn) { last.toCell -= 1; }
let fromRow = Math.min(activeCell.row, last.fromRow);
let fromCell = Math.min(newRangeColumn, last.fromCell);
let toRow = Math.max(activeCell.row, last.toRow);
let toCell = Math.max(newRangeColumn, last.toCell);
this._ranges = [new Slick.Range(fromRow, fromCell, toRow, toCell)];
} else {
this._ranges = [new Slick.Range(activeCell.row, newRangeColumn, activeCell.row, newRangeColumn)];
}
this._grid.setActiveCell(activeCell.row, newActiveCellColumn);
this.setSelectedRanges(this._ranges);
}
}
private navigateRight(e: JQueryInputEventObject, activeCell: Slick.Cell) {
let columnLength = this._grid.getColumns().length;
if (activeCell.cell < columnLength) {
let isEnd = e.which === $.ui.keyCode.END;
let newActiveCellColumn = isEnd ? columnLength : activeCell.cell + 1;
// Unsure why but for range, must record 1 index less than expected
let newRangeColumn = newActiveCellColumn - 1;
if (e.shiftKey) {
let last = this._ranges.pop();
// If we are on the leftmost edge of the range and we navigate right,
// we want to deselect the leftmost cell
if (newRangeColumn <= last.toCell) { last.fromCell += 1; }
let fromRow = Math.min(activeCell.row, last.fromRow);
let fromCell = Math.min(newRangeColumn, last.fromCell);
let toRow = Math.max(activeCell.row, last.toRow);
let toCell = Math.max(newRangeColumn, last.toCell);
this._ranges = [new Slick.Range(fromRow, fromCell, toRow, toCell)];
} else {
this._ranges = [new Slick.Range(activeCell.row, newRangeColumn, activeCell.row, newRangeColumn)];
}
this._grid.setActiveCell(activeCell.row, newActiveCellColumn);
this.setSelectedRanges(this._ranges);
}
}
private handleKeyDown(e: JQueryInputEventObject) {
let activeCell = this._grid.getActiveCell();
if (activeCell) {
// navigation keys
if (this.isNavigationKey(e)) {
e.stopImmediatePropagation();
if (e.ctrlKey || e.metaKey) {
let event = new CustomEvent('gridnav', {
detail: {
which: e.which,
ctrlKey: e.ctrlKey,
metaKey: e.metaKey,
shiftKey: e.shiftKey,
altKey: e.altKey
}
});
window.dispatchEvent(event);
return;
}
// end key
if (e.which === $.ui.keyCode.END) {
this.navigateRight(e, activeCell);
}
// home key
if (e.which === $.ui.keyCode.HOME) {
this.navigateLeft(e, activeCell);
}
// left arrow
if (e.which === $.ui.keyCode.LEFT) {
// column resize
if ((e.ctrlKey || e.metaKey) && e.shiftKey) {
let allColumns = clone(this._grid.getColumns());
allColumns[activeCell.cell - 1].width = allColumns[activeCell.cell - 1].width - this.keyColResizeIncr;
this._grid.setColumnWidths(allColumns);
} else {
this.navigateLeft(e, activeCell);
}
// up arrow
} else if (e.which === $.ui.keyCode.UP && activeCell.row > 0) {
if (e.shiftKey) {
let last = this._ranges.pop();
// If we are on the bottommost edge of the range and we navigate up,
// we want to deselect the bottommost row
let newRangeRow = activeCell.row - 1;
if (last.fromRow <= newRangeRow) { last.toRow -= 1; }
let fromRow = Math.min(activeCell.row - 1, last.fromRow);
let fromCell = Math.min(activeCell.cell - 1, last.fromCell);
let toRow = Math.max(newRangeRow, last.toRow);
let toCell = Math.max(activeCell.cell - 1, last.toCell);
this._ranges = [new Slick.Range(fromRow, fromCell, toRow, toCell)];
} else {
this._ranges = [new Slick.Range(activeCell.row - 1, activeCell.cell - 1, activeCell.row - 1, activeCell.cell - 1)];
}
this._grid.setActiveCell(activeCell.row - 1, activeCell.cell);
this.setSelectedRanges(this._ranges);
// right arrow
} else if (e.which === $.ui.keyCode.RIGHT) {
// column resize
if ((e.ctrlKey || e.metaKey) && e.shiftKey) {
let allColumns = clone(this._grid.getColumns());
allColumns[activeCell.cell - 1].width = allColumns[activeCell.cell - 1].width + this.keyColResizeIncr;
this._grid.setColumnWidths(allColumns);
} else {
this.navigateRight(e, activeCell);
}
// down arrow
} else if (e.which === $.ui.keyCode.DOWN && activeCell.row < this._grid.getDataLength() - 1) {
if (e.shiftKey) {
let last = this._ranges.pop();
// If we are on the topmost edge of the range and we navigate down,
// we want to deselect the topmost row
let newRangeRow = activeCell.row + 1;
if (newRangeRow <= last.toRow) { last.fromRow += 1; }
let fromRow = Math.min(activeCell.row + 1, last.fromRow);
let fromCell = Math.min(activeCell.cell - 1, last.fromCell);
let toRow = Math.max(activeCell.row + 1, last.toRow);
let toCell = Math.max(activeCell.cell - 1, last.toCell);
this._ranges = [new Slick.Range(fromRow, fromCell, toRow, toCell)];
} else {
this._ranges = [new Slick.Range(activeCell.row + 1, activeCell.cell - 1, activeCell.row + 1, activeCell.cell - 1)];
}
this._grid.setActiveCell(activeCell.row + 1, activeCell.cell);
this.setSelectedRanges(this._ranges);
}
}
}
}
private handleHeaderClick(e: MouseEvent, args: Slick.OnHeaderClickEventArgs<T>) {
let columnIndex = this._grid.getColumnIndex(args.column.id);
if (e.ctrlKey || e.metaKey) {
this._ranges.push(new Slick.Range(0, columnIndex, this._grid.getDataLength() - 1, columnIndex));
this._grid.setActiveCell(0, columnIndex + 1);
} else if (e.shiftKey && this._ranges.length) {
let last = this._ranges.pop().fromCell;
let from = Math.min(columnIndex, last);
let to = Math.max(columnIndex, last);
this._ranges = [];
for (let i = from; i <= to; i++) {
if (i !== last) {
this._ranges.push(new Slick.Range(0, i, this._grid.getDataLength() - 1, i));
}
}
this._ranges.push(new Slick.Range(0, last, this._grid.getDataLength() - 1, last));
} else {
this._ranges = [new Slick.Range(0, columnIndex, this._grid.getDataLength() - 1, columnIndex)];
this._grid.resetActiveCell();
}
this.setSelectedRanges(this._ranges);
e.stopImmediatePropagation();
return true;
}
private handleClick(e: MouseEvent) {
let cell = this._grid.getCellFromEvent(e);
if (!cell || !this._grid.canCellBeActive(cell.row, cell.cell)) {
return false;
}
if (!e.ctrlKey && !e.shiftKey && !e.metaKey) {
if (cell.cell !== 0) {
this._ranges = [new Slick.Range(cell.row, cell.cell - 1, cell.row, cell.cell - 1)];
this.setSelectedRanges(this._ranges);
this._grid.setActiveCell(cell.row, cell.cell);
return true;
} else {
this._ranges = [new Slick.Range(cell.row, 0, cell.row, this._grid.getColumns().length - 1)];
this.setSelectedRanges(this._ranges);
this._grid.setActiveCell(cell.row, 1);
return true;
}
}
else if (this._grid.getOptions().multiSelect) {
if (e.ctrlKey || e.metaKey) {
if (cell.cell === 0) {
this._ranges.push(new Slick.Range(cell.row, 0, cell.row, this._grid.getColumns().length - 1));
this._grid.setActiveCell(cell.row, 1);
} else {
this._ranges.push(new Slick.Range(cell.row, cell.cell - 1, cell.row, cell.cell - 1));
this._grid.setActiveCell(cell.row, cell.cell);
}
} else if (this._ranges.length && e.shiftKey) {
let last = this._ranges.pop();
if (cell.cell === 0) {
let fromRow = Math.min(cell.row, last.fromRow);
let toRow = Math.max(cell.row, last.fromRow);
this._ranges = [new Slick.Range(fromRow, 0, toRow, this._grid.getColumns().length - 1)];
} else {
let fromRow = Math.min(cell.row, last.fromRow);
let fromCell = Math.min(cell.cell - 1, last.fromCell);
let toRow = Math.max(cell.row, last.toRow);
let toCell = Math.max(cell.cell - 1, last.toCell);
this._ranges = [new Slick.Range(fromRow, fromCell, toRow, toCell)];
}
}
}
this.setSelectedRanges(this._ranges);
return true;
}
private handleDragInit(e: MouseEvent) {
e.stopImmediatePropagation();
}
private handleDragStart(e: MouseEvent) {
let cell = this._grid.getCellFromEvent(e);
e.stopImmediatePropagation();
this._dragging = true;
if (e.ctrlKey || e.metaKey) {
this._ranges.push(new Slick.Range(cell.row, cell.cell));
this._grid.setActiveCell(cell.row, cell.cell);
} else if (this._ranges.length && e.shiftKey) {
let last = this._ranges.pop();
let fromRow = Math.min(cell.row, last.fromRow);
let fromCell = Math.min(cell.cell - 1, last.fromCell);
let toRow = Math.max(cell.row, last.toRow);
let toCell = Math.max(cell.cell - 1, last.toCell);
this._ranges = [new Slick.Range(fromRow, fromCell, toRow, toCell)];
} else {
this._ranges = [new Slick.Range(cell.row, cell.cell)];
this._grid.setActiveCell(cell.row, cell.cell);
}
this.setSelectedRanges(this._ranges);
}
private handleDrag(e: MouseEvent) {
if (this._dragging) {
let cell = this._grid.getCellFromEvent(e);
let activeCell = this._grid.getActiveCell();
if (!cell || !this._grid.canCellBeActive(cell.row, cell.cell)) {
return false;
}
this._ranges.pop();
if (activeCell.cell === 0) {
let lastCell = this._grid.getColumns().length - 1;
let firstRow = Math.min(cell.row, activeCell.row);
let lastRow = Math.max(cell.row, activeCell.row);
this._ranges.push(new Slick.Range(firstRow, 0, lastRow, lastCell));
} else {
let firstRow = Math.min(cell.row, activeCell.row);
let lastRow = Math.max(cell.row, activeCell.row);
let firstColumn = Math.min(cell.cell - 1, activeCell.cell - 1);
let lastColumn = Math.max(cell.cell - 1, activeCell.cell - 1);
this._ranges.push(new Slick.Range(firstRow, firstColumn, lastRow, lastColumn));
}
this.setSelectedRanges(this._ranges);
return true;
}
return false;
}
private handleDragEnd(e: MouseEvent) {
this._dragging = false;
}
}

View File

@@ -6,6 +6,7 @@ import { mixin } from 'vs/base/common/objects';
import { SlickGrid } from 'angular2-slickgrid'; import { SlickGrid } from 'angular2-slickgrid';
import { Button } from '../../button/button'; import { Button } from '../../button/button';
import { attachButtonStyler } from 'sql/common/theme/styler'; import { attachButtonStyler } from 'sql/common/theme/styler';
import { escape } from 'sql/base/common/strings';
import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IThemeService } from 'vs/platform/theme/common/themeService';
export class HeaderFilter { export class HeaderFilter {
@@ -174,7 +175,7 @@ export class HeaderFilter {
if (filterItems[i] && filterItems[i].indexOf('Error:') < 0) { if (filterItems[i] && filterItems[i].indexOf('Error:') < 0) {
filterOptions += '<label><input type="checkbox" value="' + i + '"' filterOptions += '<label><input type="checkbox" value="' + i + '"'
+ (filtered ? ' checked="checked"' : '') + (filtered ? ' checked="checked"' : '')
+ '/>' + filterItems[i] + '</label>'; + '/>' + escape(filterItems[i]) + '</label>';
} }
} }
let $filter = $('<div class="filter">') let $filter = $('<div class="filter">')

View File

@@ -1,5 +1,6 @@
// Adopted and converted to typescript from https://github.com/6pac/SlickGrid/blob/master/plugins/slick.rowdetailview.js // Adopted and converted to typescript from https://github.com/6pac/SlickGrid/blob/master/plugins/slick.rowdetailview.js
// heavily modified // heavily modified
import { escape } from 'sql/base/common/strings';
import { mixin } from 'vs/base/common/objects'; import { mixin } from 'vs/base/common/objects';
import * as nls from 'vs/nls'; import * as nls from 'vs/nls';
@@ -354,7 +355,7 @@ export class RowDetailView {
html.push("style='height:", dataContext._height, "px;"); //set total height of padding html.push("style='height:", dataContext._height, "px;"); //set total height of padding
html.push("top:", rowHeight, "px'>"); //shift detail below 1st row html.push("top:", rowHeight, "px'>"); //shift detail below 1st row
html.push("<div id='detailViewContainer_", dataContext.id, "' class='detail-container' style='max-height:" + (dataContext._height - rowHeight + bottomMargin) + "px'>"); //sub ctr for custom styling html.push("<div id='detailViewContainer_", dataContext.id, "' class='detail-container' style='max-height:" + (dataContext._height - rowHeight + bottomMargin) + "px'>"); //sub ctr for custom styling
html.push("<div id='innerDetailView_", dataContext.id, "'>", dataContext._detailContent, "</div></div>"); html.push("<div id='innerDetailView_", dataContext.id, "'>", escape(dataContext._detailContent), "</div></div>");
//&omit a final closing detail container </div> that would come next //&omit a final closing detail container </div> that would come next
return html.join(''); return html.join('');

View File

@@ -0,0 +1,70 @@
/*---------------------------------------------------------------------------------------------
* 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 { range } from 'vs/base/common/arrays';
export interface IRowNumberColumnOptions {
numberOfRows: number;
cssClass?: string;
}
const sizePerDigit = 15;
export class RowNumberColumn<T> implements Slick.Plugin<T> {
private handler = new Slick.EventHandler();
private grid: Slick.Grid<T>;
constructor(private options: IRowNumberColumnOptions) {
}
public init(grid: Slick.Grid<T>) {
this.grid = grid;
this.handler
.subscribe(this.grid.onClick, (e, args) => this.handleClick(e, args))
.subscribe(this.grid.onHeaderClick, (e, args) => this.handleHeaderClick(e, args));
}
public destroy() {
this.handler.unsubscribeAll();
}
private handleClick(e: MouseEvent, args: Slick.OnClickEventArgs<T>): void {
if (this.grid.getColumns()[args.cell].id === 'rowNumber') {
this.grid.setActiveCell(args.row, 1);
this.grid.setSelectedRows([args.row]);
}
}
private handleHeaderClick(e: MouseEvent, args: Slick.OnHeaderClickEventArgs<T>): void {
if (args.column.id === 'rowNumber') {
this.grid.setActiveCell(0, 1);
this.grid.setSelectedRows(range(this.grid.getDataLength()));
}
}
public getColumnDefinition(): Slick.Column<T> {
return {
id: 'rowNumber',
name: '',
field: 'rowNumber',
width: this.options.numberOfRows.toString().length * sizePerDigit,
resizable: false,
cssClass: this.options.cssClass,
focusable: false,
selectable: false,
formatter: (r, c, v, cd, dc) => this.formatter(r, c, v, cd, dc)
};
}
private formatter(row, cell, value, columnDef: Slick.Column<T>, dataContext): string {
if (dataContext) {
return `<span>${row}</span>`;
}
return null;
}
}

View File

@@ -14,7 +14,7 @@ export interface IFindPosition {
row: number; row: number;
} }
export function defaultSort<T>(args: Slick.OnSortEventArgs<T>, data: Array<T>): Array<T> { function defaultSort<T>(args: Slick.OnSortEventArgs<T>, data: Array<T>): Array<T> {
let field = args.sortCol.field; let field = args.sortCol.field;
let sign = args.sortAsc ? 1 : -1; let sign = args.sortAsc ? 1 : -1;
return data.sort((a, b) => (a[field] === b[field] ? 0 : (a[field] > b[field] ? 1 : -1)) * sign); return data.sort((a, b) => (a[field] === b[field] ? 0 : (a[field] > b[field] ? 1 : -1)) * sign);

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#424242;}</style></defs><title>add</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,16H0V0H16Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M14,6v4H10v4H6V10H2V6H6V2h4V6Z"/></g><g id="iconBg"><path class="icon-vs-bg" d="M13,7V9H9v4H7V9H3V7H7V3H9V7Z"/></g></svg>

After

Width:  |  Height:  |  Size: 486 B

View File

@@ -18,6 +18,12 @@
background-image: url('start.svg'); background-image: url('start.svg');
} }
.vs .icon.add,
.vs-dark .icon.add,
.hc-black .icon.add {
background-image: url('add.svg');
}
.vs .icon.stop, .vs .icon.stop,
.vs-dark .icon.stop, .vs-dark .icon.stop,
.hc-black .icon.stop { .hc-black .icon.stop {

View File

@@ -82,7 +82,7 @@ export class Taskbar {
public static createTaskbarText(inputText: string): HTMLElement { public static createTaskbarText(inputText: string): HTMLElement {
let element = document.createElement('div'); let element = document.createElement('div');
element.className = 'taskbarTextSeparator'; element.className = 'taskbarTextSeparator';
element.innerHTML = inputText; element.textContent = inputText;
return element; return element;
} }

View File

@@ -15,6 +15,7 @@ export function escape(html: string): string {
case '>': return '&gt;'; case '>': return '&gt;';
case '&': return '&amp;'; case '&': return '&amp;';
case '"': return '&quot;'; case '"': return '&quot;';
case '\'': return '&#39';
default: return match; default: return match;
} }
}); });

View File

@@ -13,3 +13,6 @@ export const SerializationDisabled = 'Saving results into different format disab
*/ */
export const RestoreFeatureName = 'restore'; export const RestoreFeatureName = 'restore';
export const BackupFeatureName = 'backup'; export const BackupFeatureName = 'backup';
export const MssqlProviderId = 'MSSQL';

View File

@@ -81,6 +81,15 @@
content: url("status_info.svg"); content: url("status_info.svg");
} }
.vs .icon.help {
content: url("help.svg");
}
.vs-dark .icon.help,
.hc-black .icon.help {
content: url("help_inverse.svg");
}
.vs .icon.success, .vs .icon.success,
.vs-dark .icon.success, .vs-dark .icon.success,
.hc-black .icon.success { .hc-black .icon.success {

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>help_16x16</title><path d="M10.13.45A8.14,8.14,0,0,1,12,1.23a8,8,0,0,1,1.62,1.21A7.8,7.8,0,0,1,14.91,4a7.59,7.59,0,0,1,.81,1.85,7.6,7.6,0,0,1,0,4.12,7.59,7.59,0,0,1-.81,1.85,7.8,7.8,0,0,1-1.25,1.57A8,8,0,0,1,12,14.62a8.2,8.2,0,0,1-8.08,0,8,8,0,0,1-1.62-1.21,7.8,7.8,0,0,1-1.25-1.57A7.63,7.63,0,0,1,.28,10a7.6,7.6,0,0,1,0-4.12A7.63,7.63,0,0,1,1.09,4,7.8,7.8,0,0,1,2.34,2.44,8,8,0,0,1,4,1.23,8.26,8.26,0,0,1,10.13.45Zm-.27,14a7.17,7.17,0,0,0,1.67-.69,7,7,0,0,0,1.41-1.06,6.75,6.75,0,0,0,1.8-3,6.59,6.59,0,0,0,0-3.6,6.75,6.75,0,0,0-1.8-3,7,7,0,0,0-1.41-1.06,7.17,7.17,0,0,0-1.67-.69,7.21,7.21,0,0,0-3.72,0,7.17,7.17,0,0,0-1.67.69A7,7,0,0,0,3.05,3.13,6.82,6.82,0,0,0,2,4.5a6.72,6.72,0,0,0-.71,1.62,6.59,6.59,0,0,0,0,3.6A6.72,6.72,0,0,0,2,11.35a6.82,6.82,0,0,0,1.09,1.37,7,7,0,0,0,1.41,1.06,7.17,7.17,0,0,0,1.67.69,7.21,7.21,0,0,0,3.72,0ZM9,4a2.75,2.75,0,0,1,.85.56,2.61,2.61,0,0,1,.57.82,2.44,2.44,0,0,1,.21,1,2.08,2.08,0,0,1-.16.85,2.82,2.82,0,0,1-.4.65,4.28,4.28,0,0,1-.51.53c-.18.16-.36.32-.51.48a2.55,2.55,0,0,0-.4.51,1.19,1.19,0,0,0-.16.61v.52H7.46V10a2.09,2.09,0,0,1,.16-.85A2.86,2.86,0,0,1,8,8.5,4.28,4.28,0,0,1,8.54,8c.18-.16.36-.32.51-.48A2.51,2.51,0,0,0,9.45,7a1.18,1.18,0,0,0,.16-.61,1.49,1.49,0,0,0-.13-.61,1.56,1.56,0,0,0-.34-.49A1.63,1.63,0,0,0,8,4.81a1.63,1.63,0,0,0-1.14.45,1.57,1.57,0,0,0-.34.49,1.49,1.49,0,0,0-.13.61H5.32a2.41,2.41,0,0,1,.21-1,2.68,2.68,0,0,1,.58-.82A2.77,2.77,0,0,1,7,4a2.62,2.62,0,0,1,1-.21A2.65,2.65,0,0,1,9,4ZM8.54,12.6H7.46v-1H8.54Z"/></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:#fff;}</style></defs><title>help_inverse_16x16</title><path class="cls-1" d="M10.13.45A8.14,8.14,0,0,1,12,1.23a8,8,0,0,1,1.62,1.21A7.8,7.8,0,0,1,14.91,4a7.59,7.59,0,0,1,.81,1.85,7.6,7.6,0,0,1,0,4.12,7.59,7.59,0,0,1-.81,1.85,7.8,7.8,0,0,1-1.25,1.57A8,8,0,0,1,12,14.62a8.2,8.2,0,0,1-8.08,0,8,8,0,0,1-1.62-1.21,7.8,7.8,0,0,1-1.25-1.57A7.63,7.63,0,0,1,.28,10a7.6,7.6,0,0,1,0-4.12A7.63,7.63,0,0,1,1.09,4,7.8,7.8,0,0,1,2.34,2.44,8,8,0,0,1,4,1.23,8.26,8.26,0,0,1,10.13.45Zm-.27,14a7.17,7.17,0,0,0,1.67-.69,7,7,0,0,0,1.41-1.06,6.75,6.75,0,0,0,1.8-3,6.59,6.59,0,0,0,0-3.6,6.75,6.75,0,0,0-1.8-3,7,7,0,0,0-1.41-1.06,7.17,7.17,0,0,0-1.67-.69,7.21,7.21,0,0,0-3.72,0,7.17,7.17,0,0,0-1.67.69A7,7,0,0,0,3.05,3.13,6.82,6.82,0,0,0,2,4.5a6.72,6.72,0,0,0-.71,1.62,6.59,6.59,0,0,0,0,3.6A6.72,6.72,0,0,0,2,11.35a6.82,6.82,0,0,0,1.09,1.37,7,7,0,0,0,1.41,1.06,7.17,7.17,0,0,0,1.67.69,7.21,7.21,0,0,0,3.72,0ZM9,4a2.75,2.75,0,0,1,.85.56,2.61,2.61,0,0,1,.57.82,2.44,2.44,0,0,1,.21,1,2.08,2.08,0,0,1-.16.85,2.82,2.82,0,0,1-.4.65,4.28,4.28,0,0,1-.51.53c-.18.16-.36.32-.51.48a2.55,2.55,0,0,0-.4.51,1.19,1.19,0,0,0-.16.61v.52H7.46V10a2.09,2.09,0,0,1,.16-.85A2.86,2.86,0,0,1,8,8.5,4.28,4.28,0,0,1,8.54,8c.18-.16.36-.32.51-.48A2.51,2.51,0,0,0,9.45,7a1.18,1.18,0,0,0,.16-.61,1.49,1.49,0,0,0-.13-.61,1.56,1.56,0,0,0-.34-.49A1.63,1.63,0,0,0,8,4.81a1.63,1.63,0,0,0-1.14.45,1.57,1.57,0,0,0-.34.49,1.49,1.49,0,0,0-.13.61H5.32a2.41,2.41,0,0,1,.21-1,2.68,2.68,0,0,1,.58-.82A2.77,2.77,0,0,1,7,4a2.62,2.62,0,0,1,1-.21A2.65,2.65,0,0,1,9,4ZM8.54,12.6H7.46v-1H8.54Z"/></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -131,7 +131,7 @@ export class AccountDialog extends Modal {
this._noaccountViewContainer = DOM.$('div.no-account-view'); this._noaccountViewContainer = DOM.$('div.no-account-view');
let noAccountTitle = DOM.append(this._noaccountViewContainer, DOM.$('.no-account-view-label')); let noAccountTitle = DOM.append(this._noaccountViewContainer, DOM.$('.no-account-view-label'));
let noAccountLabel = localize('accountDialog.noAccountLabel', 'There is no linked account. Please add an account.'); let noAccountLabel = localize('accountDialog.noAccountLabel', 'There is no linked account. Please add an account.');
noAccountTitle.innerHTML = noAccountLabel; noAccountTitle.innerText = noAccountLabel;
// Show the add account button for the first provider // Show the add account button for the first provider
// Todo: If we have more than 1 provider, need to show all add account buttons for all providers // Todo: If we have more than 1 provider, need to show all add account buttons for all providers

View File

@@ -100,7 +100,7 @@ export class AutoOAuthDialog extends Modal {
let inputBox: InputBox; let inputBox: InputBox;
container.div({ class: 'dialog-input-section' }, (inputContainer) => { container.div({ class: 'dialog-input-section' }, (inputContainer) => {
inputContainer.div({ class: 'dialog-label' }, (labelContainer) => { inputContainer.div({ class: 'dialog-label' }, (labelContainer) => {
labelContainer.innerHtml(label); labelContainer.text(label);
}); });
inputContainer.div({ class: 'dialog-input' }, (inputCellContainer) => { inputContainer.div({ class: 'dialog-input' }, (inputCellContainer) => {

View File

@@ -145,7 +145,7 @@ export class FirewallRuleDialog extends Modal {
subnetIPRangeSection = subnetIPRangeContainer.getHTMLElement(); subnetIPRangeSection = subnetIPRangeContainer.getHTMLElement();
subnetIPRangeContainer.div({ 'class': 'dialog-input-section' }, (inputContainer) => { subnetIPRangeContainer.div({ 'class': 'dialog-input-section' }, (inputContainer) => {
inputContainer.div({ 'class': 'dialog-label' }, (labelContainer) => { inputContainer.div({ 'class': 'dialog-label' }, (labelContainer) => {
labelContainer.innerHtml(LocalizedStrings.FROM); labelContainer.text(LocalizedStrings.FROM);
}); });
inputContainer.div({ 'class': 'dialog-input' }, (inputCellContainer) => { inputContainer.div({ 'class': 'dialog-input' }, (inputCellContainer) => {
@@ -155,7 +155,7 @@ export class FirewallRuleDialog extends Modal {
}); });
inputContainer.div({ 'class': 'dialog-label' }, (labelContainer) => { inputContainer.div({ 'class': 'dialog-label' }, (labelContainer) => {
labelContainer.innerHtml(LocalizedStrings.TO); labelContainer.text(LocalizedStrings.TO);
}); });
inputContainer.div({ 'class': 'dialog-input' }, (inputCellContainer) => { inputContainer.div({ 'class': 'dialog-input' }, (inputCellContainer) => {
@@ -234,7 +234,7 @@ export class FirewallRuleDialog extends Modal {
className += ' header'; className += ' header';
} }
container.div({ 'class': className }, (labelContainer) => { container.div({ 'class': className }, (labelContainer) => {
labelContainer.innerHtml(content); labelContainer.text(content);
}); });
} }

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { IExtensionGalleryService, IExtensionTipsService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IExtensionGalleryService, IExtensionTipsService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IEditorRegistry, Extensions as EditorExtensions } from 'vs/workbench/browser/editor'; import { EditorDescriptor, IEditorRegistry, Extensions as EditorExtensions } from 'vs/workbench/browser/editor';
import { IExtensionsWorkbenchService } from 'vs/workbench/parts/extensions/common/extensions'; import { IExtensionsWorkbenchService } from 'vs/workbench/parts/extensions/common/extensions';
import { IConfigurationRegistry, Extensions as ConfigExtensions } from 'vs/platform/configuration/common/configurationRegistry'; 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';
@@ -13,10 +13,9 @@ import { Registry } from 'vs/platform/registry/common/platform';
import { DashboardEditor } from 'sql/parts/dashboard/dashboardEditor'; import { DashboardEditor } from 'sql/parts/dashboard/dashboardEditor';
import { DashboardInput } from 'sql/parts/dashboard/dashboardInput'; import { DashboardInput } from 'sql/parts/dashboard/dashboardInput';
import { AddServerGroupAction, AddServerAction } from 'sql/parts/objectExplorer/viewlet/connectionTreeAction'; import { AddServerGroupAction, AddServerAction } from 'sql/parts/objectExplorer/viewlet/connectionTreeAction';
import { ClearRecentConnectionsAction } from 'sql/parts/connection/common/connectionActions'; import { ClearRecentConnectionsAction, GetCurrentConnectionStringAction } from 'sql/parts/connection/common/connectionActions';
import { ExtensionGalleryService } from 'vs/platform/extensionManagement/node/extensionGalleryService'; import { ExtensionGalleryService } from 'vs/platform/extensionManagement/node/extensionGalleryService';
import { EditorDescriptor } from 'vs/workbench/browser/editor';
import { ExtensionTipsService } from 'vs/workbench/parts/extensions/electron-browser/extensionTipsService'; import { ExtensionTipsService } from 'vs/workbench/parts/extensions/electron-browser/extensionTipsService';
import { ExtensionsWorkbenchService } from 'vs/workbench/parts/extensions/node/extensionsWorkbenchService'; import { ExtensionsWorkbenchService } from 'vs/workbench/parts/extensions/node/extensionsWorkbenchService';
import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions';
@@ -49,6 +48,7 @@ actionRegistry.registerWorkbenchAction(
), ),
ClearRecentConnectionsAction.LABEL ClearRecentConnectionsAction.LABEL
); );
actionRegistry.registerWorkbenchAction( actionRegistry.registerWorkbenchAction(
new SyncActionDescriptor( new SyncActionDescriptor(
AddServerGroupAction, AddServerGroupAction,
@@ -67,6 +67,15 @@ actionRegistry.registerWorkbenchAction(
AddServerAction.LABEL AddServerAction.LABEL
); );
actionRegistry.registerWorkbenchAction(
new SyncActionDescriptor(
GetCurrentConnectionStringAction,
GetCurrentConnectionStringAction.ID,
GetCurrentConnectionStringAction.LABEL
),
GetCurrentConnectionStringAction.LABEL
);
let configurationRegistry = <IConfigurationRegistry>Registry.as(ConfigExtensions.Configuration); let configurationRegistry = <IConfigurationRegistry>Registry.as(ConfigExtensions.Configuration);
configurationRegistry.registerConfiguration({ configurationRegistry.registerConfiguration({
'id': 'connection', 'id': 'connection',

View File

@@ -13,6 +13,11 @@ import { IConnectionManagementService } from 'sql/parts/connection/common/connec
import { INotificationService, INotificationActions } from 'vs/platform/notification/common/notification'; import { INotificationService, INotificationActions } from 'vs/platform/notification/common/notification';
import Severity from 'vs/base/common/severity'; import Severity from 'vs/base/common/severity';
import { IDialogService, IConfirmation, IConfirmationResult } from 'vs/platform/dialogs/common/dialogs'; import { IDialogService, IConfirmation, IConfirmationResult } from 'vs/platform/dialogs/common/dialogs';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IObjectExplorerService } from '../../objectExplorer/common/objectExplorerService';
import { QueryInput } from 'sql/parts/query/common/queryInput';
import { EditDataInput } from 'sql/parts/editData/common/editDataInput';
import { DashboardInput } from 'sql/parts/dashboard/dashboardInput';
/** /**
* Workbench action to clear the recent connnections list * Workbench action to clear the recent connnections list
@@ -126,4 +131,44 @@ export class ClearSingleRecentConnectionAction extends Action {
this._onRecentConnectionRemoved.fire(); this._onRecentConnectionRemoved.fire();
}); });
} }
} }
/**
* Action to retrieve the current connection string
*/
export class GetCurrentConnectionStringAction extends Action {
public static ID = 'getCurrentConnectionStringAction';
public static LABEL = nls.localize('connectionAction.GetCurrentConnectionString', "Get Current Connection String");
constructor(
id: string,
label: string,
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
@IWorkbenchEditorService private _editorService: IWorkbenchEditorService,
@IObjectExplorerService private _objectExplorerService: IObjectExplorerService,
@INotificationService private readonly _notificationService: INotificationService
) {
super(GetCurrentConnectionStringAction.ID, GetCurrentConnectionStringAction.LABEL);
this.enabled = true;
}
public run(): TPromise<void> {
return new TPromise<void>((resolve, reject) => {
let activeInput = this._editorService.getActiveEditorInput();
if (activeInput && (activeInput instanceof QueryInput || activeInput instanceof EditDataInput || activeInput instanceof DashboardInput)
&& this._connectionManagementService.isConnected(activeInput.uri)) {
let includePassword = false;
this._connectionManagementService.getConnectionString(activeInput.uri, includePassword).then(result => {
let message = result
? result
: nls.localize('connectionAction.connectionString', "Connection string not available");
this._notificationService.info(message);
});
} else {
let message = nls.localize('connectionAction.noConnection', "No active connection available");
this._notificationService.info(message);
}
});
}
}

View File

@@ -8,7 +8,7 @@
import { RawContextKey, IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { RawContextKey, IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IConnectionProfile } from 'sqlops'; import { IConnectionProfile } from 'sqlops';
export class ConnectionContextkey implements IContextKey<IConnectionProfile> { export class ConnectionContextKey implements IContextKey<IConnectionProfile> {
static Provider = new RawContextKey<string>('connectionProvider', undefined); static Provider = new RawContextKey<string>('connectionProvider', undefined);
static Server = new RawContextKey<string>('serverName', undefined); static Server = new RawContextKey<string>('serverName', undefined);
@@ -23,10 +23,10 @@ export class ConnectionContextkey implements IContextKey<IConnectionProfile> {
constructor( constructor(
@IContextKeyService contextKeyService: IContextKeyService @IContextKeyService contextKeyService: IContextKeyService
) { ) {
this._providerKey = ConnectionContextkey.Provider.bindTo(contextKeyService); this._providerKey = ConnectionContextKey.Provider.bindTo(contextKeyService);
this._serverKey = ConnectionContextkey.Server.bindTo(contextKeyService); this._serverKey = ConnectionContextKey.Server.bindTo(contextKeyService);
this._databaseKey = ConnectionContextkey.Database.bindTo(contextKeyService); this._databaseKey = ConnectionContextKey.Database.bindTo(contextKeyService);
this._connectionKey = ConnectionContextkey.Connection.bindTo(contextKeyService); this._connectionKey = ConnectionContextKey.Connection.bindTo(contextKeyService);
} }
set(value: IConnectionProfile) { set(value: IConnectionProfile) {

View File

@@ -152,10 +152,12 @@ export interface IConnectionManagementService {
getAdvancedProperties(): sqlops.ConnectionOption[]; getAdvancedProperties(): sqlops.ConnectionOption[];
getConnectionId(connectionProfile: IConnectionProfile): string; getConnectionUri(connectionProfile: IConnectionProfile): string;
getFormattedUri(uri: string, connectionProfile: IConnectionProfile): string; getFormattedUri(uri: string, connectionProfile: IConnectionProfile): string;
getConnectionUriFromId(connectionId: string): string;
isConnected(fileUri: string): boolean; isConnected(fileUri: string): boolean;
/** /**
@@ -174,7 +176,7 @@ export interface IConnectionManagementService {
disconnectEditor(owner: IConnectableInput, force?: boolean): Promise<boolean>; disconnectEditor(owner: IConnectableInput, force?: boolean): Promise<boolean>;
disconnect(connection: ConnectionProfile): Promise<void>; disconnect(connection: IConnectionProfile): Promise<void>;
disconnect(ownerUri: string): Promise<void>; disconnect(ownerUri: string): Promise<void>;
@@ -208,7 +210,7 @@ export interface IConnectionManagementService {
*/ */
cancelEditorConnection(owner: IConnectableInput): Thenable<boolean>; cancelEditorConnection(owner: IConnectableInput): Thenable<boolean>;
showDashboard(connection: ConnectionProfile): Thenable<boolean>; showDashboard(connection: IConnectionProfile): Thenable<boolean>;
closeDashboard(uri: string): void; closeDashboard(uri: string): void;
@@ -216,7 +218,7 @@ export interface IConnectionManagementService {
hasRegisteredServers(): boolean; hasRegisteredServers(): boolean;
canChangeConnectionConfig(profile: ConnectionProfile, newGroupID: string): boolean; canChangeConnectionConfig(profile: IConnectionProfile, newGroupID: string): boolean;
getTabColorForUri(uri: string): string; getTabColorForUri(uri: string): string;
@@ -258,6 +260,11 @@ export interface IConnectionManagementService {
* in the connection profile's options dictionary, or undefined if the profile is not connected * in the connection profile's options dictionary, or undefined if the profile is not connected
*/ */
getActiveConnectionCredentials(profileId: string): { [name: string]: string }; getActiveConnectionCredentials(profileId: string): { [name: string]: string };
/**
* Get the connection string for the provided connection profile
*/
getConnectionString(ownerUri: string, includePassword: boolean): Thenable<string>;
} }
export const IConnectionDialogService = createDecorator<IConnectionDialogService>('connectionDialogService'); export const IConnectionDialogService = createDecorator<IConnectionDialogService>('connectionDialogService');
@@ -320,12 +327,13 @@ export enum MetadataType {
} }
export enum TaskStatus { export enum TaskStatus {
notStarted = 0, NotStarted = 0,
inProgress = 1, InProgress = 1,
succeeded = 2, Succeeded = 2,
succeededWithWarning = 3, SucceededWithWarning = 3,
failed = 4, Failed = 4,
canceled = 5 Canceled = 5,
Canceling = 6
} }
export interface IConnectionParams { export interface IConnectionParams {

View File

@@ -553,7 +553,7 @@ export class ConnectionManagementService extends Disposable implements IConnecti
}); });
} }
public showDashboard(connection: ConnectionProfile): Thenable<boolean> { public showDashboard(connection: IConnectionProfile): Thenable<boolean> {
return this.showDashboardForConnectionManagementInfo(connection); return this.showDashboardForConnectionManagementInfo(connection);
} }
@@ -648,6 +648,15 @@ export class ConnectionManagementService extends Disposable implements IConnecti
return this._connectionStatusManager.getActiveConnectionProfiles(); return this._connectionStatusManager.getActiveConnectionProfiles();
} }
public getConnectionUriFromId(connectionId: string): string {
let connection = this.getActiveConnections().find(connection => connection.id === connectionId);
if (connection) {
return this.getConnectionUri(connection);
} else {
return undefined;
}
}
public saveProfileGroup(profile: IConnectionProfileGroup): Promise<string> { public saveProfileGroup(profile: IConnectionProfileGroup): Promise<string> {
TelemetryUtils.addTelemetry(this._telemetryService, TelemetryKeys.AddServerGroup); TelemetryUtils.addTelemetry(this._telemetryService, TelemetryKeys.AddServerGroup);
return new Promise<string>((resolve, reject) => { return new Promise<string>((resolve, reject) => {
@@ -704,7 +713,7 @@ export class ConnectionManagementService extends Disposable implements IConnecti
return false; return false;
} }
public getConnectionId(connectionProfile: IConnectionProfile): string { public getConnectionUri(connectionProfile: IConnectionProfile): string {
return this._connectionStatusManager.getOriginalOwnerUri(Utils.generateUri(connectionProfile)); return this._connectionStatusManager.getOriginalOwnerUri(Utils.generateUri(connectionProfile));
} }
@@ -716,7 +725,7 @@ export class ConnectionManagementService extends Disposable implements IConnecti
*/ */
public getFormattedUri(uri: string, connectionProfile: IConnectionProfile): string { public getFormattedUri(uri: string, connectionProfile: IConnectionProfile): string {
if (this._connectionStatusManager.isDefaultTypeUri(uri)) { if (this._connectionStatusManager.isDefaultTypeUri(uri)) {
return this.getConnectionId(connectionProfile); return this.getConnectionUri(connectionProfile);
} else { } else {
return uri; return uri;
} }
@@ -1111,7 +1120,8 @@ export class ConnectionManagementService extends Disposable implements IConnecti
resolve(result); resolve(result);
}); });
} else { } else {
resolve(self.disconnectEditor(owner)); // If the editor is connected then there is nothing to cancel
resolve(false);
} }
}); });
} }
@@ -1336,4 +1346,24 @@ export class ConnectionManagementService extends Disposable implements IConnecti
credentials[passwordOption.name] = profile.options[passwordOption.name]; credentials[passwordOption.name] = profile.options[passwordOption.name];
return credentials; return credentials;
} }
/**
* Get the connection string for the provided connection profile
*/
public getConnectionString(ownerUri: string, includePassword: boolean = false): Thenable<string> {
if (!ownerUri) {
return Promise.resolve(undefined);
}
let providerId = this.getProviderIdFromUri(ownerUri);
if (!providerId) {
return Promise.resolve(undefined);
}
return this._providers.get(providerId).onReady.then(provider => {
return provider.getConnectionString(ownerUri, includePassword).then(connectionString => {
return connectionString;
});
});
}
} }

View File

@@ -240,7 +240,7 @@ export class ConnectionStore {
return connectionProfile; return connectionProfile;
} else { } else {
return undefined; return undefined;
}; }
}); });
} }
@@ -352,7 +352,7 @@ export class ConnectionStore {
list.unshift(savedProfile); list.unshift(savedProfile);
let newList = list.map(c => { let newList = list.map(c => {
let connectionProfile = c ? c.toIConnectionProfile() : undefined;; let connectionProfile = c ? c.toIConnectionProfile() : undefined;
return connectionProfile; return connectionProfile;
}); });
return newList.filter(n => n !== undefined); return newList.filter(n => n !== undefined);
@@ -372,7 +372,7 @@ export class ConnectionStore {
}); });
let newList = list.map(c => { let newList = list.map(c => {
let connectionProfile = c ? c.toIConnectionProfile() : undefined;; let connectionProfile = c ? c.toIConnectionProfile() : undefined;
return connectionProfile; return connectionProfile;
}); });
return newList.filter(n => n !== undefined); return newList.filter(n => n !== undefined);

View File

@@ -63,7 +63,7 @@ export class ConnectionController implements IConnectionComponentController {
tempProfile.password = password; tempProfile.password = password;
tempProfile.groupFullName = ''; tempProfile.groupFullName = '';
tempProfile.saveProfile = false; tempProfile.saveProfile = false;
let uri = this._connectionManagementService.getConnectionId(tempProfile); let uri = this._connectionManagementService.getConnectionUri(tempProfile);
return new Promise<string[]>((resolve, reject) => { return new Promise<string[]>((resolve, reject) => {
if (this._databaseCache.has(uri)) { if (this._databaseCache.has(uri)) {
let cachedDatabases: string[] = this._databaseCache.get(uri); let cachedDatabases: string[] = this._databaseCache.get(uri);

View File

@@ -144,9 +144,6 @@ export class ConnectionDialogService implements IConnectionDialogService {
} else { } else {
this._connectionManagementService.cancelConnection(this._model); this._connectionManagementService.cancelConnection(this._model);
} }
if (params && params.input && params.input.onConnectReject) {
params.input.onConnectReject();
}
this._connectionDialog.resetConnection(); this._connectionDialog.resetConnection();
this._connecting = false; this._connecting = false;
} }
@@ -339,9 +336,9 @@ export class ConnectionDialogService implements IConnectionDialogService {
if (!platform.isWindows && types.isString(message) && message.toLowerCase().includes('kerberos') && message.toLowerCase().includes('kinit')) { if (!platform.isWindows && types.isString(message) && message.toLowerCase().includes('kerberos') && message.toLowerCase().includes('kinit')) {
message = [ message = [
localize('kerberosErrorStart', "Connection failed due to Kerberos error."), localize('kerberosErrorStart', "Connection failed due to Kerberos error."),
localize('kerberosHelpLink', "&nbsp;Help configuring Kerberos is available at ") + helpLink, localize('kerberosHelpLink', "Help configuring Kerberos is available at {0}", helpLink),
localize('kerberosKinit', "&nbsp;If you have previously connected you may need to re-run kinit.") localize('kerberosKinit', "If you have previously connected you may need to re-run kinit.")
].join('<br/>'); ].join('\r\n');
actions.push(new Action('Kinit', 'Run kinit', null, true, () => { actions.push(new Action('Kinit', 'Run kinit', null, true, () => {
this._connectionDialog.close(); this._connectionDialog.close();
this._clipboardService.writeText('kinit\r'); this._clipboardService.writeText('kinit\r');

View File

@@ -262,7 +262,7 @@ export class ConnectionDialogWidget extends Modal {
let recentHistoryLabel = localize('recentHistory', 'Recent history'); let recentHistoryLabel = localize('recentHistory', 'Recent history');
recentConnectionContainer.div({ class: 'recent-titles-container' }, (container) => { recentConnectionContainer.div({ class: 'recent-titles-container' }, (container) => {
container.div({ class: 'connection-history-label' }, (recentTitle) => { container.div({ class: 'connection-history-label' }, (recentTitle) => {
recentTitle.innerHtml(recentHistoryLabel); recentTitle.text(recentHistoryLabel);
}); });
container.div({ class: 'connection-history-actions' }, (actionsContainer) => { container.div({ class: 'connection-history-actions' }, (actionsContainer) => {
this._actionbar = this._register(new ActionBar(actionsContainer.getHTMLElement(), { animated: false })); this._actionbar = this._register(new ActionBar(actionsContainer.getHTMLElement(), { animated: false }));
@@ -303,7 +303,7 @@ export class ConnectionDialogWidget extends Modal {
this._noRecentConnectionBuilder.div({ class: 'connection-recent-content' }, (noRecentConnectionContainer) => { this._noRecentConnectionBuilder.div({ class: 'connection-recent-content' }, (noRecentConnectionContainer) => {
let noRecentHistoryLabel = localize('noRecentConnections', 'No recent connection'); let noRecentHistoryLabel = localize('noRecentConnections', 'No recent connection');
noRecentConnectionContainer.div({ class: 'no-recent-connections' }, (noRecentTitle) => { noRecentConnectionContainer.div({ class: 'no-recent-connections' }, (noRecentTitle) => {
noRecentTitle.innerHtml(noRecentHistoryLabel); noRecentTitle.text(noRecentHistoryLabel);
}); });
}); });
} }
@@ -335,7 +335,7 @@ export class ConnectionDialogWidget extends Modal {
this._noSavedConnectionBuilder.div({ class: 'connection-saved-content' }, (noSavedConnectionContainer) => { this._noSavedConnectionBuilder.div({ class: 'connection-saved-content' }, (noSavedConnectionContainer) => {
let noSavedConnectionLabel = localize('noSavedConnections', 'No saved connection'); let noSavedConnectionLabel = localize('noSavedConnections', 'No saved connection');
noSavedConnectionContainer.div({ class: 'no-saved-connections' }, (titleContainer) => { noSavedConnectionContainer.div({ class: 'no-saved-connections' }, (titleContainer) => {
titleContainer.innerHtml(noSavedConnectionLabel); titleContainer.text(noSavedConnectionLabel);
}); });
}); });
} }

View File

@@ -22,6 +22,7 @@
border-top-color: transparent; border-top-color: transparent;
height: calc(100% - 350px); height: calc(100% - 350px);
display: block; display: block;
min-height: 120px;
} }
.connection-recent, .connection-saved { .connection-recent, .connection-saved {

View File

@@ -32,22 +32,10 @@ export abstract class DashboardTab extends TabChild implements OnDestroy {
public enableEdit(): void { public enableEdit(): void {
// no op // no op
} }
private _toDispose: IDisposable[] = [];
constructor() { constructor() {
super(); super();
} }
public dispose(): void {
this._toDispose = dispose(this._toDispose);
}
protected _register<T extends IDisposable>(t: T): T {
this._toDispose.push(t);
return t;
}
ngOnDestroy() { ngOnDestroy() {
this.dispose(); this.dispose();
} }

View File

@@ -40,7 +40,7 @@ export class DashboardErrorContainer extends DashboardTab implements AfterViewIn
ngAfterViewInit() { ngAfterViewInit() {
let errorMessage = this._errorMessageContainer.nativeElement as HTMLElement; let errorMessage = this._errorMessageContainer.nativeElement as HTMLElement;
errorMessage.innerHTML = nls.localize('dashboardNavSection_loadTabError', 'The "{0}" section has invalid content. Please contact extension owner.', this.tab.title); errorMessage.innerText = nls.localize('dashboardNavSection_loadTabError', 'The "{0}" section has invalid content. Please contact extension owner.', this.tab.title);
} }
public get id(): string { public get id(): string {

View File

@@ -57,13 +57,14 @@ import { OperatorsViewComponent } from 'sql/parts/jobManagement/views/operatorsV
import { ProxiesViewComponent } from 'sql/parts/jobManagement/views/proxiesView.component'; import { ProxiesViewComponent } from 'sql/parts/jobManagement/views/proxiesView.component';
import { Checkbox } from 'sql/base/browser/ui/checkbox/checkbox.component'; import { Checkbox } from 'sql/base/browser/ui/checkbox/checkbox.component';
import { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox.component'; import { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox.component';
import { EditableDropDown } from 'sql/base/browser/ui/editableDropdown/editableDropdown.component';
import { InputBox } from 'sql/base/browser/ui/inputBox/inputBox.component'; import { InputBox } from 'sql/base/browser/ui/inputBox/inputBox.component';
let baseComponents = [DashboardHomeContainer, DashboardComponent, DashboardWidgetWrapper, DashboardWebviewContainer, let baseComponents = [DashboardHomeContainer, DashboardComponent, DashboardWidgetWrapper, DashboardWebviewContainer,
DashboardWidgetContainer, DashboardGridContainer, DashboardErrorContainer, DashboardNavSection, ModelViewContent, WebviewContent, WidgetContent, DashboardWidgetContainer, DashboardGridContainer, DashboardErrorContainer, DashboardNavSection, ModelViewContent, WebviewContent, WidgetContent,
ComponentHostDirective, BreadcrumbComponent, ControlHostContent, DashboardControlHostContainer, ComponentHostDirective, BreadcrumbComponent, ControlHostContent, DashboardControlHostContainer,
JobsViewComponent, AgentViewComponent, JobHistoryComponent, JobStepsViewComponent, AlertsViewComponent, ProxiesViewComponent, OperatorsViewComponent, JobsViewComponent, AgentViewComponent, JobHistoryComponent, JobStepsViewComponent, AlertsViewComponent, ProxiesViewComponent, OperatorsViewComponent,
DashboardModelViewContainer, ModelComponentWrapper, Checkbox, SelectBox, InputBox,]; DashboardModelViewContainer, ModelComponentWrapper, Checkbox, EditableDropDown, SelectBox, InputBox,];
/* Panel */ /* Panel */
import { PanelModule } from 'sql/base/browser/ui/panel/panel.module'; import { PanelModule } from 'sql/base/browser/ui/panel/panel.module';

View File

@@ -18,7 +18,7 @@ import { DashboardModule } from './dashboard.module';
import { bootstrapAngular } from 'sql/services/bootstrap/bootstrapService'; import { bootstrapAngular } from 'sql/services/bootstrap/bootstrapService';
import { IDashboardComponentParams } from 'sql/services/bootstrap/bootstrapParams'; import { IDashboardComponentParams } from 'sql/services/bootstrap/bootstrapParams';
import { DASHBOARD_SELECTOR } from 'sql/parts/dashboard/dashboard.component'; import { DASHBOARD_SELECTOR } from 'sql/parts/dashboard/dashboard.component';
import { ConnectionContextkey } from 'sql/parts/connection/common/connectionContextKey'; import { ConnectionContextKey } from 'sql/parts/connection/common/connectionContextKey';
import { IDashboardService } from 'sql/services/dashboard/common/dashboardService'; import { IDashboardService } from 'sql/services/dashboard/common/dashboardService';
import { ConnectionProfile } from 'sql/parts/connection/common/connectionProfile'; import { ConnectionProfile } from 'sql/parts/connection/common/connectionProfile';
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces'; import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
@@ -71,6 +71,7 @@ export class DashboardEditor extends BaseEditor {
* To be called when the container of this editor changes size. * To be called when the container of this editor changes size.
*/ */
public layout(dimension: DOM.Dimension): void { public layout(dimension: DOM.Dimension): void {
this._dashboardService.layout(dimension);
} }
public setInput(input: DashboardInput, options: EditorOptions): TPromise<void> { public setInput(input: DashboardInput, options: EditorOptions): TPromise<void> {
@@ -110,7 +111,7 @@ export class DashboardEditor extends BaseEditor {
let serverInfo = this._connMan.getConnectionInfo(this.input.uri).serverInfo; let serverInfo = this._connMan.getConnectionInfo(this.input.uri).serverInfo;
this._dashboardService.changeToDashboard({ profile, serverInfo }); this._dashboardService.changeToDashboard({ profile, serverInfo });
let scopedContextService = this._contextKeyService.createScoped(input.container); let scopedContextService = this._contextKeyService.createScoped(input.container);
let connectionContextKey = new ConnectionContextkey(scopedContextService); let connectionContextKey = new ConnectionContextKey(scopedContextService);
connectionContextKey.set(input.connectionProfile); connectionContextKey.set(input.connectionProfile);
let params: IDashboardComponentParams = { let params: IDashboardComponentParams = {

View File

@@ -163,7 +163,7 @@ export class NewDashboardTabDialog extends Modal {
this._noExtensionViewContainer = DOM.$('.no-extension-view'); this._noExtensionViewContainer = DOM.$('.no-extension-view');
let noExtensionTitle = DOM.append(this._noExtensionViewContainer, DOM.$('.no-extensionTab-label')); let noExtensionTitle = DOM.append(this._noExtensionViewContainer, DOM.$('.no-extensionTab-label'));
let noExtensionLabel = localize('newdashboardTabDialog.noExtensionLabel', 'No dashboard extensions are installed at this time. Go to Extension Manager to explore recommended extensions.'); let noExtensionLabel = localize('newdashboardTabDialog.noExtensionLabel', 'No dashboard extensions are installed at this time. Go to Extension Manager to explore recommended extensions.');
noExtensionTitle.innerHTML = noExtensionLabel; noExtensionTitle.textContent = noExtensionLabel;
DOM.append(container, this._noExtensionViewContainer); DOM.append(container, this._noExtensionViewContainer);
} }

View File

@@ -15,7 +15,6 @@ import {
import { ICapabilitiesService } from 'sql/services/capabilities/capabilitiesService'; import { ICapabilitiesService } from 'sql/services/capabilities/capabilitiesService';
import { ConnectionManagementInfo } from 'sql/parts/connection/common/connectionManagementInfo'; import { ConnectionManagementInfo } from 'sql/parts/connection/common/connectionManagementInfo';
import * as Constants from 'sql/parts/connection/common/constants'; import * as Constants from 'sql/parts/connection/common/constants';
import { OEAction } from 'sql/parts/objectExplorer/viewlet/objectExplorerActions';
import { IQueryEditorService } from 'sql/parts/query/common/queryEditorService'; import { IQueryEditorService } from 'sql/parts/query/common/queryEditorService';
import { IScriptingService } from 'sql/services/scripting/scriptingService'; import { IScriptingService } from 'sql/services/scripting/scriptingService';

View File

@@ -16,7 +16,7 @@ const properties: IJSONSchema = {
properties: { properties: {
yAxisMin: { yAxisMin: {
type: 'number', type: 'number',
description: nls.localize('yAxisMin', "Minumum value of the y axis") description: nls.localize('yAxisMin', "Minimum value of the y axis")
}, },
yAxisMax: { yAxisMax: {
type: 'number', type: 'number',
@@ -28,7 +28,7 @@ const properties: IJSONSchema = {
}, },
xAxisMin: { xAxisMin: {
type: 'number', type: 'number',
description: nls.localize('xAxisMin', "Minumum value of the x axis") description: nls.localize('xAxisMin', "Minimum value of the x axis")
}, },
xAxisMax: { xAxisMax: {
type: 'number', type: 'number',

View File

@@ -11,8 +11,8 @@ import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/work
import { IInsightsView, IInsightData } from 'sql/parts/dashboard/widgets/insights/interfaces'; import { IInsightsView, IInsightData } from 'sql/parts/dashboard/widgets/insights/interfaces';
import { Table } from 'sql/base/browser/ui/table/table'; import { Table } from 'sql/base/browser/ui/table/table';
import { TableDataView } from 'sql/base/browser/ui/table/tableDataView'; import { TableDataView } from 'sql/base/browser/ui/table/tableDataView';
import { DragCellSelectionModel } from 'sql/base/browser/ui/table/plugins/dragCellSelectionModel.plugin'; import { attachTableStyler } from 'sql/common/theme/styler';
import { attachTableStyler} from 'sql/common/theme/styler'; import { CellSelectionModel } from 'sql/base/browser/ui/table/plugins/cellSelectionModel.plugin';
@Component({ @Component({
template: '' template: ''
@@ -63,7 +63,7 @@ export default class TableInsight extends Disposable implements IInsightsView, O
private createTable() { private createTable() {
if (!this.table) { if (!this.table) {
this.table = new Table(this._elementRef.nativeElement, this.dataView, this.columns, { showRowNumber: true }); this.table = new Table(this._elementRef.nativeElement, this.dataView, this.columns, { showRowNumber: true });
this.table.setSelectionModel(new DragCellSelectionModel()); this.table.setSelectionModel(new CellSelectionModel());
this._register(attachTableStyler(this.table, this.themeService)); this._register(attachTableStyler(this.table, this.themeService));
} }
} }

View File

@@ -146,7 +146,7 @@ export class BackupUiService implements IBackupUiService {
let backupOptions = this.getOptions(this._currentProvider); let backupOptions = this.getOptions(this._currentProvider);
return new TPromise<void>(() => { return new TPromise<void>(() => {
let uri = this._connectionManagementService.getConnectionId(connection) let uri = this._connectionManagementService.getConnectionUri(connection)
+ ProviderConnectionInfo.idSeparator + ProviderConnectionInfo.idSeparator
+ ConnectionUtils.ConnectionUriBackupIdAttributeName + ConnectionUtils.ConnectionUriBackupIdAttributeName
+ ProviderConnectionInfo.nameValueSeparator + ProviderConnectionInfo.nameValueSeparator

View File

@@ -179,8 +179,8 @@ export class RestoreDialogController implements IRestoreDialogController {
private isSuccessfulRestore(response: TaskNode): boolean { private isSuccessfulRestore(response: TaskNode): boolean {
return (response.taskName === this._restoreTaskName && return (response.taskName === this._restoreTaskName &&
response.message === this._restoreCompleted && response.message === this._restoreCompleted &&
(response.status === TaskStatus.succeeded || (response.status === TaskStatus.Succeeded ||
response.status === TaskStatus.succeededWithWarning) && response.status === TaskStatus.SucceededWithWarning) &&
(response.taskExecutionMode === TaskExecutionMode.execute || (response.taskExecutionMode === TaskExecutionMode.execute ||
response.taskExecutionMode === TaskExecutionMode.executeAndScript)); response.taskExecutionMode === TaskExecutionMode.executeAndScript));
} }
@@ -293,7 +293,7 @@ export class RestoreDialogController implements IRestoreDialogController {
return new TPromise<void>((resolve, reject) => { return new TPromise<void>((resolve, reject) => {
let result: void; let result: void;
this._ownerUri = this._connectionService.getConnectionId(connection) this._ownerUri = this._connectionService.getConnectionUri(connection)
+ ProviderConnectionInfo.idSeparator + ProviderConnectionInfo.idSeparator
+ Utils.ConnectionUriRestoreIdAttributeName + Utils.ConnectionUriRestoreIdAttributeName
+ ProviderConnectionInfo.nameValueSeparator + ProviderConnectionInfo.nameValueSeparator

View File

@@ -226,7 +226,7 @@ export class RestoreDialog extends Modal {
destinationContainer.div({ class: 'dialog-input-section' }, (inputContainer) => { destinationContainer.div({ class: 'dialog-input-section' }, (inputContainer) => {
inputContainer.div({ class: 'dialog-label' }, (labelContainer) => { inputContainer.div({ class: 'dialog-label' }, (labelContainer) => {
labelContainer.innerHtml(LocalizedStrings.TARGETDATABASE); labelContainer.text(LocalizedStrings.TARGETDATABASE);
}); });
inputContainer.div({ class: 'dialog-input' }, (inputCellContainer) => { inputContainer.div({ class: 'dialog-input' }, (inputCellContainer) => {
@@ -471,7 +471,7 @@ export class RestoreDialog extends Modal {
className += ' header'; className += ' header';
} }
container.div({ class: className }, (labelContainer) => { container.div({ class: className }, (labelContainer) => {
labelContainer.innerHtml(content); labelContainer.text(content);
}); });
} }
@@ -535,7 +535,7 @@ export class RestoreDialog extends Modal {
let selectBox: SelectBox; let selectBox: SelectBox;
container.div({ class: 'dialog-input-section' }, (inputContainer) => { container.div({ class: 'dialog-input-section' }, (inputContainer) => {
inputContainer.div({ class: 'dialog-label' }, (labelContainer) => { inputContainer.div({ class: 'dialog-label' }, (labelContainer) => {
labelContainer.innerHtml(label); labelContainer.text(label);
}); });
inputContainer.div({ class: 'dialog-input' }, (inputCellContainer) => { inputContainer.div({ class: 'dialog-input' }, (inputCellContainer) => {

View File

@@ -16,6 +16,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati
import Severity from 'vs/base/common/severity'; import Severity from 'vs/base/common/severity';
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput'; import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
import { EditDataResultsInput } from 'sql/parts/editData/common/editDataResultsInput'; import { EditDataResultsInput } from 'sql/parts/editData/common/editDataResultsInput';
import { IEditorViewState } from 'vs/editor/common/editorCommon';
/** /**
* Input for the EditDataEditor. * Input for the EditDataEditor.
@@ -37,6 +38,8 @@ export class EditDataInput extends EditorInput implements IConnectableInput {
private _css: HTMLStyleElement; private _css: HTMLStyleElement;
private _useQueryFilter: boolean; private _useQueryFilter: boolean;
public savedViewState: IEditorViewState;
constructor( constructor(
private _uri: URI, private _uri: URI,
private _schemaName, private _schemaName,

View File

@@ -8,6 +8,7 @@
import { localize } from 'vs/nls'; import { localize } from 'vs/nls';
import { TPromise } from 'vs/base/common/winjs.base'; import { TPromise } from 'vs/base/common/winjs.base';
import { EditorInput } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor';
import { Emitter } from 'vs/base/common/event';
/** /**
* Input for the EditDataResultsEditor. This input helps with logic for the viewing and editing of * Input for the EditDataResultsEditor. This input helps with logic for the viewing and editing of
@@ -25,6 +26,9 @@ export class EditDataResultsInput extends EditorInput {
private _editorContainer: HTMLElement; private _editorContainer: HTMLElement;
public css: HTMLStyleElement; public css: HTMLStyleElement;
public readonly onRestoreViewStateEmitter = new Emitter<void>();
public readonly onSaveViewStateEmitter = new Emitter<void>();
constructor(private _uri: string) { constructor(private _uri: string) {
super(); super();
this._visible = false; this._visible = false;

View File

@@ -13,7 +13,7 @@ import { Builder } from 'vs/base/browser/builder';
import { EditorOptions, EditorInput } from 'vs/workbench/common/editor'; import { EditorOptions, EditorInput } from 'vs/workbench/common/editor';
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
import { Position, IEditorControl, IEditor } from 'vs/platform/editor/common/editor'; import { Position, IEditorControl, IEditor, IEditorInput } from 'vs/platform/editor/common/editor';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IThemeService } from 'vs/platform/theme/common/themeService';
@@ -43,6 +43,8 @@ import { IFlexibleSash, VerticalFlexibleSash, HorizontalFlexibleSash } from 'sql
import { Orientation } from 'vs/base/browser/ui/sash/sash'; import { Orientation } from 'vs/base/browser/ui/sash/sash';
import { EditDataResultsEditor } from 'sql/parts/editData/editor/editDataResultsEditor'; import { EditDataResultsEditor } from 'sql/parts/editData/editor/editDataResultsEditor';
import { EditDataResultsInput } from 'sql/parts/editData/common/editDataResultsInput'; import { EditDataResultsInput } from 'sql/parts/editData/common/editDataResultsInput';
import { IEditorViewState } from 'vs/editor/common/editorCommon';
import { Emitter } from 'vs/base/common/event';
/** /**
* Editor that hosts an action bar and a resultSetInput for an edit data session * Editor that hosts an action bar and a resultSetInput for an edit data session
@@ -96,6 +98,14 @@ export class EditDataEditor extends BaseEditor {
if (contextKeyService) { if (contextKeyService) {
this._queryEditorVisible = queryContext.QueryEditorVisibleContext.bindTo(contextKeyService); this._queryEditorVisible = queryContext.QueryEditorVisibleContext.bindTo(contextKeyService);
} }
if (_editorGroupService) {
_editorGroupService.onEditorOpening(e => {
if (this.isVisible() && (e.input !== this.input || e.position !== this.position)) {
this.saveEditorViewState();
}
});
}
} }
// PUBLIC METHODS //////////////////////////////////////////////////////////// // PUBLIC METHODS ////////////////////////////////////////////////////////////
@@ -594,7 +604,15 @@ export class EditDataEditor extends BaseEditor {
// Run all three steps synchronously // Run all three steps synchronously
return createEditors() return createEditors()
.then(onEditorsCreated) .then(onEditorsCreated)
.then(doLayout); .then(doLayout)
.then(() => {
if (newInput.results) {
newInput.results.onRestoreViewStateEmitter.fire();
}
if (newInput.savedViewState) {
this._sqlEditor.getControl().restoreViewState(newInput.savedViewState);
}
});
} }
private _setSashDimension(): void { private _setSashDimension(): void {
@@ -631,7 +649,6 @@ export class EditDataEditor extends BaseEditor {
* has been opened with the same editor, or we are opening the editor for the first time). * has been opened with the same editor, or we are opening the editor for the first time).
*/ */
private _updateInput(oldInput: EditDataInput, newInput: EditDataInput, options?: EditorOptions): TPromise<void> { private _updateInput(oldInput: EditDataInput, newInput: EditDataInput, options?: EditorOptions): TPromise<void> {
if (this._sqlEditor) { if (this._sqlEditor) {
this._sqlEditor.clearInput(); this._sqlEditor.clearInput();
} }
@@ -727,4 +744,16 @@ export class EditDataEditor extends BaseEditor {
public queryPaneEnabled(): boolean { public queryPaneEnabled(): boolean {
return this.editDataInput.queryPaneEnabled; return this.editDataInput.queryPaneEnabled;
} }
private saveEditorViewState(): void {
let editDataInput = this.input as EditDataInput;
if (editDataInput) {
if (this._sqlEditor) {
editDataInput.savedViewState = this._sqlEditor.getControl().saveViewState();
}
if (editDataInput.results) {
editDataInput.results.onSaveViewStateEmitter.fire();
}
}
}
} }

View File

@@ -23,6 +23,7 @@ import { IEditDataComponentParams } from 'sql/services/bootstrap/bootstrapParams
import { EditDataModule } from 'sql/parts/grid/views/editData/editData.module'; import { EditDataModule } from 'sql/parts/grid/views/editData/editData.module';
import { EDITDATA_SELECTOR } from 'sql/parts/grid/views/editData/editData.component'; import { EDITDATA_SELECTOR } from 'sql/parts/grid/views/editData/editData.component';
import { EditDataResultsInput } from 'sql/parts/editData/common/editDataResultsInput'; import { EditDataResultsInput } from 'sql/parts/editData/common/editDataResultsInput';
import { Event } from 'vs/base/common/event';
export class EditDataResultsEditor extends BaseEditor { export class EditDataResultsEditor extends BaseEditor {
@@ -109,7 +110,11 @@ export class EditDataResultsEditor extends BaseEditor {
// Otherwise many components will be left around and be subscribed // Otherwise many components will be left around and be subscribed
// to events from the backing data service // to events from the backing data service
const parent = input.container; const parent = input.container;
let params: IEditDataComponentParams = { dataService: dataService }; let params: IEditDataComponentParams = {
dataService: dataService,
onSaveViewState: input.onSaveViewStateEmitter.event,
onRestoreViewState: input.onRestoreViewStateEmitter.event
};
bootstrapAngular(this._instantiationService, bootstrapAngular(this._instantiationService,
EditDataModule, EditDataModule,
parent, parent,

View File

@@ -4,7 +4,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { IColumnDefinition, IObservableCollection, IGridDataRow } from 'angular2-slickgrid'; import { ISlickColumn, IObservableCollection, IGridDataRow } from 'angular2-slickgrid';
export interface ISlickRange { export interface ISlickRange {
fromCell: number; fromCell: number;
@@ -42,7 +42,7 @@ export interface IGridIcon {
export interface IGridDataSet { export interface IGridDataSet {
dataRows: IObservableCollection<IGridDataRow>; dataRows: IObservableCollection<IGridDataRow>;
columnDefinitions: IColumnDefinition[]; columnDefinitions: ISlickColumn<any>[];
resized: any; // EventEmitter<any>; resized: any; // EventEmitter<any>;
totalRows: number; totalRows: number;
batchId: number; batchId: number;

View File

@@ -17,14 +17,13 @@
showDataTypeIcon="false" showDataTypeIcon="false"
showHeader="true" showHeader="true"
[resized]="dataSet.resized" [resized]="dataSet.resized"
[plugins]="slickgridPlugins" [plugins]="plugins[i]"
(activeCellChanged)="onActiveCellChanged($event)" (activeCellChanged)="onActiveCellChanged($event)"
(cellEditBegin)="onCellEditBegin($event)" (cellEditBegin)="onCellEditBegin($event)"
(cellEditExit)="onCellEditEnd($event)" (cellEditExit)="onCellEditEnd($event)"
(rowEditBegin)="onRowEditBegin($event)" (rowEditBegin)="onRowEditBegin($event)"
(rowEditExit)="onRowEditEnd($event)" (rowEditExit)="onRowEditEnd($event)"
(contextMenu)="openContextMenu($event, dataSet.batchId, dataSet.resultId, i)" (contextMenu)="openContextMenu($event, dataSet.batchId, dataSet.resultId, i)"
[isColumnEditable]="onIsColumnEditable"
[isCellEditValid]="onIsCellEditValid" [isCellEditValid]="onIsCellEditValid"
[overrideCellFn]="overrideCellFn" [overrideCellFn]="overrideCellFn"
enableEditing="true" enableEditing="true"

View File

@@ -13,7 +13,7 @@ import 'vs/css!sql/parts/grid/media/slickGrid';
import 'vs/css!./media/editData'; import 'vs/css!./media/editData';
import { ElementRef, ChangeDetectorRef, OnInit, OnDestroy, Component, Inject, forwardRef, EventEmitter } from '@angular/core'; import { ElementRef, ChangeDetectorRef, OnInit, OnDestroy, Component, Inject, forwardRef, EventEmitter } from '@angular/core';
import { IGridDataRow, VirtualizedCollection } from 'angular2-slickgrid'; import { IGridDataRow, VirtualizedCollection, ISlickRange } from 'angular2-slickgrid';
import { IGridDataSet } from 'sql/parts/grid/common/interfaces'; import { IGridDataSet } from 'sql/parts/grid/common/interfaces';
import * as Services from 'sql/parts/grid/services/sharedServices'; import * as Services from 'sql/parts/grid/services/sharedServices';
@@ -24,6 +24,10 @@ import { error } from 'sql/base/common/log';
import { clone, mixin } from 'sql/base/common/objects'; import { clone, mixin } from 'sql/base/common/objects';
import { IQueryEditorService } from 'sql/parts/query/common/queryEditorService'; import { IQueryEditorService } from 'sql/parts/query/common/queryEditorService';
import { IBootstrapParams } from 'sql/services/bootstrap/bootstrapService'; import { IBootstrapParams } from 'sql/services/bootstrap/bootstrapService';
import { RowNumberColumn } from 'sql/base/browser/ui/table/plugins/rowNumberColumn.plugin';
import { AutoColumnSize } from 'sql/base/browser/ui/table/plugins/autoSizeColumns.plugin';
import { AdditionalKeyBindings } from 'sql/base/browser/ui/table/plugins/additionalKeyBindings.plugin';
import { escape } from 'sql/base/common/strings';
import { INotificationService } from 'vs/platform/notification/common/notification'; import { INotificationService } from 'vs/platform/notification/common/notification';
import Severity from 'vs/base/common/severity'; import Severity from 'vs/base/common/severity';
@@ -35,7 +39,6 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { KeyCode } from 'vs/base/common/keyCodes'; import { KeyCode } from 'vs/base/common/keyCodes';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
export const EDITDATA_SELECTOR: string = 'editdata-component'; export const EDITDATA_SELECTOR: string = 'editdata-component';
@Component({ @Component({
@@ -65,6 +68,7 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
private newRowVisible: boolean; private newRowVisible: boolean;
private removingNewRow: boolean; private removingNewRow: boolean;
private rowIdMappings: { [gridRowId: number]: number } = {}; private rowIdMappings: { [gridRowId: number]: number } = {};
protected plugins = new Array<Array<Slick.Plugin<any>>>();
// Edit Data functions // Edit Data functions
public onActiveCellChanged: (event: { row: number, column: number }) => void; public onActiveCellChanged: (event: { row: number, column: number }) => void;
@@ -77,6 +81,12 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
public overrideCellFn: (rowNumber, columnId, value?, data?) => string; public overrideCellFn: (rowNumber, columnId, value?, data?) => string;
public loadDataFunction: (offset: number, count: number) => Promise<IGridDataRow[]>; public loadDataFunction: (offset: number, count: number) => Promise<IGridDataRow[]>;
private savedViewState: {
gridSelections: ISlickRange[];
scrollTop;
scrollLeft;
};
constructor( constructor(
@Inject(forwardRef(() => ElementRef)) el: ElementRef, @Inject(forwardRef(() => ElementRef)) el: ElementRef,
@Inject(forwardRef(() => ChangeDetectorRef)) cd: ChangeDetectorRef, @Inject(forwardRef(() => ChangeDetectorRef)) cd: ChangeDetectorRef,
@@ -94,6 +104,8 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
this._el.nativeElement.className = 'slickgridContainer'; this._el.nativeElement.className = 'slickgridContainer';
this.dataService = params.dataService; this.dataService = params.dataService;
this.actionProvider = this.instantiationService.createInstance(EditDataGridActionProvider, this.dataService, this.onGridSelectAll(), this.onDeleteRow(), this.onRevertRow()); this.actionProvider = this.instantiationService.createInstance(EditDataGridActionProvider, this.dataService, this.onGridSelectAll(), this.onDeleteRow(), this.onRevertRow());
params.onRestoreViewState(() => this.restoreViewState());
params.onSaveViewState(() => this.saveViewState());
} }
/** /**
@@ -166,17 +178,6 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
this.onRowEditEnd = (event: { row: number }): void => { }; this.onRowEditEnd = (event: { row: number }): void => { };
this.onIsColumnEditable = (column: number): boolean => {
let result = false;
// Check that our variables exist
if (column !== undefined && !!this.dataSet && !!this.dataSet.columnDefinitions[column]) {
result = this.dataSet.columnDefinitions[column].isEditable;
}
// If no column definition exists then the row is not editable
return result;
};
this.overrideCellFn = (rowNumber, columnId, value?, data?): string => { this.overrideCellFn = (rowNumber, columnId, value?, data?): string => {
let returnVal = ''; let returnVal = '';
if (Services.DBCellValue.isDBCellValue(value)) { if (Services.DBCellValue.isDBCellValue(value)) {
@@ -196,9 +197,9 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
self.idMapping[rowIndex] = row.id; self.idMapping[rowIndex] = row.id;
rowIndex++; rowIndex++;
return { return {
values: row.cells.map(c => { values: [{}].concat(row.cells.map(c => {
return mixin({ ariaLabel: c.displayValue }, c); return mixin({ ariaLabel: escape(c.displayValue) }, c);
}), row: row.id })), row: row.id
}; };
}); });
@@ -350,6 +351,8 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
let maxHeight = this.getMaxHeight(resultSet.rowCount); let maxHeight = this.getMaxHeight(resultSet.rowCount);
let minHeight = this.getMinHeight(resultSet.rowCount); let minHeight = this.getMinHeight(resultSet.rowCount);
let rowNumberColumn = new RowNumberColumn({ numberOfRows: resultSet.rowCount });
// Store the result set from the event // Store the result set from the event
let dataSet: IGridDataSet = { let dataSet: IGridDataSet = {
resized: undefined, resized: undefined,
@@ -364,21 +367,23 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
this.loadDataFunction, this.loadDataFunction,
index => { return { values: [] }; } index => { return { values: [] }; }
), ),
columnDefinitions: resultSet.columnInfo.map((c, i) => { columnDefinitions: [rowNumberColumn.getColumnDefinition()].concat(resultSet.columnInfo.map((c, i) => {
let isLinked = c.isXml || c.isJson; let isLinked = c.isXml || c.isJson;
let linkType = c.isXml ? 'xml' : 'json'; let linkType = c.isXml ? 'xml' : 'json';
return { return {
id: i.toString(), id: i.toString(),
name: c.columnName === 'Microsoft SQL Server 2005 XML Showplan' name: c.columnName === 'Microsoft SQL Server 2005 XML Showplan'
? 'XML Showplan' ? 'XML Showplan'
: c.columnName, : escape(c.columnName),
type: self.stringToFieldType('string'), field: i.toString(),
formatter: isLinked ? Services.hyperLinkFormatter : Services.textFormatter, formatter: isLinked ? Services.hyperLinkFormatter : Services.textFormatter,
asyncPostRender: isLinked ? self.linkHandler(linkType) : undefined, asyncPostRender: isLinked ? self.linkHandler(linkType) : undefined,
isEditable: c.isUpdatable isEditable: c.isUpdatable
}; };
}) }))
}; };
self.plugins.push([rowNumberColumn, new AutoColumnSize(), new AdditionalKeyBindings()]);
self.dataSet = dataSet; self.dataSet = dataSet;
// Create a dataSet to render without rows to reduce DOM size // Create a dataSet to render without rows to reduce DOM size
@@ -581,4 +586,25 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
? (this._defaultNumShowingRows + 1) * this._rowHeight + 10 ? (this._defaultNumShowingRows + 1) * this._rowHeight + 10
: this.getMaxHeight(rowCount); : this.getMaxHeight(rowCount);
} }
private saveViewState(): void {
let gridSelections = this.slickgrids.toArray()[0].getSelectedRanges();
let viewport = ((this.slickgrids.toArray()[0] as any)._grid.getCanvasNode() as HTMLElement).parentElement;
this.savedViewState = {
gridSelections,
scrollTop: viewport.scrollTop,
scrollLeft: viewport.scrollLeft
};
}
private restoreViewState(): void {
if (this.savedViewState) {
this.slickgrids.toArray()[0].selection = this.savedViewState.gridSelections;
let viewport = ((this.slickgrids.toArray()[0] as any)._grid.getCanvasNode() as HTMLElement).parentElement;
viewport.scrollLeft = this.savedViewState.scrollLeft;
viewport.scrollTop = this.savedViewState.scrollTop;
this.savedViewState = undefined;
}
}
} }

View File

@@ -26,6 +26,7 @@ import * as GridContentEvents from 'sql/parts/grid/common/gridContentEvents';
import { ResultsVisibleContext, ResultsGridFocussedContext, ResultsMessagesFocussedContext, QueryEditorVisibleContext } from 'sql/parts/query/common/queryContext'; import { ResultsVisibleContext, ResultsGridFocussedContext, ResultsMessagesFocussedContext, QueryEditorVisibleContext } from 'sql/parts/query/common/queryContext';
import { error } from 'sql/base/common/log'; import { error } from 'sql/base/common/log';
import { IQueryEditorService } from 'sql/parts/query/common/queryEditorService'; import { IQueryEditorService } from 'sql/parts/query/common/queryEditorService';
import { CellSelectionModel } from 'sql/base/browser/ui/table/plugins/cellSelectionModel.plugin';
import { IAction } from 'vs/base/common/actions'; import { IAction } from 'vs/base/common/actions';
import { ResolvedKeybinding } from 'vs/base/common/keyCodes'; import { ResolvedKeybinding } from 'vs/base/common/keyCodes';
@@ -33,8 +34,6 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { AutoColumnSize } from 'sql/base/browser/ui/table/plugins/autoSizeColumns.plugin';
import { DragCellSelectionModel } from 'sql/base/browser/ui/table/plugins/dragCellSelectionModel.plugin';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
@@ -42,14 +41,8 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
export abstract class GridParentComponent { export abstract class GridParentComponent {
// CONSTANTS // CONSTANTS
// tslint:disable:no-unused-variable // tslint:disable:no-unused-variable
protected get selectionModel(): DragCellSelectionModel<any> {
return new DragCellSelectionModel<any>(); protected get selectionModel() { return new CellSelectionModel(); }
}
protected get slickgridPlugins(): Array<any> {
return [
new AutoColumnSize<any>({})
];
}
protected _rowHeight = 29; protected _rowHeight = 29;
protected _defaultNumShowingRows = 8; protected _defaultNumShowingRows = 8;
protected Constants = Constants; protected Constants = Constants;
@@ -94,7 +87,6 @@ export abstract class GridParentComponent {
public onRowEditBegin: (event: { row: number }) => void; public onRowEditBegin: (event: { row: number }) => void;
public onRowEditEnd: (event: { row: number }) => void; public onRowEditEnd: (event: { row: number }) => void;
public onIsCellEditValid: (row: number, column: number, newValue: any) => boolean; public onIsCellEditValid: (row: number, column: number, newValue: any) => boolean;
public onIsColumnEditable: (column: number) => boolean;
public overrideCellFn: (rowNumber, columnId, value?, data?) => string; public overrideCellFn: (rowNumber, columnId, value?, data?) => string;
public loadDataFunction: (offset: number, count: number) => Promise<IGridDataRow[]>; public loadDataFunction: (offset: number, count: number) => Promise<IGridDataRow[]>;
@@ -244,21 +236,25 @@ export abstract class GridParentComponent {
this.messagesFocussedContextKey.set(false); this.messagesFocussedContextKey.set(false);
} }
protected getSelection(index?: number): ISlickRange[] {
let selection = this.slickgrids.toArray()[index || this.activeGrid].getSelectedRanges();
selection = selection.map(c => { return <ISlickRange>{ fromCell: c.fromCell - 1, toCell: c.toCell - 1, toRow: c.toRow, fromRow: c.fromRow }; });
return selection;
}
private copySelection(): void { private copySelection(): void {
let messageText = this.getMessageText(); let messageText = this.getMessageText();
if (messageText.length > 0) { if (messageText.length > 0) {
this.clipboardService.writeText(messageText); this.clipboardService.writeText(messageText);
} else { } else {
let activeGrid = this.activeGrid; let activeGrid = this.activeGrid;
let selection = this.slickgrids.toArray()[activeGrid].getSelectedRanges(); this.dataService.copyResults(this.getSelection(activeGrid), this.renderedDataSets[activeGrid].batchId, this.renderedDataSets[activeGrid].resultId);
this.dataService.copyResults(selection, this.renderedDataSets[activeGrid].batchId, this.renderedDataSets[activeGrid].resultId);
} }
} }
private copyWithHeaders(): void { private copyWithHeaders(): void {
let activeGrid = this.activeGrid; let activeGrid = this.activeGrid;
let selection = this.slickgrids.toArray()[activeGrid].getSelectedRanges(); this.dataService.copyResults(this.getSelection(activeGrid), this.renderedDataSets[activeGrid].batchId,
this.dataService.copyResults(selection, this.renderedDataSets[activeGrid].batchId,
this.renderedDataSets[activeGrid].resultId, true); this.renderedDataSets[activeGrid].resultId, true);
} }
@@ -372,8 +368,7 @@ export abstract class GridParentComponent {
let activeGrid = this.activeGrid; let activeGrid = this.activeGrid;
let batchId = this.renderedDataSets[activeGrid].batchId; let batchId = this.renderedDataSets[activeGrid].batchId;
let resultId = this.renderedDataSets[activeGrid].resultId; let resultId = this.renderedDataSets[activeGrid].resultId;
let selection = this.slickgrids.toArray()[activeGrid].getSelectedRanges(); this.dataService.sendSaveRequest({ batchIndex: batchId, resultSetNumber: resultId, format: format, selection: this.getSelection(activeGrid) });
this.dataService.sendSaveRequest({ batchIndex: batchId, resultSetNumber: resultId, format: format, selection: selection });
} }
protected _keybindingFor(action: IAction): ResolvedKeybinding { protected _keybindingFor(action: IAction): ResolvedKeybinding {
@@ -385,7 +380,7 @@ export abstract class GridParentComponent {
let slick: any = this.slickgrids.toArray()[index]; let slick: any = this.slickgrids.toArray()[index];
let grid = slick._grid; let grid = slick._grid;
let selection = this.slickgrids.toArray()[index].getSelectedRanges(); let selection = this.getSelection(index);
if (selection && selection.length === 0) { if (selection && selection.length === 0) {
let cell = (grid as Slick.Grid<any>).getCellFromEvent(event); let cell = (grid as Slick.Grid<any>).getCellFromEvent(event);
@@ -423,7 +418,9 @@ export abstract class GridParentComponent {
let self = this; let self = this;
return (gridIndex: number) => { return (gridIndex: number) => {
self.activeGrid = gridIndex; self.activeGrid = gridIndex;
self.slickgrids.toArray()[this.activeGrid].selection = true; let grid = self.slickgrids.toArray()[self.activeGrid];
grid.setActive();
grid.selection = true;
}; };
} }
@@ -433,22 +430,6 @@ export abstract class GridParentComponent {
} }
} }
/**
* Used to convert the string to a enum compatible with SlickGrid
*/
protected stringToFieldType(input: string): FieldType {
let fieldtype: FieldType;
switch (input) {
case 'string':
fieldtype = FieldType.String;
break;
default:
fieldtype = FieldType.String;
break;
}
return fieldtype;
}
/** /**
* Makes a resultset take up the full result height if this is not already true * Makes a resultset take up the full result height if this is not already true
* Otherwise rerenders the result sets from default * Otherwise rerenders the result sets from default

View File

@@ -10,9 +10,9 @@
<span> {{LocalizedConstants.resultPaneLabel}} </span> <span> {{LocalizedConstants.resultPaneLabel}} </span>
<span class="queryResultsShortCut"> {{resultShortcut}} </span> <span class="queryResultsShortCut"> {{resultShortcut}} </span>
</div> </div>
<div id="results" *ngIf="renderedDataSets.length > 0" class="results vertBox scrollable" <div #resultsScrollBox id="results" *ngIf="renderedDataSets.length > 0" class="results vertBox scrollable"
(onScroll)="onScroll($event)" [scrollEnabled]="scrollEnabled" [class.hidden]="!resultActive" (onScroll)="onScroll($event)" [scrollEnabled]="scrollEnabled" [class.hidden]="!resultActive"
(focusin)="onGridFocus()" (focusout)="onGridFocusout()"> (focusin)="onGridFocus()" (focusout)="onGridFocusout()">
<div class="boxRow content horzBox slickgrid" *ngFor="let dataSet of renderedDataSets; let i = index" <div class="boxRow content horzBox slickgrid" *ngFor="let dataSet of renderedDataSets; let i = index"
[style.max-height]="dataSet.maxHeight" [style.min-height]="dataSet.minHeight"> [style.max-height]="dataSet.maxHeight" [style.min-height]="dataSet.minHeight">
<slick-grid #slickgrid id="slickgrid_{{i}}" [columnDefinitions]="dataSet.columnDefinitions" <slick-grid #slickgrid id="slickgrid_{{i}}" [columnDefinitions]="dataSet.columnDefinitions"
@@ -20,12 +20,11 @@
[dataRows]="dataSet.dataRows" [dataRows]="dataSet.dataRows"
(contextMenu)="openContextMenu($event, dataSet.batchId, dataSet.resultId, i)" (contextMenu)="openContextMenu($event, dataSet.batchId, dataSet.resultId, i)"
enableAsyncPostRender="true" enableAsyncPostRender="true"
showDataTypeIcon="false"
showHeader="true" showHeader="true"
[resized]="dataSet.resized" [resized]="dataSet.resized"
(mousedown)="navigateToGrid(i)" (mousedown)="navigateToGrid(i)"
[selectionModel]="selectionModel" [selectionModel]="selectionModel"
[plugins]="slickgridPlugins" [plugins]="plugins[i]"
class="boxCol content vertBox slickgrid" class="boxCol content vertBox slickgrid"
[rowHeight]="rowHeight"> [rowHeight]="rowHeight">
</slick-grid> </slick-grid>

View File

@@ -15,7 +15,7 @@ import {
ElementRef, QueryList, ChangeDetectorRef, OnInit, OnDestroy, Component, Inject, ElementRef, QueryList, ChangeDetectorRef, OnInit, OnDestroy, Component, Inject,
ViewChildren, forwardRef, EventEmitter, Input, ViewChild ViewChildren, forwardRef, EventEmitter, Input, ViewChild
} from '@angular/core'; } from '@angular/core';
import { IGridDataRow, SlickGrid, VirtualizedCollection } from 'angular2-slickgrid'; import { IGridDataRow, SlickGrid, VirtualizedCollection, ISlickRange } from 'angular2-slickgrid';
import * as LocalizedConstants from 'sql/parts/query/common/localizedConstants'; import * as LocalizedConstants from 'sql/parts/query/common/localizedConstants';
import * as Services from 'sql/parts/grid/services/sharedServices'; import * as Services from 'sql/parts/grid/services/sharedServices';
@@ -28,6 +28,9 @@ import { TabChild } from 'sql/base/browser/ui/panel/tab.component';
import { clone, mixin } from 'sql/base/common/objects'; import { clone, mixin } from 'sql/base/common/objects';
import { IQueryEditorService } from 'sql/parts/query/common/queryEditorService'; import { IQueryEditorService } from 'sql/parts/query/common/queryEditorService';
import { escape } from 'sql/base/common/strings'; import { escape } from 'sql/base/common/strings';
import { RowNumberColumn } from 'sql/base/browser/ui/table/plugins/rowNumberColumn.plugin';
import { AutoColumnSize } from 'sql/base/browser/ui/table/plugins/autoSizeColumns.plugin';
import { AdditionalKeyBindings } from 'sql/base/browser/ui/table/plugins/additionalKeyBindings.plugin';
import { format } from 'vs/base/common/strings'; import { format } from 'vs/base/common/strings';
import * as DOM from 'vs/base/browser/dom'; import * as DOM from 'vs/base/browser/dom';
@@ -61,7 +64,9 @@ export class QueryComponent extends GridParentComponent implements OnInit, OnDes
// create a function alias to use inside query.component // create a function alias to use inside query.component
// tslint:disable-next-line:no-unused-variable // tslint:disable-next-line:no-unused-variable
private stringsFormat: any = format; protected stringsFormat: any = format;
protected plugins = new Array<Array<Slick.Plugin<any>>>();
// tslint:disable-next-line:no-unused-variable // tslint:disable-next-line:no-unused-variable
private dataIcons: IGridIcon[] = [ private dataIcons: IGridIcon[] = [
@@ -86,7 +91,7 @@ export class QueryComponent extends GridParentComponent implements OnInit, OnDes
icon: () => { return 'saveCsv'; }, icon: () => { return 'saveCsv'; },
hoverText: () => { return LocalizedConstants.saveCSVLabel; }, hoverText: () => { return LocalizedConstants.saveCSVLabel; },
functionality: (batchId, resultId, index) => { functionality: (batchId, resultId, index) => {
let selection = this.slickgrids.toArray()[index].getSelectedRanges(); let selection = this.getSelection(index);
if (selection.length <= 1) { if (selection.length <= 1) {
this.handleContextClick({ type: 'savecsv', batchId: batchId, resultId: resultId, index: index, selection: selection }); this.handleContextClick({ type: 'savecsv', batchId: batchId, resultId: resultId, index: index, selection: selection });
} else { } else {
@@ -99,7 +104,7 @@ export class QueryComponent extends GridParentComponent implements OnInit, OnDes
icon: () => { return 'saveJson'; }, icon: () => { return 'saveJson'; },
hoverText: () => { return LocalizedConstants.saveJSONLabel; }, hoverText: () => { return LocalizedConstants.saveJSONLabel; },
functionality: (batchId, resultId, index) => { functionality: (batchId, resultId, index) => {
let selection = this.slickgrids.toArray()[index].getSelectedRanges(); let selection = this.getSelection(index);
if (selection.length <= 1) { if (selection.length <= 1) {
this.handleContextClick({ type: 'savejson', batchId: batchId, resultId: resultId, index: index, selection: selection }); this.handleContextClick({ type: 'savejson', batchId: batchId, resultId: resultId, index: index, selection: selection });
} else { } else {
@@ -112,7 +117,7 @@ export class QueryComponent extends GridParentComponent implements OnInit, OnDes
icon: () => { return 'saveExcel'; }, icon: () => { return 'saveExcel'; },
hoverText: () => { return LocalizedConstants.saveExcelLabel; }, hoverText: () => { return LocalizedConstants.saveExcelLabel; },
functionality: (batchId, resultId, index) => { functionality: (batchId, resultId, index) => {
let selection = this.slickgrids.toArray()[index].getSelectedRanges(); let selection = this.getSelection(index);
if (selection.length <= 1) { if (selection.length <= 1) {
this.handleContextClick({ type: 'saveexcel', batchId: batchId, resultId: resultId, index: index, selection: selection }); this.handleContextClick({ type: 'saveexcel', batchId: batchId, resultId: resultId, index: index, selection: selection });
} else { } else {
@@ -156,6 +161,13 @@ export class QueryComponent extends GridParentComponent implements OnInit, OnDes
public showChartRequested: EventEmitter<IGridDataSet> = new EventEmitter<IGridDataSet>(); public showChartRequested: EventEmitter<IGridDataSet> = new EventEmitter<IGridDataSet>();
public goToNextQueryOutputTabRequested: EventEmitter<void> = new EventEmitter<void>(); public goToNextQueryOutputTabRequested: EventEmitter<void> = new EventEmitter<void>();
private savedViewState: {
gridSelections: ISlickRange[][];
resultsScroll: number;
messagePaneScroll: number;
slickGridScrolls: { vertical: number; horizontal: number }[];
};
@Input() public queryParameters: IQueryComponentParams; @Input() public queryParameters: IQueryComponentParams;
@ViewChildren('slickgrid') slickgrids: QueryList<SlickGrid>; @ViewChildren('slickgrid') slickgrids: QueryList<SlickGrid>;
@@ -163,6 +175,8 @@ export class QueryComponent extends GridParentComponent implements OnInit, OnDes
@ViewChild('resultsPane', { read: ElementRef }) private _resultsPane: ElementRef; @ViewChild('resultsPane', { read: ElementRef }) private _resultsPane: ElementRef;
@ViewChild('queryLink', { read: ElementRef }) private _queryLinkElement: ElementRef; @ViewChild('queryLink', { read: ElementRef }) private _queryLinkElement: ElementRef;
@ViewChild('messagesContainer', { read: ElementRef }) private _messagesContainer: ElementRef; @ViewChild('messagesContainer', { read: ElementRef }) private _messagesContainer: ElementRef;
@ViewChild('resultsScrollBox', { read: ElementRef }) private _resultsScrollBox: ElementRef;
@ViewChildren('slickgrid', { read: ElementRef }) private _slickgridElements: QueryList<ElementRef>;
constructor( constructor(
@Inject(forwardRef(() => ElementRef)) el: ElementRef, @Inject(forwardRef(() => ElementRef)) el: ElementRef,
@Inject(forwardRef(() => ChangeDetectorRef)) cd: ChangeDetectorRef, @Inject(forwardRef(() => ChangeDetectorRef)) cd: ChangeDetectorRef,
@@ -220,6 +234,10 @@ export class QueryComponent extends GridParentComponent implements OnInit, OnDes
} }
self._cd.detectChanges(); self._cd.detectChanges();
}); });
this.queryParameters.onSaveViewState(() => this.saveViewState());
this.queryParameters.onRestoreViewState(() => this.restoreViewState());
this.dataService.onAngularLoaded(); this.dataService.onAngularLoaded();
} }
@@ -302,9 +320,9 @@ export class QueryComponent extends GridParentComponent implements OnInit, OnDes
for (let row = 0; row < rows.rows.length; row++) { for (let row = 0; row < rows.rows.length; row++) {
// Push row values onto end of gridData for slickgrid // Push row values onto end of gridData for slickgrid
gridData.push({ gridData.push({
values: rows.rows[row].map(c => { values: [{}].concat(rows.rows[row].map(c => {
return mixin({ ariaLabel: escape(c.displayValue) }, c); return mixin({ ariaLabel: escape(c.displayValue) }, c);
}) }))
}); });
} }
@@ -331,6 +349,8 @@ export class QueryComponent extends GridParentComponent implements OnInit, OnDes
minHeight = minHeightNumber.toString() + 'px'; minHeight = minHeightNumber.toString() + 'px';
} }
let rowNumberColumn = new RowNumberColumn({ numberOfRows: resultSet.rowCount });
// Store the result set from the event // Store the result set from the event
let dataSet: IGridDataSet = { let dataSet: IGridDataSet = {
resized: undefined, resized: undefined,
@@ -345,20 +365,22 @@ export class QueryComponent extends GridParentComponent implements OnInit, OnDes
loadDataFunction, loadDataFunction,
index => { return { values: [] }; } index => { return { values: [] }; }
), ),
columnDefinitions: resultSet.columnInfo.map((c, i) => { columnDefinitions: [rowNumberColumn.getColumnDefinition()].concat(resultSet.columnInfo.map((c, i) => {
let isLinked = c.isXml || c.isJson; let isLinked = c.isXml || c.isJson;
let linkType = c.isXml ? 'xml' : 'json'; let linkType = c.isXml ? 'xml' : 'json';
return { return {
id: i.toString(), id: i.toString(),
name: c.columnName === 'Microsoft SQL Server 2005 XML Showplan' name: c.columnName === 'Microsoft SQL Server 2005 XML Showplan'
? 'XML Showplan' ? 'XML Showplan'
: c.columnName, : escape(c.columnName),
type: self.stringToFieldType('string'), field: i.toString(),
formatter: isLinked ? Services.hyperLinkFormatter : Services.textFormatter, formatter: isLinked ? Services.hyperLinkFormatter : Services.textFormatter,
asyncPostRender: isLinked ? self.linkHandler(linkType) : undefined asyncPostRender: isLinked ? self.linkHandler(linkType) : undefined
}; };
}) }))
}; };
self.plugins.push([rowNumberColumn, new AutoColumnSize(), new AdditionalKeyBindings()]);
self.dataSets.push(dataSet); self.dataSets.push(dataSet);
// check if the resultset is for a query plan // check if the resultset is for a query plan
@@ -642,6 +664,43 @@ export class QueryComponent extends GridParentComponent implements OnInit, OnDes
} }
} }
private saveViewState(): void {
let gridSelections = this.slickgrids.map(grid => grid.getSelectedRanges());
let resultsScrollElement = (this._resultsScrollBox.nativeElement as HTMLElement);
let resultsScroll = resultsScrollElement.scrollTop;
let messagePaneScroll = (this._messagesContainer.nativeElement as HTMLElement).scrollTop;
let slickGridScrolls = this._slickgridElements.map(element => {
// Get the slick grid's viewport element and save its scroll position
let scrollElement = (element.nativeElement as HTMLElement).children[0].children[3];
return {
vertical: scrollElement.scrollTop,
horizontal: scrollElement.scrollLeft
};
});
this.savedViewState = {
gridSelections,
messagePaneScroll,
resultsScroll,
slickGridScrolls
};
}
private restoreViewState(): void {
if (this.savedViewState) {
this.slickgrids.forEach((grid, index) => grid.selection = this.savedViewState.gridSelections[index]);
(this._resultsScrollBox.nativeElement as HTMLElement).scrollTop = this.savedViewState.resultsScroll;
(this._messagesContainer.nativeElement as HTMLElement).scrollTop = this.savedViewState.messagePaneScroll;
this._slickgridElements.forEach((element, index) => {
let scrollElement = (element.nativeElement as HTMLElement).children[0].children[3];
let savedScroll = this.savedViewState.slickGridScrolls[index];
scrollElement.scrollTop = savedScroll.vertical;
scrollElement.scrollLeft = savedScroll.horizontal;
});
this.savedViewState = undefined;
}
}
layout() { layout() {
this.resizeGrids(); this.resizeGrids();
} }

View File

@@ -295,8 +295,9 @@ export class InsightsDialogView extends Modal {
for (let action of this._insight.actions.types) { for (let action of this._insight.actions.types) {
let task = tasks.includes(action); let task = tasks.includes(action);
let commandAction = MenuRegistry.getCommand(action); let commandAction = MenuRegistry.getCommand(action);
if (task) { let commandLabel = types.isString(commandAction.title) ? commandAction.title : commandAction.title.value;
let button = this.addFooterButton(types.isString(commandAction.title) ? commandAction.title : commandAction.title.value, () => { if (task && !this.findFooterButton(commandLabel)) {
let button = this.addFooterButton(commandLabel, () => {
let element = this._topTable.getSelectedRows(); let element = this._topTable.getSelectedRows();
let resource: ListResource; let resource: ListResource;
if (element && element.length > 0) { if (element && element.length > 0) {

View File

@@ -7,18 +7,11 @@ import 'vs/css!../common/media/jobs';
import 'sql/parts/dashboard/common/dashboardPanelStyles'; import 'sql/parts/dashboard/common/dashboardPanelStyles';
import * as nls from 'vs/nls'; import * as nls from 'vs/nls';
import { Component, Inject, forwardRef, ElementRef, ChangeDetectorRef, ViewChild, Injectable } from '@angular/core'; import { Component, Inject, forwardRef, ChangeDetectorRef, ViewChild, Injectable } from '@angular/core';
import * as Utils from 'sql/parts/connection/common/utils'; import { AgentJobInfo } from 'sqlops';
import { RefreshWidgetAction, EditDashboardAction } from 'sql/parts/dashboard/common/actions';
import { IColorTheme } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { IDisposable } from 'vs/base/common/lifecycle';
import * as themeColors from 'vs/workbench/common/theme';
import { DashboardPage } from 'sql/parts/dashboard/common/dashboardPage.component';
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { DashboardServiceInterface } from 'sql/parts/dashboard/services/dashboardServiceInterface.service';
import { AgentJobInfo, AgentJobHistoryInfo } from 'sqlops';
import { PanelComponent, IPanelOptions, NavigationBarLayout } from 'sql/base/browser/ui/panel/panel.component'; import { PanelComponent, IPanelOptions, NavigationBarLayout } from 'sql/base/browser/ui/panel/panel.component';
import { IJobManagementService } from 'sql/parts/jobManagement/common/interfaces'; import { IJobManagementService } from 'sql/parts/jobManagement/common/interfaces';
import { IDashboardService } from 'sql/services/dashboard/common/dashboardService';
export const DASHBOARD_SELECTOR: string = 'agentview-component'; export const DASHBOARD_SELECTOR: string = 'agentview-component';
@@ -32,12 +25,6 @@ export class AgentViewComponent {
@ViewChild(PanelComponent) private _panel: PanelComponent; @ViewChild(PanelComponent) private _panel: PanelComponent;
// tslint:disable:no-unused-variable
private readonly jobsComponentTitle: string = nls.localize('jobview.Jobs', "Jobs");
private readonly alertsComponentTitle: string = nls.localize('jobview.Alerts', "Alerts");
private readonly proxiesComponentTitle: string = nls.localize('jobview.Proxies', "Proxies");
private readonly operatorsComponentTitle: string = nls.localize('jobview.Operators', "Operators");
private _showHistory: boolean = false; private _showHistory: boolean = false;
private _jobId: string = null; private _jobId: string = null;
private _agentJobInfo: AgentJobInfo = null; private _agentJobInfo: AgentJobInfo = null;
@@ -49,6 +36,11 @@ export class AgentViewComponent {
public proxiesIconClass: string = 'proxiesview-icon'; public proxiesIconClass: string = 'proxiesview-icon';
public operatorsIconClass: string = 'operatorsview-icon'; public operatorsIconClass: string = 'operatorsview-icon';
private readonly jobsComponentTitle: string = nls.localize('jobview.Jobs', "Jobs");
private readonly alertsComponentTitle: string = nls.localize('jobview.Alerts', "Alerts");
private readonly proxiesComponentTitle: string = nls.localize('jobview.Proxies', "Proxies");
private readonly operatorsComponentTitle: string = nls.localize('jobview.Operators', "Operators");
// tslint:disable-next-line:no-unused-variable // tslint:disable-next-line:no-unused-variable
private readonly panelOpt: IPanelOptions = { private readonly panelOpt: IPanelOptions = {
showTabsWhenOne: true, showTabsWhenOne: true,
@@ -58,7 +50,8 @@ export class AgentViewComponent {
constructor( constructor(
@Inject(forwardRef(() => ChangeDetectorRef)) private _cd: ChangeDetectorRef, @Inject(forwardRef(() => ChangeDetectorRef)) private _cd: ChangeDetectorRef,
@Inject(IJobManagementService) jobManagementService: IJobManagementService) { @Inject(IJobManagementService) jobManagementService: IJobManagementService,
@Inject(IDashboardService) dashboardService: IDashboardService,) {
this._expanded = new Map<string, string>(); this._expanded = new Map<string, string>();
let self = this; let self = this;

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