Compare commits

...

144 Commits

Author SHA1 Message Date
Karl Burtram
9717b7516d Bump Agent, Import and Profiler extensions versions (#2330) 2018-08-24 19:16:11 -04:00
AlexFsmn
b92c23df2b Added functionality for adding any file to import wizard (#2329)
#2316
2018-08-24 19:01:22 -04:00
Aditya Bist
df617f19e0 Feature: Usage metrics (#2227)
* added user stats telemetry

* finished daily, weekly and monthly events

* code review comments

* added wekly and monthly start dates

* fixed the day condition using testing
2018-08-24 12:11:22 -07:00
Alan Ren
fb565c2548 insights widget accessibility support (#2324) 2018-08-24 10:22:58 -07:00
Kevin Cunnane
2bfc3a6c85 Support position property in editor component (#2314)
* Support position property in editor component
- This needs to be set directly on the editor component so cannot just use CSSStyles feature
- Given its importance to this scenario, it also warrants a dedicated property.

* Fix window resize when action bar clicked

* Renamed per Abbie's suggestion

* Changed name to clarify the comments
2018-08-23 17:32:13 -07:00
Alan Ren
4ab5d84b94 hide the backup restore commands from command palette (#2317) 2018-08-23 17:08:54 -07:00
Karl Burtram
154213b705 Bump Electron to 1.7.16 (#2312) 2018-08-23 19:55:40 -04:00
Anthony Dresser
4cce29ea9d change sqlops to be dev dep (#2311) 2018-08-23 14:15:08 -07:00
Alan Yu
9d5d00aa8f Updated Profiler readme with getting started instructions 2018-08-23 13:09:13 -07:00
Karl Burtram
70d47c1757 Prompt to create Profiler session per server (#2303)
* Null check getXEventSessions return value

* Profiler state WIP

* Fix various bugs getting session state
2018-08-23 15:33:23 -04:00
Anthony Dresser
befa34790f Refactor results grid (#2147)
* got a basic ui

* working on message panel

* done with messages moving to grids

* formatting

* working on multiple grids

* it does work

* styling

* formatting

* formatting

* fixed reset methods

* moved for scrollable

* formatting

* fixing scrolling

* making progress

* formatting

* fixed scrolling

* fix horizontal scrolling and size

* fix columns for tables

* integrate view item

* implementing heightmap scrolling

* add context menu and fix scrolling

* formatting

* revert slickgrid

* add actions to message pane

* formatting

* formatting

* bottom padding for tables

* minimized and maximized table actions

* add timestamp

* added batch start message  with selection

* updating

* formatting

* formatting

* fix execution time

* formatting

* fix problems

* fix rendering issues, add icons

* formatting

* formatting

* added commit change

* fix performance, message scrolling, etc

* formatting

* formatting

* fixing performance

* formatting

* update package

* tring to fix bugs

* reworking

* the problem is the 1st sash is always the first sash visible

* remove resizing from grid panels

* add missing files

* trying to get edit to work

* fix editdata

* formatting

* update angular2-slickgrid
2018-08-23 12:32:47 -07:00
Kevin Cunnane
84da9d289b Support vertical orientation for toolbar (#2308)
* Support vertical orientation for toolbar modelview component

* Add tab support for each button

* Fix padding and simplify styles
2018-08-23 10:54:20 -07:00
Matt Irvine
7a30d535e8 Make it possible to tab to result export buttons (#2302) 2018-08-23 10:27:48 -07:00
Kevin Cunnane
199701d26b Temporarily roll back SqlToolsService update to fix #2304 (#2305)
- Credential Service and Azure / Resource Provider Services are all broken in 1.5.0-alpha.29 so rolling back until fixed
2018-08-22 20:03:23 -07:00
Alan Ren
3ac2cfa528 fix the active cell's bottom border disappearing issue (#2301) 2018-08-22 16:56:32 -07:00
Karl Burtram
78d68aa1b9 Fix typo in SQL Tools Service config.json 2018-08-22 13:57:52 -07:00
Cory Rivera
8042b78f1e Check whether page is valid before enabling next button. (#2293) 2018-08-22 12:12:33 -07:00
Matt Irvine
000d064276 Show no account message after deleting all azure accounts (#2294) 2018-08-22 11:57:29 -07:00
Karl Burtram
600a78f35f Update SQL Tools Service to 1.5.0-alpha.29 2018-08-21 22:12:04 -07:00
Alan Ren
6f5e4c30dc fix edit data issue (#2285) 2018-08-21 18:29:21 -07:00
Karl Burtram
8e7457911e Bump DMP client to pick-up build connection string LIB (#2284) 2018-08-21 20:17:16 -04:00
Aditya Bist
43b3207937 changed desktop icon flag (#2281) 2018-08-21 16:50:30 -07:00
Abbie Petchtes
22f2151c21 fix the layout issue in model view (#2280) 2018-08-21 16:16:15 -07:00
Matt Irvine
7c744f2307 Put newline between multiple selections when copying results (#2279) 2018-08-21 14:04:46 -07:00
Karl Burtram
08d57fed86 Check-in current XLF files (#2275)
* Check-in current XLF files

* Remove duplicate IDs from SQL.XLF file

* Add comment why line is being disabled
2018-08-21 16:57:22 -04:00
AlexFsmn
6e8a0fe0ef Fixed background issue when copying a chart to clipboard (#2215)
#183
2018-08-20 19:35:49 -04:00
Anthony Dresser
6668ec4b5d Fix accessibility bugs in Chart Viewer and Advanced properties (#2240)
* fix accessibility bugs in chart viewer

* add advanced properties

* add comments for changes in vs
2018-08-20 16:19:29 -07:00
Alan Ren
08a8288293 fix the server group disapearing issue #1835 (#2269)
* fix the server group disapearing issue #1835

* address review comments
2018-08-20 15:43:16 -04:00
Matt Irvine
3001640eec Fix getUriForConnection API returning wrong URIs (#2202)
* Fix connection URI api to return working URI

* run tsfmt

* Keep using hand-built connection string for now in import

* Use connection ID instead of URI to get connection string
2018-08-20 15:41:06 -04:00
Aditya Bist
efa3658ced added preview messages (#2208) 2018-08-20 15:01:29 -04:00
AlexFsmn
92bc253cf7 Fixed problem where vertical charts didn't display labels correctly. (#2263)
#2017
2018-08-20 15:01:00 -04:00
AlexFsmn
a190190843 Fixed Initial values for charts to match visuals (#2266)
#2265
2018-08-20 14:59:43 -04:00
Kevin Cunnane
9c40bd1a23 Fix error where rename controller wasn't in InstantiationService (#2243) 2018-08-20 11:58:40 -07:00
Abbie Petchtes
dc2193138d fix the layout issue in model view dialog (#2242) 2018-08-20 11:53:47 -07:00
Anthony Dresser
033c8cb8b1 Adds build connection info feature (#2192)
* connection string

* formatting

* change serailize reponse type to match connect params

* add connection string serialization

* readd the connection string to the connection widget

* format

* remove unnecessary change

* update serializer to require provider

* update name of function

* fix function name

* bump dataprotocol and sqltools

* revert unnecessary change

* remove more unnecessary chagnes

* bump sqltoolsserivce

* adde configuration for auto parsing the clipboard
2018-08-20 14:50:16 -04:00
Aditya Bist
21c4429c6e Agent: Added support for high contrast theme (#2229)
* added support for high contrast theme

* initial theming

* programmatically change theme colors in agent
2018-08-20 10:08:00 -07:00
AlexFsmn
348a96b033 Renamed chart option labels (#2264)
#2262
2018-08-19 21:12:16 -04:00
AlexFsmn
0c930d7c0f Added feature for opening file after exporting to CSV/XLS/JSON & query files (#2216)
* Fix #746.
Added prompt for opening saved file location/file after save.
This fix includes saving of JSON/CSV/Excel & saving of a new SQL file.

* Changed var to let.
Moved code from vs dir to sql.
Removed support for showing file location after file save.
(Will be moved to another PR).
#746
2018-08-17 20:28:00 -04:00
Karl Burtram
98aca2b988 Bump SQL Ops to 0.32.6 (#2256) 2018-08-17 20:24:39 -04:00
Karl Burtram
8d7f497e0c Switch back DB icon but use default colors (#2254) 2018-08-17 18:49:03 -04:00
Leila Lali
1b6328b451 reverting a change in declarative table (#2246) 2018-08-17 14:50:41 -07:00
Karl Burtram
d10e08e63e Update SQL Ops Studio to 0.32.4 2018-08-16 17:35:54 -07:00
Anthony Dresser
a8f21b56f0 Fix context menus in grids (#2245)
* fix context menus

* fix edit data context menu
2018-08-16 20:34:52 -04:00
Karl Burtram
173842510c Bump yarn.lock package reference (#2248) 2018-08-16 20:33:19 -04:00
Alan Ren
19c08fe0eb Alanren/fixfor1782 (#2247)
* fix for issue #1782, refresh the cached data

* pick up the latest version of slickgrid and tools service
2018-08-16 16:54:07 -07:00
Amir Ali Omidi
db817a7192 Required fields and labelling of buttons (#2237)
* Required fields and labelling of buttons

* Update the readme.

* Change localized string
2018-08-16 11:16:06 -07:00
Amir Ali Omidi
2c8e93cc96 Support right clicking the database to start the importer task (#2233) 2018-08-15 14:11:54 -07:00
Alan Ren
44e9a97f09 fix for issue: Results Grid Row Indicator Zero Based #2152 (#2232) 2018-08-14 17:38:00 -07:00
Abbie Petchtes
298ddc4195 fix layout and add css styling for flex item (#2231) 2018-08-14 15:33:03 -07:00
Alan Ren
d9134d6085 Fix for: https://github.com/Microsoft/sqlopsstudio/issues/1317 (#2228)
use culture invariant display value for number conversion
2018-08-14 13:57:22 -07:00
Amir Ali Omidi
b17b4ce880 Entry Point updates (#2222)
* Entry point changes

* Navigator validation changes.

* Minor modifications to how the validator is called and setup
2018-08-14 13:18:07 -07:00
Leila Lali
2304c32453 fixed the issue caused by my latest check in in connection service (#2220) 2018-08-13 11:56:32 -07:00
Abbie Petchtes
2b68e4a7df fix issue where tree item doesn't expand when collapsible state is expanded (#2212) 2018-08-13 09:59:26 -07:00
Abbie Petchtes
8f06e72318 add aria label to the title of dialog (#2210) 2018-08-10 15:34:59 -07:00
Aditya Bist
5fa740ead4 added fix for disabling dropdown (#2203) 2018-08-10 11:07:54 -07:00
Leila Lali
e5096e61e5 Feature/ext connection dialog (#2201)
* Connection Dialog API for extensions
2018-08-10 09:29:46 -07:00
Amir Ali Omidi
2a3195636e Feat/import/language used (#2204)
Updated language used by flat file import
2018-08-09 15:44:58 -07:00
Amir Ali Omidi
5fb9b8ccd3 Clean importer startup (#2197)
* Cleans up issues from: https://github.com/Microsoft/sqlopsstudio/issues/2183
2018-08-09 14:34:17 -07:00
Amir Ali Omidi
7089e2299a Switches distribution of OSX binaries: https://github.com/Microsoft/sqlopsstudio/issues/2188 (#2198) 2018-08-09 14:00:23 -07:00
Anthony Dresser
b553cbb68c update build to use yarn to fix errors (#2191) 2018-08-08 15:09:12 -07:00
Amir Ali Omidi
c712411e77 Fixes the build issues. (#2190) 2018-08-08 14:21:21 -07:00
Leila Lali
48d5cc554c fixed the bug with collapsibleState (#2189) 2018-08-08 12:27:55 -07:00
Amir Ali Omidi
39bfd69dc9 Import Wizard (#2130)
Flat File Importer
2018-08-07 17:27:07 -07:00
Karl Burtram
d690b80493 Avoid null ref when workspace folder uri is undefined (#2179) 2018-08-07 19:48:26 -04:00
Karl Burtram
eb48a9f993 Fix Profiler missing title property error message (#2176) 2018-08-07 19:06:45 -04:00
Leila Lali
5a54abaf44 fixed the issue with tree component layout (#2174) 2018-08-07 14:10:57 -07:00
Karl Burtram
47c161f9f1 Bump SQL Ops to 0.32.4 and bump extensions (#2169) 2018-08-07 14:51:10 -04:00
Karl Burtram
807f8e68f3 Fix drag-n-drop null ref in OE (#2163) 2018-08-07 13:57:59 -04:00
Karl Burtram
fba8536c33 Update active grid tracking to fix copy bug (#2162) 2018-08-07 13:57:24 -04:00
Aditya Bist
36fc1bb71a added option for desktop icon (#2155) 2018-08-07 08:41:46 -07:00
Cory Rivera
ccaa96c81e Add >= and <= operators for context key expressions. (#2160) 2018-08-06 18:24:18 -07:00
Anthony Dresser
ac2f279c88 Fix edit data bugs (#2157)
* fix edit data bugs

* formatting
2018-08-06 20:42:52 -04:00
Aditya Bist
91cb99b8c8 changed linux packaging comment (#2158) 2018-08-06 17:25:21 -07:00
Aditya Bist
02b1a525e3 made the tree single click mode (#2159) 2018-08-06 17:17:05 -07:00
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
279 changed files with 16224 additions and 3467 deletions

View File

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

View File

@@ -1,5 +1,26 @@
# 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
* Release date: June 20, 2018
* 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
-- | --
Windows Setup Installer | https://go.microsoft.com/fwlink/?linkid=875602
Windows ZIP | https://go.microsoft.com/fwlink/?linkid=875603
macOS ZIP | https://go.microsoft.com/fwlink/?linkid=875604
Linux TAR.GZ | https://go.microsoft.com/fwlink/?linkid=875605
Linux RPM | https://go.microsoft.com/fwlink/?linkid=875606
Linux DEB | https://go.microsoft.com/fwlink/?linkid=875607
Windows Setup Installer | https://go.microsoft.com/fwlink/?linkid=2005949
Windows ZIP | https://go.microsoft.com/fwlink/?linkid=2005950
macOS ZIP | https://go.microsoft.com/fwlink/?linkid=2005959
Linux TAR.GZ | https://go.microsoft.com/fwlink/?linkid=2005960
Linux RPM | https://go.microsoft.com/fwlink/?linkid=2006083
Linux DEB | https://go.microsoft.com/fwlink/?linkid=2006084
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, 謝政廷
* 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
* 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)

View File

@@ -78,6 +78,7 @@ const sqlBuiltInExtensions = [
// Add SQL built-in extensions here.
// the extension will be excluded from SQLOps package and will have separate vsix packages
'agent',
'import',
'profiler'
];
@@ -273,7 +274,8 @@ function packageBuiltInExtensions() {
console.info('Creating vsix for ' + element.path + ' result:' + packagePath);
vsce.createVSIX({
cwd: element.path,
packagePath: packagePath
packagePath: packagePath,
useYarn: true
});
});
}
@@ -412,7 +414,7 @@ function packageTask(platform, arch, opts) {
license,
watermark,
api,
// {{SQL CARBON EDIT}}
// {{SQL CARBON EDIT}}
copiedModules,
dataApi,
sources,
@@ -526,7 +528,9 @@ gulp.task('vscode-translations-push-test', ['optimize-vscode'], function () {
gulp.src(pathToMetadata).pipe(i18n.createXlfFilesForCoreBundle()),
gulp.src(pathToSetup).pipe(i18n.createXlfFilesForIsl()),
gulp.src(pathToExtensions).pipe(i18n.createXlfFilesForExtensions())
).pipe(i18n.findObsoleteResources(apiHostname, apiName, apiToken)
// {{SQL CARBON EDIT}}
// disable since function makes calls to VS Code Transifex API
// ).pipe(i18n.findObsoleteResources(apiHostname, apiName, apiToken)
).pipe(vfs.dest('../vscode-transifex-input'));
});

View File

@@ -60,7 +60,9 @@ Type: files; Name: "{app}\resources\app\Credits_45.0.2454.85.html"; Check: IsNot
Type: filesandordirs; Name: "{app}\_"
[Tasks]
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: checkedonce;
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: "runcode"; Description: "{cm:RunAfter,{#NameShort}}"; GroupDescription: "{cm:Other}"; Check: WizardSilent
@@ -82,6 +84,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: 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]
// Don't allow installing conflicting architectures
function InitializeSetup(): Boolean;

View File

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

View File

@@ -63,8 +63,9 @@ export class AlertDialog extends AgentDialog<AlertData> {
private static readonly AlertTypes: string[] = [
AlertData.AlertTypeSqlServerEventString,
AlertData.AlertTypePerformanceConditionString,
AlertData.AlertTypeWmiEventString
// Disabled until next release
// AlertData.AlertTypePerformanceConditionString,
// AlertData.AlertTypeWmiEventString
];
private static readonly AlertSeverities: string[] = [
@@ -366,11 +367,18 @@ export class AlertDialog extends AgentDialog<AlertData> {
title: AlertDialog.NewJobButtonLabel
}], { componentWidth: '100%'}).component();
let previewTag = view.modelBuilder.text()
.withProperties({
value: 'Feature Preview'
}).component();
this.notifyOperatorsCheckBox = view.modelBuilder.checkBox()
.withProperties({
label: AlertDialog.NotifyOperatorsTextBoxLabel
}).component();
this.notifyOperatorsCheckBox.enabled = false;
this.operatorsTable = view.modelBuilder.table()
.withProperties({
columns: [
@@ -422,6 +430,9 @@ export class AlertDialog extends AgentDialog<AlertData> {
}, {
component: executeJobContainer,
title: ''
}, {
component: previewTag,
title: ''
}, {
component: this.notifyOperatorsCheckBox,
title: ''

View File

@@ -125,7 +125,6 @@ export class JobDialog extends AgentDialog<JobData> {
this.initializeSchedulesTab();
this.initializeNotificationsTab();
this.dialog.content = [this.generalTab, this.stepsTab, this.schedulesTab, this.alertsTab, this.notificationsTab];
this.dialog.registerCloseValidator(() => {
this.updateModel();
let validationResult = this.model.validate();
@@ -194,6 +193,10 @@ export class JobDialog extends AgentDialog<JobData> {
private initializeStepsTab() {
this.stepsTab.registerContent(async view => {
let previewTag = view.modelBuilder.text()
.withProperties({
value: 'Feature Preview'
}).component();
this.stepsTable = view.modelBuilder.table()
.withProperties({
columns: [
@@ -219,6 +222,9 @@ export class JobDialog extends AgentDialog<JobData> {
width: 80
}).component();
this.moveStepUpButton.enabled = false;
this.moveStepDownButton.enabled = false;
this.newStepButton = view.modelBuilder.button().withProperties({
label: this.NewStepButtonString,
width: 80
@@ -243,8 +249,14 @@ export class JobDialog extends AgentDialog<JobData> {
width: 80
}).component();
this.stepsTable.enabled = false;
let formModel = view.modelBuilder.formContainer()
.withFormItems([{
component: previewTag,
title: ''
},
{
component: this.stepsTable,
title: this.JobStepsTopLabelString,
actions: [this.moveStepUpButton, this.moveStepDownButton, this.newStepButton, this.editStepButton, this.deleteStepButton]
@@ -255,6 +267,10 @@ export class JobDialog extends AgentDialog<JobData> {
private initializeAlertsTab() {
this.alertsTab.registerContent(async view => {
let previewTag = view.modelBuilder.text()
.withProperties({
value: 'Feature Preview'
}).component();
this.alertsTable = view.modelBuilder.table()
.withProperties({
columns: [
@@ -279,6 +295,9 @@ export class JobDialog extends AgentDialog<JobData> {
let formModel = view.modelBuilder.formContainer()
.withFormItems([{
component: previewTag,
title: ''
}, {
component: this.alertsTable,
title: this.AlertsTopLabelString,
actions: [this.newAlertButton]

View File

@@ -30,6 +30,7 @@ export class JobStepDialog {
private readonly PreviousButtonText: string = localize('jobStepDialog.previous','Previous');
private readonly SuccessfulParseText: string = localize('jobStepDialog.successParse', 'The command was successfully parsed.');
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');
@@ -139,13 +140,19 @@ export class JobStepDialog {
this.openButton = view.modelBuilder.button()
.withProperties({
label: this.OpenCommandText,
width: '80px'
width: '80px',
isFile: true
}).component();
this.parseButton = view.modelBuilder.button()
.withProperties({
label: this.ParseCommandText,
width: '80px'
width: '80px',
isFile: false
}).component();
this.openButton.onDidClick(e => {
let queryContent = e;
this.commandTextBox.value = queryContent;
});
this.parseButton.onDidClick(e => {
if (this.commandTextBox.value) {
queryProvider.parseSyntax(this.ownerUri, this.commandTextBox.value).then(result => {
@@ -185,6 +192,12 @@ export class JobStepDialog {
.withProperties({
}).component();
this.nameTextBox.required = true;
this.nameTextBox.onTextChanged(() => {
if (this.nameTextBox.value.length > 0) {
this.dialog.message = null;
}
});
this.typeDropdown = view.modelBuilder.dropDown()
.withProperties({
value: this.TSQLScript,
@@ -465,7 +478,12 @@ export class JobStepDialog {
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.id = this.stepId;
this.model.server = this.server;
@@ -479,7 +497,6 @@ export class JobStepDialog {
this.model.failureAction = this.failureActionDropdown.value as string;
this.model.outputFileName = this.outputFileNameBox.value;
this.model.appendToLogFile = this.appendToExistingFileCheckbox.checked;
await this.model.save();
}
public async openNewStepDialog() {

View File

@@ -368,6 +368,10 @@ export class OperatorDialog extends AgentDialog<OperatorData> {
private initializeNotificationTab() {
this.notificationsTab.registerContent(async view => {
let previewTag = view.modelBuilder.text()
.withProperties({
value: 'Feature Preview'
}).component();
this.alertsTable = view.modelBuilder.table()
.withProperties({
columns: [
@@ -381,6 +385,9 @@ export class OperatorDialog extends AgentDialog<OperatorData> {
let formModel = view.modelBuilder.formContainer()
.withFormItems([{
component: previewTag,
title: ''
}, {
component: this.alertsTable,
title: OperatorDialog.AlertsTableLabel
}]).withLayout({ width: '100%' }).component();

View File

@@ -1,6 +1,6 @@
/*---------------------------------------------------------------------------------------------
* 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';

View File

@@ -1,6 +1,6 @@
/*---------------------------------------------------------------------------------------------
* 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.
*--------------------------------------------------------------------------------------------*/

1
extensions/import/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
flatfileimportservice/

View File

@@ -0,0 +1,2 @@
client/src/**
client/tsconfig.json

View File

@@ -0,0 +1,25 @@
# Microsoft SQL Server Import for SQL Operations Studio
Import Flat File Wizard is a simple way to copy data from a flat file (.csv, .txt) to a destination. This overview describes the reasons for using this wizard, how to find this wizard, and a simple example to follow.
![image](https://user-images.githubusercontent.com/30873802/43433347-c958ed28-942b-11e8-8bbc-f4f2529c3978.png)
## Why would I use this wizard?
This wizard was created to improve the current import experience leveraging an intelligent framework known as Program Synthesis using Examples ([PROSE](https://microsoft.github.io/prose/)). For a user without specialized domain knowledge, importing data can often be a complex, error prone, and tedious task. This wizard streamlines the import process as simple as selecting an input file and unique table name, and the PROSE framework handles the rest.
PROSE analyzes data patterns in your input file to infer column names, types, delimiters, and more. This framework learns the structure of the file and does all of the hard work so users don't have to.
## Code of Conduct
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
## Privacy Statement
The [Microsoft Enterprise and Developer Privacy Statement](https://privacy.microsoft.com/en-us/privacystatement) describes the privacy statement of this software.
## License
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the [Source EULA](https://raw.githubusercontent.com/Microsoft/sqlopsstudio/master/LICENSE.txt).

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 21 21"><defs><style>.cls-1{fill:#fff;}.cls-2{fill:#231f20;}.cls-3{fill:#0095d7;}</style></defs><title>importflatfile_inverse</title><path class="cls-1" d="M13.34,1.57c-2.81,0-7.52.53-7.65,2.49v3.2L7,8.54V5.66a17.11,17.11,0,0,0,6.37,1,17.1,17.1,0,0,0,6.38-1V17.58c-.17.46-2.55,1.35-6.38,1.35a19.63,19.63,0,0,1-3.43-.27V20a23.78,23.78,0,0,0,3.43.25c2.86,0,7.66-.57,7.66-2.64V4.06C20.87,2.1,16.16,1.57,13.34,1.57Zm6.38,2.55c-.2.45-2.56,1.28-6.38,1.28S7.24,4.6,7,4.14c.27-.47,2.6-1.29,6.37-1.29s6.16.85,6.38,1.25h0Z"/><polygon class="cls-2" points="18.55 3.06 18.53 3.07 18.53 3.04 18.55 3.06"/><path class="cls-1" d="M7,10,5.69,8.68,5,8H0V19.85H8.91v-8ZM5.2,9.24l.49.49L7,11l.67.67H5.2Zm3,9.86H.74V8.71H4.46v3.71H8.17Z"/><path class="cls-3" d="M16.5,15a.27.27,0,0,1-.08.2L14.2,17.4a.26.26,0,0,1-.19.08.28.28,0,0,1-.2-.08.26.26,0,0,1-.08-.2.82.82,0,0,1,0-.14l.06-.25.08-.32.08-.32.07-.27,0-.17H4.5v-1.5h9.59l0-.17L14,13.79l-.08-.32-.08-.31-.06-.25a.91.91,0,0,1,0-.14.26.26,0,0,1,.08-.2.28.28,0,0,1,.2-.08.26.26,0,0,1,.19.08l2.22,2.22A.26.26,0,0,1,16.5,15Z"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 21 21"><defs><style>.cls-1{fill:#212121;}.cls-2{fill:#231f20;}.cls-3{fill:#00539c;}</style></defs><title>importflatfile</title><path class="cls-1" d="M13.34,1.3c-2.81,0-7.52.53-7.65,2.49V7L7,8.27V5.39a17.11,17.11,0,0,0,6.37,1,17.1,17.1,0,0,0,6.38-1V17.31c-.17.46-2.55,1.35-6.38,1.35a19.63,19.63,0,0,1-3.43-.27V19.7a23.78,23.78,0,0,0,3.43.25C16.2,20,21,19.38,21,17.31V3.79C20.87,1.83,16.16,1.3,13.34,1.3Zm6.38,2.55c-.2.45-2.56,1.28-6.38,1.28S7.24,4.33,7,3.87c.27-.47,2.6-1.29,6.37-1.29s6.16.85,6.38,1.25h0Z"/><polygon class="cls-2" points="18.55 2.79 18.53 2.81 18.53 2.78 18.55 2.79"/><path class="cls-1" d="M7,9.69,5.69,8.41,5,7.7H0V19.58H8.91v-8ZM5.2,9l.49.49L7,10.74l.67.67H5.2Zm3,9.86H.74V8.44H4.46v3.71H8.17Z"/><path class="cls-3" d="M16.5,14.72a.27.27,0,0,1-.08.2L14.2,17.14a.26.26,0,0,1-.19.08.28.28,0,0,1-.2-.08.26.26,0,0,1-.08-.2.82.82,0,0,1,0-.14l.06-.25.08-.32.08-.32.07-.27,0-.17H4.5V14h9.59l0-.17L14,13.53l-.08-.32-.08-.31-.06-.25a.91.91,0,0,1,0-.14.26.26,0,0,1,.08-.2.28.28,0,0,1,.2-.08.26.26,0,0,1,.19.08l2.22,2.22A.26.26,0,0,1,16.5,14.72Z"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

View File

@@ -0,0 +1,91 @@
{
"name": "import",
"displayName": "SQL Server Import",
"description": "Imports data from a flat file.",
"version": "0.0.2",
"publisher": "Microsoft",
"preview": true,
"engines": {
"vscode": "^1.25.0",
"sqlops": "*"
},
"license": "https://raw.githubusercontent.com/Microsoft/sqlopsstudio/master/LICENSE.txt",
"icon": "images/sqlserver.png",
"aiKey": "AIF-5574968e-856d-40d2-af67-c89a14e76412",
"activationEvents": [
"*"
],
"main": "./out/main",
"repository": {
"type": "git",
"url": "https://github.com/Microsoft/sqlopsstudio.git"
},
"extensionDependencies": [
"Microsoft.mssql"
],
"contributes": {
"commands": [
{
"command": "flatFileImport.start",
"title": "Import wizard",
"category": "Flat File Import",
"icon": {
"light": "./images/light_icon.svg",
"dark": "./images/dark_icon.svg"
}
}
],
"keybindings": [
{
"command": "flatFileImport.start",
"key": "ctrl+i",
"mac": "ctrl+i"
}
],
"dashboard.tabs": [
{
"id": "flat-file-import",
"title": "Flat File Import",
"description": "The flat file importer.",
"container": {
"flat-file-import-container": {}
}
}
],
"dashboard.containers": [
{
"id": "flat-file-import-container",
"container": {
"widgets-container": [
{
"name": "Tasks",
"widget": {
"tasks-widget": [
"flatFileImport.start"
]
}
}
]
}
}
],
"menus": {
"objectExplorer/item/context": [
{
"command": "flatFileImport.start",
"when": "connectionProvider == MSSQL && nodeType && nodeType == Database",
"group": "import"
}
]
}
},
"dependencies": {
"dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#0.2.7",
"opener": "^1.4.3",
"service-downloader": "github:anthonydresser/service-downloader#0.1.4",
"vscode-extension-telemetry": "^0.0.5",
"vscode-nls": "^3.2.1"
},
"devDependencies": {
}
}

View File

@@ -0,0 +1,14 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
export const extensionConfigSectionName = 'flatFileImport';
export const serviceName = 'Flat File Import Service';
export const providerId = 'FlatFileImport';
export const configLogDebugInfo = 'logDebugInfo';
export const sqlConfigSectionName = 'sql';
export const serviceCrashLink = 'https://github.com/Microsoft/sqlopsstudio/issues/2090';

View File

@@ -0,0 +1,30 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as vscode from 'vscode';
export default abstract class ControllerBase implements vscode.Disposable {
protected _context: vscode.ExtensionContext;
protected constructor(context: vscode.ExtensionContext) {
this._context = context;
}
public get extensionContext(): vscode.ExtensionContext {
return this._context;
}
abstract activate(): Promise<boolean>;
abstract deactivate(): void;
public dispose(): void {
this.deactivate();
}
}

View File

@@ -0,0 +1,44 @@
/*---------------------------------------------------------------------------------------------
* 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 constants from '../constants';
import * as sqlops from 'sqlops';
import ControllerBase from './controllerBase';
import * as vscode from 'vscode';
import { FlatFileWizard } from '../wizard/flatFileWizard';
import { ServiceClient } from '../services/serviceClient';
import { ApiType, managerInstance } from '../services/serviceApiManager';
import { FlatFileProvider } from '../services/contracts';
/**
* The main controller class that initializes the extension
*/
export default class MainController extends ControllerBase {
public constructor(context: vscode.ExtensionContext) {
super(context);
}
/**
*/
public deactivate(): void {
}
public activate(): Promise<boolean> {
const outputChannel = vscode.window.createOutputChannel(constants.serviceName);
new ServiceClient(outputChannel).startService(this._context);
managerInstance.onRegisteredApi<FlatFileProvider>(ApiType.FlatFileProvider)(provider => {
this.initializeFlatFileProvider(provider);
});
return Promise.resolve(true);
}
private initializeFlatFileProvider(provider: FlatFileProvider) {
sqlops.tasks.registerTask('flatFileImport.start', (profile: sqlops.IConnectionProfile, ...args: any[]) => new FlatFileWizard(provider).start(profile, args));
}
}

View File

@@ -0,0 +1,37 @@
/*---------------------------------------------------------------------------------------------
* 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 ControllerBase from './controllers/controllerBase';
import MainController from './controllers/mainController';
let controllers: ControllerBase[] = [];
export function activate(context: vscode.ExtensionContext) {
let activations: Promise<boolean>[] = [];
// Start the main controller
let mainController = new MainController(context);
controllers.push(mainController);
context.subscriptions.push(mainController);
activations.push(mainController.activate());
return Promise.all(activations)
.then((results: boolean[]) => {
for (let result of results) {
if (!result) {
return false;
}
}
return true;
});
}
export function deactivate() {
for (let controller of controllers) {
controller.deactivate();
}
}

View File

@@ -0,0 +1,16 @@
{
"downloadUrl": "https://sqlopsextensions.blob.core.windows.net/extensions/import/{#fileName#}",
"useDefaultLinuxRuntime": true,
"version": "0.0.1",
"downloadFileNames": {
"Windows_64": "win-x64.zip",
"Windows_86": "win-x86.zip",
"OSX": "osx.tar.gz",
"Linux_64": "linux-x64.tar.gz"
},
"installDirectory": "flatfileimportservice/{#platform#}/{#version#}",
"executableFiles": [
"MicrosoftSqlToolsFlatFileImport",
"MicrosoftSqlToolsFlatFileImport.exe"
]
}

View File

@@ -0,0 +1,149 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { RequestType, NotificationType } from 'vscode-languageclient';
/**
* @interface IMessage
*/
export interface IMessage {
jsonrpc: string;
}
// ------------------------------- < Telemetry Sent Event > ------------------------------------
/**
* Event sent when the language service send a telemetry event
*/
export namespace TelemetryNotification {
export const type = new NotificationType<TelemetryParams, void>('telemetry/sqlevent');
}
/**
* Update event parameters
*/
export class TelemetryParams {
public params: {
eventName: string;
properties: ITelemetryEventProperties;
measures: ITelemetryEventMeasures;
};
}
export interface ITelemetryEventProperties {
[key: string]: string;
}
export interface ITelemetryEventMeasures {
[key: string]: number;
}
/**
* Contract Classes
*/
export interface Result {
success: boolean;
errorMessage: string;
}
export interface ColumnInfo {
name: string;
sqlType: string;
isNullable: boolean;
}
/**
* PROSEDiscoveryRequest
* Send this request to create a new PROSE session with a new file and preview it
*/
const proseDiscoveryRequestName = 'flatfile/proseDiscovery';
export interface PROSEDiscoveryParams {
filePath: string;
tableName: string;
schemaName?: string;
fileType?: string;
}
export interface PROSEDiscoveryResponse {
dataPreview: string[][];
columnInfo: ColumnInfo[];
}
/**
* InsertDataRequest
*/
const insertDataRequestName = 'flatfile/insertData';
export interface InsertDataParams {
connectionString: string;
batchSize: number;
}
export interface InsertDataResponse {
result: Result;
}
/**
* GetColumnInfoRequest
*/
const getColumnInfoRequestName = 'flatfile/getColumnInfo';
export interface GetColumnInfoParams {
}
export interface GetColumnInfoResponse {
columnInfo: ColumnInfo[];
}
/**
* ChangeColumnSettingsRequest
*/
const changeColumnSettingsRequestName = 'flatfile/changeColumnSettings';
export interface ChangeColumnSettingsParams {
index: number;
newName?: string;
newDataType?: string;
newNullable?: boolean;
newInPrimaryKey?: boolean;
}
export interface ChangeColumnSettingsResponse {
result: Result;
}
/**
* Requests
*/
export namespace PROSEDiscoveryRequest {
export const type = new RequestType<PROSEDiscoveryParams, PROSEDiscoveryResponse, void, void>(proseDiscoveryRequestName);
}
export namespace InsertDataRequest {
export const type = new RequestType<InsertDataParams, InsertDataResponse, void, void>(insertDataRequestName);
}
export namespace GetColumnInfoRequest {
export const type = new RequestType<GetColumnInfoParams, GetColumnInfoResponse, void, void>(getColumnInfoRequestName);
}
export namespace ChangeColumnSettingsRequest {
export const type = new RequestType<ChangeColumnSettingsParams, ChangeColumnSettingsResponse, void, void>(changeColumnSettingsRequestName);
}
export interface FlatFileProvider {
providerId?: string;
sendPROSEDiscoveryRequest(params: PROSEDiscoveryParams): Thenable<PROSEDiscoveryResponse>;
sendInsertDataRequest(params: InsertDataParams): Thenable<InsertDataResponse>;
sendGetColumnInfoRequest(params: GetColumnInfoParams): Thenable<GetColumnInfoResponse>;
sendChangeColumnSettingsRequest(params: ChangeColumnSettingsParams): Thenable<ChangeColumnSettingsResponse>;
}

View File

@@ -0,0 +1,96 @@
/*---------------------------------------------------------------------------------------------
* 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 { SqlOpsDataClient, SqlOpsFeature } from 'dataprotocol-client';
import {
ClientCapabilities,
StaticFeature,
RPCMessageType,
ServerCapabilities
} from 'vscode-languageclient';
import * as UUID from 'vscode-languageclient/lib/utils/uuid';
import { Disposable } from 'vscode';
import { Telemetry } from './telemetry';
import * as serviceUtils from './serviceUtils';
import * as Contracts from './contracts';
import { managerInstance, ApiType } from './serviceApiManager';
export class TelemetryFeature implements StaticFeature {
constructor(private _client: SqlOpsDataClient) {
}
fillClientCapabilities(capabilities: ClientCapabilities): void {
serviceUtils.ensure(capabilities, 'telemetry')!.telemetry = true;
}
initialize(): void {
this._client.onNotification(Contracts.TelemetryNotification.type, e => {
Telemetry.sendTelemetryEvent(e.params.eventName, e.params.properties, e.params.measures);
});
}
}
export class FlatFileImportFeature extends SqlOpsFeature<undefined> {
private static readonly messagesTypes: RPCMessageType[] = [
Contracts.PROSEDiscoveryRequest.type
];
constructor(client: SqlOpsDataClient) {
super(client, FlatFileImportFeature.messagesTypes);
}
public fillClientCapabilities(capabilities: ClientCapabilities): void {
}
public initialize(capabilities: ServerCapabilities): void {
this.register(this.messages, {
id: UUID.generateUuid(),
registerOptions: undefined
});
}
protected registerProvider(options: undefined): Disposable {
const client = this._client;
let requestSender = (requestType, params) => {
return client.sendRequest(requestType, params).then(
r => {
return r as any;
},
e => {
client.logFailedRequest(requestType, e);
return Promise.reject(e);
}
);
};
let sendPROSEDiscoveryRequest = (params: Contracts.PROSEDiscoveryParams): Thenable<Contracts.PROSEDiscoveryResponse> => {
return requestSender(Contracts.PROSEDiscoveryRequest.type, params);
};
let sendInsertDataRequest = (params: Contracts.InsertDataParams): Thenable<Contracts.InsertDataResponse> => {
return requestSender(Contracts.InsertDataRequest.type, params);
};
let sendGetColumnInfoRequest = (params: Contracts.GetColumnInfoParams): Thenable<Contracts.GetColumnInfoResponse> => {
return requestSender(Contracts.GetColumnInfoRequest.type, params);
};
let sendChangeColumnSettingsRequest = (params: Contracts.ChangeColumnSettingsParams): Thenable<Contracts.ChangeColumnSettingsResponse> => {
return requestSender(Contracts.ChangeColumnSettingsRequest.type, params);
};
return managerInstance.registerApi<Contracts.FlatFileProvider>(ApiType.FlatFileProvider, {
providerId: client.providerId,
sendPROSEDiscoveryRequest,
sendChangeColumnSettingsRequest,
sendGetColumnInfoRequest,
sendInsertDataRequest
});
}
}

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 * as sqlops from 'sqlops';
import * as vscode from 'vscode';
import * as contracts from './contracts';
import { SqlOpsDataClient } from 'dataprotocol-client/lib/main';
export enum ApiType {
FlatFileProvider = 'FlatFileProvider'
}
export interface IServiceApi {
onRegisteredApi<T>(type: ApiType): vscode.Event<T>;
registerApi<T>(type: ApiType, feature: T): vscode.Disposable;
}
export interface IModelViewDefinition {
id: string;
modelView: sqlops.ModelView;
}
export class ServiceApiManager implements IServiceApi {
private modelViewRegistrations: { [id: string]: boolean } = {};
private featureEventChannels: { [type: string]: vscode.EventEmitter<any> } = {};
private _onRegisteredModelView = new vscode.EventEmitter<IModelViewDefinition>();
public onRegisteredApi<T>(type: ApiType): vscode.Event<T> {
let featureEmitter = this.featureEventChannels[type];
if (!featureEmitter) {
featureEmitter = new vscode.EventEmitter<T>();
this.featureEventChannels[type] = featureEmitter;
}
return featureEmitter.event;
}
public registerApi<T>(type: ApiType, feature: T): vscode.Disposable {
let featureEmitter = this.featureEventChannels[type];
if (featureEmitter) {
featureEmitter.fire(feature);
}
// TODO handle unregistering API on close
return {
dispose: () => undefined
};
}
public get onRegisteredModelView(): vscode.Event<IModelViewDefinition> {
return this._onRegisteredModelView.event;
}
public registerModelView(id: string, modelView: sqlops.ModelView): void {
this._onRegisteredModelView.fire({
id: id,
modelView: modelView
});
}
}
export let managerInstance = new ServiceApiManager();

View File

@@ -0,0 +1,165 @@
/*---------------------------------------------------------------------------------------------
* 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 { SqlOpsDataClient, ClientOptions } from 'dataprotocol-client';
import { IConfig, ServerProvider, Events } from 'service-downloader';
import { ServerOptions, TransportKind } from 'vscode-languageclient';
import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
import * as path from 'path';
import { EventAndListener } from 'eventemitter2';
import { Telemetry, LanguageClientErrorHandler } from './telemetry';
import * as Constants from '../constants';
import { TelemetryFeature, FlatFileImportFeature } from './features';
import * as serviceUtils from './serviceUtils';
const baseConfig = require('./config.json');
export class ServiceClient {
private statusView: vscode.StatusBarItem;
constructor(private outputChannel: vscode.OutputChannel) {
this.statusView = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left);
}
public startService(context: vscode.ExtensionContext): Promise<SqlOpsDataClient> {
let config: IConfig = JSON.parse(JSON.stringify(baseConfig));
config.installDirectory = path.join(context.extensionPath, config.installDirectory);
config.proxy = vscode.workspace.getConfiguration('http').get('proxy');
config.strictSSL = vscode.workspace.getConfiguration('http').get('proxyStrictSSL') || true;
const serverdownloader = new ServerProvider(config);
serverdownloader.eventEmitter.onAny(this.generateHandleServerProviderEvent());
let clientOptions: ClientOptions = this.createClientOptions();
const installationStart = Date.now();
let client: SqlOpsDataClient;
return new Promise((resolve, reject) => {
serverdownloader.getOrDownloadServer().then(e => {
const installationComplete = Date.now();
let serverOptions = this.generateServerOptions(e);
client = new SqlOpsDataClient(Constants.serviceName, serverOptions, clientOptions);
const processStart = Date.now();
client.onReady().then(() => {
const processEnd = Date.now();
this.statusView.text = localize('serviceStarted', 'Service Started');
setTimeout(() => {
this.statusView.hide();
}, 1500);
Telemetry.sendTelemetryEvent('startup/LanguageClientStarted', {
installationTime: String(installationComplete - installationStart),
processStartupTime: String(processEnd - processStart),
totalTime: String(processEnd - installationStart),
beginningTimestamp: String(installationStart)
});
});
this.statusView.show();
this.statusView.text = localize('serviceStarting', 'Starting service');
let disposable = client.start();
context.subscriptions.push(disposable);
resolve(client);
}, e => {
Telemetry.sendTelemetryEvent('ServiceInitializingFailed');
vscode.window.showErrorMessage(localize('flatFileImport.serviceStartFailed', 'Failed to start Import service{0}', e));
// Just resolve to avoid unhandled promise. We show the error to the user.
resolve(undefined);
});
});
}
private createClientOptions(): ClientOptions {
return {
providerId: Constants.providerId,
errorHandler: new LanguageClientErrorHandler(),
synchronize: {
configurationSection: [Constants.extensionConfigSectionName, Constants.sqlConfigSectionName]
},
features: [
// we only want to add new features
TelemetryFeature,
FlatFileImportFeature
],
outputChannel: new CustomOutputChannel()
};
}
private generateServerOptions(executablePath: string): ServerOptions {
let launchArgs = [];
launchArgs.push('--log-dir');
let logFileLocation = path.join(serviceUtils.getDefaultLogLocation(), 'flatfileimport');
launchArgs.push(logFileLocation);
let config = vscode.workspace.getConfiguration(Constants.extensionConfigSectionName);
if (config) {
let logDebugInfo = config[Constants.configLogDebugInfo];
if (logDebugInfo) {
launchArgs.push('--enable-logging');
}
}
return { command: executablePath, args: launchArgs, transport: TransportKind.stdio };
}
private generateHandleServerProviderEvent(): EventAndListener {
let dots = 0;
return (e: string, ...args: any[]) => {
this.outputChannel.show();
this.statusView.show();
switch (e) {
case Events.INSTALL_START:
this.outputChannel.appendLine(localize('installingServiceDetailed', 'Installing {0} service to {1}', Constants.serviceName, args[0]));
this.statusView.text = localize('installingService', 'Installing Service');
break;
case Events.INSTALL_END:
this.outputChannel.appendLine(localize('serviceInstalled', 'Installed'));
break;
case Events.DOWNLOAD_START:
this.outputChannel.appendLine(localize('downloadingService', 'Downloading {0}', args[0]));
this.outputChannel.append(`(${Math.ceil(args[1] / 1024)} KB)`);
this.statusView.text = localize('downloadingServiceStatus', 'Downloading Service');
break;
case Events.DOWNLOAD_PROGRESS:
let newDots = Math.ceil(args[0] / 5);
if (newDots > dots) {
this.outputChannel.append('.'.repeat(newDots - dots));
dots = newDots;
}
break;
case Events.DOWNLOAD_END:
this.outputChannel.appendLine(localize('downloadingServiceComplete', 'Done!'));
break;
default:
break;
}
};
}
}
class CustomOutputChannel implements vscode.OutputChannel {
name: string;
append(value: string): void {
}
appendLine(value: string): void {
}
// tslint:disable-next-line:no-empty
clear(): void {
}
show(preserveFocus?: boolean): void;
show(column?: vscode.ViewColumn, preserveFocus?: boolean): void;
// tslint:disable-next-line:no-empty
show(column?: any, preserveFocus?: any): void {
}
// tslint:disable-next-line:no-empty
hide(): void {
}
// tslint:disable-next-line:no-empty
dispose(): void {
}
}

View File

@@ -0,0 +1,156 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as path from 'path';
import * as crypto from 'crypto';
import * as os from 'os';
const baseConfig = require('./config.json');
// The function is a duplicate of \src\paths.js. IT would be better to import path.js but it doesn't
// work for now because the extension is running in different process.
export function getAppDataPath(): string {
let platform = process.platform;
switch (platform) {
case 'win32': return process.env['APPDATA'] || path.join(process.env['USERPROFILE'], 'AppData', 'Roaming');
case 'darwin': return path.join(os.homedir(), 'Library', 'Application Support');
case 'linux': return process.env['XDG_CONFIG_HOME'] || path.join(os.homedir(), '.config');
default: throw new Error('Platform not supported');
}
}
export function getDefaultLogLocation(): string {
return path.join(getAppDataPath(), 'sqlops');
}
export function ensure(target: object, key: string): any {
if (target[key] === void 0) {
target[key] = {} as any;
}
return target[key];
}
export interface IPackageInfo {
name: string;
version: string;
aiKey: string;
}
export function getPackageInfo(packageJson: any): IPackageInfo {
if (packageJson) {
return {
name: packageJson.name,
version: packageJson.version,
aiKey: packageJson.aiKey
};
}
}
export function generateUserId(): Promise<string> {
return new Promise<string>(resolve => {
try {
let interfaces = os.networkInterfaces();
let mac;
for (let key of Object.keys(interfaces)) {
let item = interfaces[key][0];
if (!item.internal) {
mac = item.mac;
break;
}
}
if (mac) {
resolve(crypto.createHash('sha256').update(mac + os.homedir(), 'utf8').digest('hex'));
} else {
resolve(generateGuid());
}
} catch (err) {
resolve(generateGuid()); // fallback
}
});
}
export function generateGuid(): string {
let hexValues: string[] = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'];
// c.f. rfc4122 (UUID version 4 = xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx)
let oct: string = '';
let tmp: number;
/* tslint:disable:no-bitwise */
for (let a: number = 0; a < 4; a++) {
tmp = (4294967296 * Math.random()) | 0;
oct += hexValues[tmp & 0xF] +
hexValues[tmp >> 4 & 0xF] +
hexValues[tmp >> 8 & 0xF] +
hexValues[tmp >> 12 & 0xF] +
hexValues[tmp >> 16 & 0xF] +
hexValues[tmp >> 20 & 0xF] +
hexValues[tmp >> 24 & 0xF] +
hexValues[tmp >> 28 & 0xF];
}
// 'Set the two most significant bits (bits 6 and 7) of the clock_seq_hi_and_reserved to zero and one, respectively'
let clockSequenceHi: string = hexValues[8 + (Math.random() * 4) | 0];
return oct.substr(0, 8) + '-' + oct.substr(9, 4) + '-4' + oct.substr(13, 3) + '-' + clockSequenceHi + oct.substr(16, 3) + '-' + oct.substr(19, 12);
/* tslint:enable:no-bitwise */
}
export function verifyPlatform(): Thenable<boolean> {
if (os.platform() === 'darwin' && parseFloat(os.release()) < 16.0) {
return Promise.resolve(false);
} else {
return Promise.resolve(true);
}
}
export function getServiceInstallConfig(basePath?: string): any {
if (!basePath) {
basePath = __dirname;
}
let config = JSON.parse(JSON.stringify(baseConfig));
config.installDirectory = path.join(basePath, config.installDirectory);
return config;
}
export function getResolvedServiceInstallationPath(runtime: Runtime, basePath?: string): string {
let config = getServiceInstallConfig(basePath);
let dir = config.installDirectory;
dir = dir.replace('{#version#}', config.version);
dir = dir.replace('{#platform#}', getRuntimeDisplayName(runtime));
return dir;
}
export function getRuntimeDisplayName(runtime: Runtime): string {
switch (runtime) {
case Runtime.Windows_64:
return 'Windows';
case Runtime.Windows_86:
return 'Windows';
case Runtime.OSX:
return 'OSX';
case Runtime.Linux_64:
return 'Linux';
default:
return 'Unknown';
}
}
export enum Runtime {
Unknown = <any>'Unknown',
Windows_86 = <any>'Windows_86',
Windows_64 = <any>'Windows_64',
OSX = <any>'OSX',
CentOS_7 = <any>'CentOS_7',
Debian_8 = <any>'Debian_8',
Fedora_23 = <any>'Fedora_23',
OpenSUSE_13_2 = <any>'OpenSUSE_13_2',
SLES_12_2 = <any>'SLES_12_2',
RHEL_7 = <any>'RHEL_7',
Ubuntu_14 = <any>'Ubuntu_14',
Ubuntu_16 = <any>'Ubuntu_16',
Linux_64 = <any>'Linux_64',
Linux_86 = <any>'Linux-86'
}

View File

@@ -0,0 +1,216 @@
/*---------------------------------------------------------------------------------------------
* 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 { ErrorAction, CloseAction } from 'vscode-languageclient';
import TelemetryReporter from 'vscode-extension-telemetry';
import { PlatformInformation } from 'service-downloader/out/platform';
import * as opener from 'opener';
import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
import * as constants from '../constants';
import * as serviceUtils from './serviceUtils';
import { IMessage, ITelemetryEventProperties, ITelemetryEventMeasures } from './contracts';
/**
* Handle Language Service client errors
* @class LanguageClientErrorHandler
*/
export class LanguageClientErrorHandler {
/**
* Creates an instance of LanguageClientErrorHandler.
* @memberOf LanguageClientErrorHandler
*/
constructor() {
}
/**
* Show an error message prompt with a link to known issues wiki page
* @memberOf LanguageClientErrorHandler
*/
showOnErrorPrompt(): void {
// TODO add telemetry
// Telemetry.sendTelemetryEvent('SqlToolsServiceCrash');
let crashButtonText = localize('import.serviceCrashButton', 'Give Feedback');
vscode.window.showErrorMessage(
localize('serviceCrashMessage', 'service component could not start'),
crashButtonText
).then(action => {
if (action && action === crashButtonText) {
opener(constants.serviceCrashLink);
}
});
}
/**
* Callback for language service client error
*
* @param {Error} error
* @param {Message} message
* @param {number} count
* @returns {ErrorAction}
*
* @memberOf LanguageClientErrorHandler
*/
error(error: Error, message: IMessage, count: number): ErrorAction {
this.showOnErrorPrompt();
// we don't retry running the service since crashes leave the extension
// in a bad, unrecovered state
return ErrorAction.Shutdown;
}
/**
* Callback for language service client closed
*
* @returns {CloseAction}
*
* @memberOf LanguageClientErrorHandler
*/
closed(): CloseAction {
this.showOnErrorPrompt();
// we don't retry running the service since crashes leave the extension
// in a bad, unrecovered state
return CloseAction.DoNotRestart;
}
}
/**
* Filters error paths to only include source files. Exported to support testing
*/
export function FilterErrorPath(line: string): string {
if (line) {
let values: string[] = line.split('/out/');
if (values.length <= 1) {
// Didn't match expected format
return line;
} else {
return values[1];
}
}
}
export class Telemetry {
private static reporter: TelemetryReporter;
private static userId: string;
private static platformInformation: PlatformInformation;
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 = serviceUtils.generateUserId();
id.then(newId => {
this.userId = newId;
resolve(this.userId);
});
} else {
resolve(this.userId);
}
});
}
public static getPlatformInformation(): Promise<PlatformInformation> {
if (this.platformInformation) {
return Promise.resolve(this.platformInformation);
} else {
return new Promise<PlatformInformation>(resolve => {
PlatformInformation.getCurrent().then(info => {
this.platformInformation = info;
resolve(this.platformInformation);
});
});
}
}
/**
* Disable telemetry reporting
*/
public static disable(): void {
this.disabled = true;
}
/**
* Initialize the telemetry reporter for use.
*/
public static initialize(): void {
if (typeof this.reporter === 'undefined') {
// Check if the user has opted out of telemetry
if (!vscode.workspace.getConfiguration('telemetry').get<boolean>('enableTelemetry', true)) {
this.disable();
return;
}
let packageInfo = vscode.extensions.getExtension('Microsoft.import').packageJSON;
this.reporter = new TelemetryReporter(packageInfo.name, packageInfo.version, packageInfo.aiKey);
}
}
/**
* Send a telemetry event for an exception
*/
public static sendTelemetryEventForException(
err: any, methodName: string, extensionConfigName: string): void {
try {
let stackArray: string[];
let firstLine: string = '';
if (err !== undefined && err.stack !== undefined) {
stackArray = err.stack.split('\n');
if (stackArray !== undefined && stackArray.length >= 2) {
firstLine = stackArray[1]; // The fist line is the error message and we don't want to send that telemetry event
firstLine = FilterErrorPath(firstLine);
}
}
// Only adding the method name and the fist line of the stack trace. We don't add the error message because it might have PII
this.sendTelemetryEvent('Exception', { methodName: methodName, errorLine: firstLine });
// Utils.logDebug('Unhandled Exception occurred. error: ' + err + ' method: ' + methodName, extensionConfigName);
} catch (telemetryErr) {
// If sending telemetry event fails ignore it so it won't break the extension
// Utils.logDebug('Failed to send telemetry event. error: ' + telemetryErr, extensionConfigName);
}
}
/**
* Send a telemetry event using application insights
*/
public static sendTelemetryEvent(
eventName: string,
properties?: ITelemetryEventProperties,
measures?: ITelemetryEventMeasures): void {
if (typeof this.disabled === 'undefined') {
this.disabled = false;
}
if (this.disabled || typeof (this.reporter) === 'undefined') {
// Don't do anything if telemetry is disabled
return;
}
if (!properties || typeof properties === 'undefined') {
properties = {};
}
// Augment the properties structure with additional common properties before sending
Promise.all([this.getUserId(), this.getPlatformInformation()]).then(() => {
properties['userId'] = this.userId;
properties['distribution'] = (this.platformInformation && this.platformInformation.distribution) ?
`${this.platformInformation.distribution.name}, ${this.platformInformation.distribution.version}` : '';
this.reporter.sendTelemetryEvent(eventName, properties, measures);
});
}
}
Telemetry.initialize();

View File

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

View File

@@ -0,0 +1,59 @@
/*---------------------------------------------------------------------------------------------
* 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 { ImportDataModel } from './models';
import * as sqlops from 'sqlops';
import { FlatFileProvider } from '../../services/contracts';
import { FlatFileWizard } from '../flatFileWizard';
export abstract class ImportPage {
protected readonly wizardPage: sqlops.window.modelviewdialog.WizardPage;
protected readonly instance: FlatFileWizard;
protected readonly model: ImportDataModel;
protected readonly view: sqlops.ModelView;
protected readonly provider: FlatFileProvider;
protected constructor(instance: FlatFileWizard, wizardPage: sqlops.window.modelviewdialog.WizardPage, model: ImportDataModel, view: sqlops.ModelView, provider: FlatFileProvider) {
this.instance = instance;
this.wizardPage = wizardPage;
this.model = model;
this.view = view;
this.provider = provider;
}
/**
* This method constructs all the elements of the page.
* @returns {Promise<boolean>}
*/
public async abstract start(): Promise<boolean>;
/**
* This method is called when the user is entering the page.
* @returns {Promise<boolean>}
*/
public async abstract onPageEnter(): Promise<boolean>;
/**
* This method is called when the user is leaving the page.
* @returns {Promise<boolean>}
*/
public async abstract onPageLeave(): Promise<boolean>;
/**
* Sets up a navigation validator.
* This will be called right before onPageEnter().
*/
public abstract setupNavigationValidator();
/**
* Override this method to cleanup what you don't need cached in the page.
* @returns {Promise<boolean>}
*/
public async cleanup(): Promise<boolean> {
return true;
}
}

View File

@@ -0,0 +1,33 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as sqlops from 'sqlops';
/**
* The main data model that communicates between the pages.
*/
export interface ImportDataModel {
ownerUri: string;
proseColumns: ColumnMetadata[];
proseDataPreview: string[][];
server: sqlops.connection.Connection;
serverId: string;
database: string;
table: string;
schema: string;
filePath: string;
fileType: string;
}
/**
* Metadata of a column
*/
export interface ColumnMetadata {
columnName: string;
dataType: string;
primaryKey: boolean;
nullable: boolean;
}

View File

@@ -0,0 +1,144 @@
/*---------------------------------------------------------------------------------------------
* 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 nls from 'vscode-nls';
import * as sqlops from 'sqlops';
import { FlatFileProvider } from '../services/contracts';
import { ImportDataModel } from './api/models';
import { ImportPage } from './api/importPage';
// pages
import { FileConfigPage } from './pages/fileConfigPage';
import { ProsePreviewPage } from './pages/prosePreviewPage';
import { ModifyColumnsPage } from './pages/modifyColumnsPage';
import { SummaryPage } from './pages/summaryPage';
const localize = nls.loadMessageBundle();
export class FlatFileWizard {
private readonly provider: FlatFileProvider;
private wizard: sqlops.window.modelviewdialog.Wizard;
private importAnotherFileButton: sqlops.window.modelviewdialog.Button;
constructor(provider: FlatFileProvider) {
this.provider = provider;
}
public async start(p: any, ...args: any[]) {
let model = <ImportDataModel>{};
let profile = <sqlops.IConnectionProfile>p.connectionProfile;
if (profile) {
model.serverId = profile.id;
model.database = profile.databaseName;
}
let pages: Map<number, ImportPage> = new Map<number, ImportPage>();
let connections = await sqlops.connection.getActiveConnections();
if (!connections || connections.length === 0) {
vscode.window.showErrorMessage(localize('import.needConnection', 'Please connect to a server before using this wizard.'));
return;
}
this.wizard = sqlops.window.modelviewdialog.createWizard(localize('flatFileImport.wizardName', 'Import flat file wizard'));
let page1 = sqlops.window.modelviewdialog.createWizardPage(localize('flatFileImport.page1Name', 'Specify Input File'));
let page2 = sqlops.window.modelviewdialog.createWizardPage(localize('flatFileImport.page2Name', 'Preview Data'));
let page3 = sqlops.window.modelviewdialog.createWizardPage(localize('flatFileImport.page3Name', 'Modify Columns'));
let page4 = sqlops.window.modelviewdialog.createWizardPage(localize('flatFileImport.page4Name', 'Summary'));
let fileConfigPage: FileConfigPage;
page1.registerContent(async (view) => {
fileConfigPage = new FileConfigPage(this, page1, model, view, this.provider);
pages.set(0, fileConfigPage);
await fileConfigPage.start().then(() => {
fileConfigPage.setupNavigationValidator();
fileConfigPage.onPageEnter();
});
});
let prosePreviewPage: ProsePreviewPage;
page2.registerContent(async (view) => {
prosePreviewPage = new ProsePreviewPage(this, page2, model, view, this.provider);
pages.set(1, prosePreviewPage);
await prosePreviewPage.start();
});
let modifyColumnsPage: ModifyColumnsPage;
page3.registerContent(async (view) => {
modifyColumnsPage = new ModifyColumnsPage(this, page3, model, view, this.provider);
pages.set(2, modifyColumnsPage);
await modifyColumnsPage.start();
});
let summaryPage: SummaryPage;
page4.registerContent(async (view) => {
summaryPage = new SummaryPage(this, page4, model, view, this.provider);
pages.set(3, summaryPage);
await summaryPage.start();
});
this.importAnotherFileButton = sqlops.window.modelviewdialog.createButton(localize('flatFileImport.importNewFile', 'Import new file'));
this.importAnotherFileButton.onClick(() => {
//TODO replace this with proper cleanup for all the pages
this.wizard.close();
pages.forEach((page) => page.cleanup());
this.wizard.open();
});
this.importAnotherFileButton.hidden = true;
this.wizard.customButtons = [this.importAnotherFileButton];
this.wizard.onPageChanged(async (event) => {
let idx = event.newPage;
let page = pages.get(idx);
if (page) {
page.setupNavigationValidator();
page.onPageEnter();
}
});
this.wizard.onPageChanged(async (event) => {
let idx = event.lastPage;
let page = pages.get(idx);
if (page) {
page.onPageLeave();
}
});
//not needed for this wizard
this.wizard.generateScriptButton.hidden = true;
this.wizard.pages = [page1, page2, page3, page4];
this.wizard.open();
}
public setImportAnotherFileVisibility(visibility: boolean) {
this.importAnotherFileButton.hidden = !visibility;
}
public registerNavigationValidator(validator: (pageChangeInfo: sqlops.window.modelviewdialog.WizardPageChangeInfo) => boolean) {
this.wizard.registerNavigationValidator(validator);
}
public changeNextButtonLabel(label: string) {
this.wizard.nextButton.label = label;
}
}

View File

@@ -0,0 +1,411 @@
/*---------------------------------------------------------------------------------------------
* 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';
import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
import { ImportDataModel } from '../api/models';
import { ImportPage } from '../api/importPage';
import { FlatFileProvider } from '../../services/contracts';
import { FlatFileWizard } from '../flatFileWizard';
const localize = nls.loadMessageBundle();
export class FileConfigPage extends ImportPage {
private serverDropdown: sqlops.DropDownComponent;
private databaseDropdown: sqlops.DropDownComponent;
private fileTextBox: sqlops.InputBoxComponent;
private fileButton: sqlops.ButtonComponent;
private tableNameTextBox: sqlops.InputBoxComponent;
private schemaDropdown: sqlops.DropDownComponent;
private form: sqlops.FormContainer;
private databaseLoader: sqlops.LoadingComponent;
private schemaLoader: sqlops.LoadingComponent;
private tableNames: string[] = [];
public constructor(instance: FlatFileWizard, wizardPage: sqlops.window.modelviewdialog.WizardPage, model: ImportDataModel, view: sqlops.ModelView, provider: FlatFileProvider) {
super(instance, wizardPage, model, view, provider);
}
async start(): Promise<boolean> {
let schemaComponent = await this.createSchemaDropdown();
let tableNameComponent = await this.createTableNameBox();
let fileBrowserComponent = await this.createFileBrowser();
let databaseComponent = await this.createDatabaseDropdown();
let serverComponent = await this.createServerDropdown();
this.form = this.view.modelBuilder.formContainer()
.withFormItems(
[
serverComponent,
databaseComponent,
fileBrowserComponent,
tableNameComponent,
schemaComponent
]).component();
await this.view.initializeModel(this.form);
return true;
}
async onPageEnter(): Promise<boolean> {
let r1 = await this.populateServerDropdown();
let r2 = await this.populateDatabaseDropdown();
let r3 = await this.populateSchemaDropdown();
return r1 && r2 && r3;
}
async onPageLeave(): Promise<boolean> {
delete this.model.serverId;
return true;
}
public async cleanup(): Promise<boolean> {
delete this.model.filePath;
delete this.model.table;
return true;
}
public setupNavigationValidator() {
this.instance.registerNavigationValidator((info) => {
if (this.schemaLoader.loading || this.databaseLoader.loading) {
return false;
}
return true;
});
}
private async createServerDropdown(): Promise<sqlops.FormComponent> {
this.serverDropdown = this.view.modelBuilder.dropDown().withProperties({
required: true
}).component();
// Handle server changes
this.serverDropdown.onValueChanged(async (params) => {
this.model.server = (this.serverDropdown.value as ConnectionDropdownValue).connection;
await this.populateDatabaseDropdown();
await this.populateSchemaDropdown();
});
return {
component: this.serverDropdown,
title: localize('flatFileImport.serverDropdownTitle', 'Server the database is in')
};
}
private async populateServerDropdown(): Promise<boolean> {
let cons = await sqlops.connection.getActiveConnections();
// This user has no active connections ABORT MISSION
if (!cons || cons.length === 0) {
return true;
}
let count = -1;
let idx = -1;
let values = cons.map(c => {
// Handle the code to remember what the user's choice was from before
count++;
if (idx === -1) {
if (this.model.server && c.connectionId === this.model.server.connectionId) {
idx = count;
} else if (this.model.serverId && c.connectionId === this.model.serverId) {
idx = count;
}
}
let db = c.options.databaseDisplayName;
let usr = c.options.user;
let srv = c.options.server;
if (!db) {
db = '<default>';
}
if (!usr) {
usr = 'default';
}
let finalName = `${srv}, ${db} (${usr})`;
return {
connection: c,
displayName: finalName,
name: c.connectionId
};
});
if (idx >= 0) {
let tmp = values[0];
values[0] = values[idx];
values[idx] = tmp;
} else {
delete this.model.server;
delete this.model.serverId;
delete this.model.database;
delete this.model.schema;
}
this.model.server = values[0].connection;
this.serverDropdown.updateProperties({
values: values
});
return true;
}
private async createDatabaseDropdown(): Promise<sqlops.FormComponent> {
this.databaseDropdown = this.view.modelBuilder.dropDown().withProperties({
required: true
}).component();
// Handle database changes
this.databaseDropdown.onValueChanged(async (db) => {
this.model.database = (<sqlops.CategoryValue>this.databaseDropdown.value).name;
//this.populateTableNames();
this.populateSchemaDropdown();
});
this.databaseLoader = this.view.modelBuilder.loadingComponent().withItem(this.databaseDropdown).component();
return {
component: this.databaseLoader,
title: localize('flatFileImport.databaseDropdownTitle', 'Database the table is created in')
};
}
private async populateDatabaseDropdown(): Promise<boolean> {
this.databaseLoader.loading = true;
this.databaseDropdown.updateProperties({ values: [] });
this.schemaDropdown.updateProperties({ values: [] });
if (!this.model.server) {
//TODO handle error case
this.databaseLoader.loading = false;
return false;
}
let idx = -1;
let count = -1;
let values = (await sqlops.connection.listDatabases(this.model.server.connectionId)).map(db => {
count++;
if (this.model.database && db === this.model.database) {
idx = count;
}
return {
displayName: db,
name: db
};
});
if (idx >= 0) {
let tmp = values[0];
values[0] = values[idx];
values[idx] = tmp;
} else {
delete this.model.database;
delete this.model.schema;
}
this.model.database = values[0].name;
this.databaseDropdown.updateProperties({
values: values
});
this.databaseLoader.loading = false;
return true;
}
private async createFileBrowser(): Promise<sqlops.FormComponent> {
this.fileTextBox = this.view.modelBuilder.inputBox().withProperties({
required: true
}).component();
this.fileButton = this.view.modelBuilder.button().withProperties({
label: localize('flatFileImport.browseFiles', 'Browse'),
}).component();
this.fileButton.onDidClick(async (click) => {
let fileUris = await vscode.window.showOpenDialog(
{
canSelectFiles: true,
canSelectFolders: false,
canSelectMany: false,
openLabel: localize('flatFileImport.openFile', 'Open'),
filters: {
'CSV/TXT Files': ['csv', 'txt'],
'All Files': ['*']
}
}
);
if (!fileUris || fileUris.length === 0) {
return;
}
let fileUri = fileUris[0];
this.fileTextBox.value = fileUri.fsPath;
// Get the name of the file.
let nameStart = fileUri.path.lastIndexOf('/');
let nameEnd = fileUri.path.lastIndexOf('.');
// Handle files without extensions
if (nameEnd === 0) {
nameEnd = fileUri.path.length;
}
this.model.fileType = 'TXT';
let extension = fileUri.path.substring(nameEnd + 1, fileUri.path.length);
if (extension.toLowerCase() === 'json') {
this.model.fileType = 'JSON';
}
this.tableNameTextBox.value = fileUri.path.substring(nameStart + 1, nameEnd);
this.model.table = this.tableNameTextBox.value;
this.tableNameTextBox.validate();
// Let then model know about the file path
this.model.filePath = fileUri.fsPath;
});
return {
component: this.fileTextBox,
title: localize('flatFileImport.fileTextboxTitle', 'Location of the file to be imported'),
actions: [this.fileButton]
};
}
private async createTableNameBox(): Promise<sqlops.FormComponent> {
this.tableNameTextBox = this.view.modelBuilder.inputBox().withValidation((name) => {
let tableName = name.value;
if (!tableName || tableName.length === 0) {
return false;
}
// This won't actually do anything until table names are brought back in.
if (this.tableNames.indexOf(tableName) !== -1) {
return false;
}
return true;
}).withProperties({
required: true,
}).component();
this.tableNameTextBox.onTextChanged((tableName) => {
this.model.table = tableName;
});
return {
component: this.tableNameTextBox,
title: localize('flatFileImport.tableTextboxTitle', 'New table name'),
};
}
private async createSchemaDropdown(): Promise<sqlops.FormComponent> {
this.schemaDropdown = this.view.modelBuilder.dropDown().withProperties({
required: true
}).component();
this.schemaLoader = this.view.modelBuilder.loadingComponent().withItem(this.schemaDropdown).component();
this.schemaDropdown.onValueChanged(() => {
this.model.schema = (<sqlops.CategoryValue>this.schemaDropdown.value).name;
});
return {
component: this.schemaLoader,
title: localize('flatFileImport.schemaTextboxTitle', 'Table schema'),
};
}
private async populateSchemaDropdown(): Promise<boolean> {
this.schemaLoader.loading = true;
let connectionUri = await sqlops.connection.getUriForConnection(this.model.server.connectionId);
let queryProvider = sqlops.dataprotocol.getProvider<sqlops.QueryProvider>(this.model.server.providerName, sqlops.DataProviderType.QueryProvider);
let query = `SELECT name FROM sys.schemas`;
let results = await queryProvider.runQueryAndReturn(connectionUri, query);
let idx = -1;
let count = -1;
let values = results.rows.map(row => {
let schemaName = row[0].displayValue;
count++;
if (this.model.schema && schemaName === this.model.schema) {
idx = count;
}
let val = row[0].displayValue;
return {
name: val,
displayName: val
};
});
if (idx >= 0) {
let tmp = values[0];
values[0] = values[idx];
values[idx] = tmp;
}
this.model.schema = values[0].name;
this.schemaDropdown.updateProperties({
values: values
});
this.schemaLoader.loading = false;
return true;
}
// private async populateTableNames(): Promise<boolean> {
// this.tableNames = [];
// let databaseName = (<sqlops.CategoryValue>this.databaseDropdown.value).name;
//
// if (!databaseName || databaseName.length === 0) {
// this.tableNames = [];
// return false;
// }
//
// let connectionUri = await sqlops.connection.getUriForConnection(this.model.server.connectionId);
// let queryProvider = sqlops.dataprotocol.getProvider<sqlops.QueryProvider>(this.model.server.providerName, sqlops.DataProviderType.QueryProvider);
// let results: sqlops.SimpleExecuteResult;
//
// try {
// //let query = sqlstring.format('USE ?; SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = \'BASE TABLE\'', [databaseName]);
// //results = await queryProvider.runQueryAndReturn(connectionUri, query);
// } catch (e) {
// return false;
// }
//
// this.tableNames = results.rows.map(row => {
// return row[0].displayValue;
// });
//
// return true;
// }
}
interface ConnectionDropdownValue extends sqlops.CategoryValue {
connection: sqlops.connection.Connection;
}

View File

@@ -0,0 +1,171 @@
/*---------------------------------------------------------------------------------------------
* 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';
import * as nls from 'vscode-nls';
import { ColumnMetadata, ImportDataModel } from '../api/models';
import { ImportPage } from '../api/importPage';
import { FlatFileProvider } from '../../services/contracts';
import { FlatFileWizard } from '../flatFileWizard';
const localize = nls.loadMessageBundle();
export class ModifyColumnsPage extends ImportPage {
private readonly categoryValues = [
{ name: 'bigint', displayName: 'bigint' },
{ name: 'binary(50)', displayName: 'binary(50)' },
{ name: 'bit', displayName: 'bit' },
{ name: 'char(10)', displayName: 'char(10)' },
{ name: 'date', displayName: 'date' },
{ name: 'datetime', displayName: 'datetime' },
{ name: 'datetime2(7)', displayName: 'datetime2(7)' },
{ name: 'datetimeoffset(7)', displayName: 'datetimeoffset(7)' },
{ name: 'decimal(18, 10)', displayName: 'decimal(18, 10)' },
{ name: 'float', displayName: 'float' },
{ name: 'geography', displayName: 'geography' },
{ name: 'geometry', displayName: 'geometry' },
{ name: 'hierarchyid', displayName: 'hierarchyid' },
{ name: 'int', displayName: 'int' },
{ name: 'money', displayName: 'money' },
{ name: 'nchar(10)', displayName: 'nchar(10)' },
{ name: 'ntext', displayName: 'ntext' },
{ name: 'numeric(18, 0)', displayName: 'numeric(18, 0)' },
{ name: 'nvarchar(50)', displayName: 'nvarchar(50)' },
{ name: 'nvarchar(MAX)', displayName: 'nvarchar(MAX)' },
{ name: 'real', displayName: 'real' },
{ name: 'smalldatetime', displayName: 'smalldatetime' },
{ name: 'smallint', displayName: 'smallint' },
{ name: 'smallmoney', displayName: 'smallmoney' },
{ name: 'sql_variant', displayName: 'sql_variant' },
{ name: 'text', displayName: 'text' },
{ name: 'time(7)', displayName: 'time(7)' },
{ name: 'timestamp', displayName: 'timestamp' },
{ name: 'tinyint', displayName: 'tinyint' },
{ name: 'uniqueidentifier', displayName: 'uniqueidentifier' },
{ name: 'varbinary(50)', displayName: 'varbinary(50)' },
{ name: 'varbinary(MAX)', displayName: 'varbinary(MAX)' },
{ name: 'varchar(50)', displayName: 'varchar(50)' },
{ name: 'varchar(MAX)', displayName: 'varchar(MAX)' }
];
private table: sqlops.DeclarativeTableComponent;
private loading: sqlops.LoadingComponent;
private text: sqlops.TextComponent;
private form: sqlops.FormContainer;
public constructor(instance: FlatFileWizard, wizardPage: sqlops.window.modelviewdialog.WizardPage, model: ImportDataModel, view: sqlops.ModelView, provider: FlatFileProvider) {
super(instance, wizardPage, model, view, provider);
}
private static convertMetadata(column: ColumnMetadata): any[] {
return [column.columnName, column.dataType, false, column.nullable];
}
async start(): Promise<boolean> {
this.loading = this.view.modelBuilder.loadingComponent().component();
this.table = this.view.modelBuilder.declarativeTable().component();
this.text = this.view.modelBuilder.text().component();
this.table.onDataChanged((e) => {
this.model.proseColumns = [];
this.table.data.forEach((row) => {
this.model.proseColumns.push({
columnName: row[0],
dataType: row[1],
primaryKey: row[2],
nullable: row[3]
});
});
});
this.form = this.view.modelBuilder.formContainer()
.withFormItems(
[
{
component: this.text,
title: ''
},
{
component: this.table,
title: ''
}
], {
horizontal: false,
componentWidth: '100%'
}).component();
this.loading.component = this.form;
await this.view.initializeModel(this.form);
return true;
}
async onPageEnter(): Promise<boolean> {
this.loading.loading = true;
await this.populateTable();
this.instance.changeNextButtonLabel(localize('flatFileImport.importData', 'Import Data'));
this.loading.loading = false;
return true;
}
async onPageLeave(): Promise<boolean> {
this.instance.changeNextButtonLabel(localize('flatFileImport.next', 'Next'));
return undefined;
}
async cleanup(): Promise<boolean> {
delete this.model.proseColumns;
this.instance.changeNextButtonLabel(localize('flatFileImport.next', 'Next'));
return true;
}
public setupNavigationValidator() {
this.instance.registerNavigationValidator((info) => {
return !this.loading.loading;
});
}
private async populateTable() {
let data: any[][] = [];
this.model.proseColumns.forEach((column) => {
data.push(ModifyColumnsPage.convertMetadata(column));
});
this.table.updateProperties({
height: 400,
columns: [{
displayName: localize('flatFileImport.columnName', 'Column Name'),
valueType: sqlops.DeclarativeDataType.string,
width: '150px',
isReadOnly: false
}, {
displayName: localize('flatFileImport.dataType', 'Data Type'),
valueType: sqlops.DeclarativeDataType.editableCategory,
width: '150px',
isReadOnly: false,
categoryValues: this.categoryValues
}, {
displayName: localize('flatFileImport.primaryKey', 'Primary Key'),
valueType: sqlops.DeclarativeDataType.boolean,
width: '100px',
isReadOnly: false
}, {
displayName: localize('flatFileImport.allowNulls', 'Allow Nulls'),
valueType: sqlops.DeclarativeDataType.boolean,
isReadOnly: false,
width: '100px'
}],
data: data
});
}
}

View File

@@ -0,0 +1,123 @@
/*---------------------------------------------------------------------------------------------
* 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';
import * as nls from 'vscode-nls';
import { ImportDataModel } from '../api/models';
import { ImportPage } from '../api/importPage';
import { FlatFileProvider } from '../../services/contracts';
import { FlatFileWizard } from '../flatFileWizard';
const localize = nls.loadMessageBundle();
export class ProsePreviewPage extends ImportPage {
private table: sqlops.TableComponent;
private loading: sqlops.LoadingComponent;
private form: sqlops.FormContainer;
private refresh: sqlops.ButtonComponent;
public constructor(instance: FlatFileWizard, wizardPage: sqlops.window.modelviewdialog.WizardPage, model: ImportDataModel, view: sqlops.ModelView, provider: FlatFileProvider) {
super(instance, wizardPage, model, view, provider);
}
async start(): Promise<boolean> {
this.table = this.view.modelBuilder.table().component();
this.refresh = this.view.modelBuilder.button().withProperties({
label: localize('flatFileImport.refresh', 'Refresh'),
isFile: false
}).component();
this.refresh.onDidClick(async () => {
this.onPageEnter();
});
this.loading = this.view.modelBuilder.loadingComponent().component();
this.form = this.view.modelBuilder.formContainer().withFormItems([
{
component: this.table,
title: localize('flatFileImport.prosePreviewMessage', 'This operation analyzed the input file structure to generate the preview below for up to the first 50 rows.'),
actions: [this.refresh]
}
]).component();
this.loading.component = this.form;
await this.view.initializeModel(this.loading);
return true;
}
async onPageEnter(): Promise<boolean> {
this.loading.loading = true;
await this.handleProse();
await this.populateTable(this.model.proseDataPreview, this.model.proseColumns.map(c => c.columnName));
this.loading.loading = false;
return true;
}
async onPageLeave(): Promise<boolean> {
await this.emptyTable();
return true;
}
async cleanup(): Promise<boolean> {
delete this.model.proseDataPreview;
return true;
}
public setupNavigationValidator() {
this.instance.registerNavigationValidator((info) => {
return !this.loading.loading;
});
}
private async handleProse() {
await this.provider.sendPROSEDiscoveryRequest({
filePath: this.model.filePath,
tableName: this.model.table,
schemaName: this.model.schema,
fileType: this.model.fileType
}).then((result) => {
this.model.proseDataPreview = result.dataPreview;
this.model.proseColumns = [];
result.columnInfo.forEach((column) => {
this.model.proseColumns.push({
columnName: column.name,
dataType: column.sqlType,
primaryKey: false,
nullable: column.isNullable
});
});
});
}
private async populateTable(tableData: string[][], columnHeaders: string[]) {
let rows;
let rowsLength = tableData.length;
if (rowsLength > 50) {
rows = tableData;
}
else {
rows = tableData.slice(0, rowsLength);
}
this.table.updateProperties({
data: rows,
columns: columnHeaders,
height: 400,
width: '700',
});
}
private async emptyTable() {
this.table.updateProperties([]);
}
}

View File

@@ -0,0 +1,173 @@
/*---------------------------------------------------------------------------------------------
* 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';
import * as nls from 'vscode-nls';
import { ImportDataModel } from '../api/models';
import { ImportPage } from '../api/importPage';
import { FlatFileProvider, InsertDataResponse } from '../../services/contracts';
import { FlatFileWizard } from '../flatFileWizard';
const localize = nls.loadMessageBundle();
export class SummaryPage extends ImportPage {
private table: sqlops.TableComponent;
private statusText: sqlops.TextComponent;
private loading: sqlops.LoadingComponent;
private form: sqlops.FormContainer;
public constructor(instance: FlatFileWizard, wizardPage: sqlops.window.modelviewdialog.WizardPage, model: ImportDataModel, view: sqlops.ModelView, provider: FlatFileProvider) {
super(instance, wizardPage, model, view, provider);
}
async start(): Promise<boolean> {
this.table = this.view.modelBuilder.table().component();
this.statusText = this.view.modelBuilder.text().component();
this.loading = this.view.modelBuilder.loadingComponent().withItem(this.statusText).component();
this.form = this.view.modelBuilder.formContainer().withFormItems(
[
{
component: this.table,
title: localize('flatFileImport.importInformation', 'Import information')
},
{
component: this.loading,
title: localize('flatFileImport.importStatus', 'Import status')
}
]
).component();
await this.view.initializeModel(this.form);
return true;
}
async onPageEnter(): Promise<boolean> {
this.loading.loading = true;
this.populateTable();
await this.handleImport();
this.loading.loading = false;
this.instance.setImportAnotherFileVisibility(true);
return true;
}
async onPageLeave(): Promise<boolean> {
this.instance.setImportAnotherFileVisibility(false);
return true;
}
public setupNavigationValidator() {
this.instance.registerNavigationValidator((info) => {
return !this.loading.loading;
});
}
private populateTable() {
this.table.updateProperties({
data: [
[localize('flatFileImport.serverName', 'Server name'), this.model.server.providerName],
[localize('flatFileImport.databaseName', 'Database name'), this.model.database],
[localize('flatFileImport.tableName', 'Table name'), this.model.table],
[localize('flatFileImport.tableSchema', 'Table schema'), this.model.schema],
[localize('flatFileImport.fileImport', 'File to be imported'), this.model.filePath]],
columns: ['Object type', 'Name'],
width: 600,
height: 200
});
}
private async handleImport(): Promise<boolean> {
let changeColumnResults = [];
this.model.proseColumns.forEach((val, i, arr) => {
let columnChangeParams = {
index: i,
newName: val.columnName,
newDataType: val.dataType,
newNullable: val.nullable,
newInPrimaryKey: val.primaryKey
};
changeColumnResults.push(this.provider.sendChangeColumnSettingsRequest(columnChangeParams));
});
let result: InsertDataResponse;
let err;
try {
result = await this.provider.sendInsertDataRequest({
connectionString: await this.getConnectionString(),
//TODO check what SSMS uses as batch size
batchSize: 500
});
} catch (e) {
err = e.toString();
}
let updateText: string;
if (!result || !result.result.success) {
updateText = '✗ ';
if (!result) {
updateText += err;
} else {
updateText += result.result.errorMessage;
}
} else {
// TODO: When sql statements are in, implement this.
//let rows = await this.getCountRowsInserted();
//if (rows < 0) {
updateText = localize('flatFileImport.success.norows', '✔ You have successfully inserted the data into a table.');
//} else {
//updateText = localize('flatFileImport.success.rows', '✔ You have successfully inserted {0} rows.', rows);
//}
}
this.statusText.updateProperties({
value: updateText
});
return true;
}
/**
* Gets the connection string to send to the middleware
* @returns {Promise<string>}
*/
private async getConnectionString(): Promise<string> {
let options = this.model.server.options;
let connectionString: string;
if (options.authenticationType === 'Integrated') {
connectionString = `Data Source=${options.server + (options.port ? `,${options.port}` : '')};Initial Catalog=${this.model.database};Integrated Security=True`;
} else {
let credentials = await sqlops.connection.getCredentials(this.model.server.connectionId);
connectionString = `Data Source=${options.server + (options.port ? `,${options.port}` : '')};Initial Catalog=${this.model.database};Integrated Security=False;User Id=${options.user};Password=${credentials.password}`;
}
// TODO: Fix this, it's returning undefined string.
//await sqlops.connection.getConnectionString(this.model.server.connectionId, true);
return connectionString;
}
// private async getCountRowsInserted(): Promise<Number> {
// let connectionUri = await sqlops.connection.getUriForConnection(this.model.server.connectionId);
// let queryProvider = sqlops.dataprotocol.getProvider<sqlops.QueryProvider>(this.model.server.providerName, sqlops.DataProviderType.QueryProvider);
// try {
// let query = sqlstring.format('USE ?; SELECT COUNT(*) FROM ?', [this.model.database, this.model.table]);
// let results = await queryProvider.runQueryAndReturn(connectionUri, query);
// let cell = results.rows[0][0];
// if (!cell || cell.isNull) {
// return -1;
// }
// let numericCell = Number(cell.displayValue);
// if (isNaN(numericCell)) {
// return -1;
// }
// return numericCell;
// } catch (e) {
// return -1;
// }
// }
}

View File

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

409
extensions/import/yarn.lock Normal file
View File

@@ -0,0 +1,409 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
agent-base@4, agent-base@^4.1.0:
version "4.2.1"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9"
dependencies:
es6-promisify "^5.0.0"
applicationinsights@0.15.6:
version "0.15.6"
resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-0.15.6.tgz#201a0682c0704fe4bdd9a92d0b2cbe34d2ae5972"
base64-js@0.0.8:
version "0.0.8"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-0.0.8.tgz#1101e9544f4a76b1bc3b26d452ca96d7a35e7978"
bl@^1.0.0:
version "1.2.2"
resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.2.tgz#a160911717103c07410cef63ef51b397c025af9c"
dependencies:
readable-stream "^2.3.5"
safe-buffer "^5.1.1"
buffer-alloc-unsafe@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0"
buffer-alloc@^1.1.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/buffer-alloc/-/buffer-alloc-1.2.0.tgz#890dd90d923a873e08e10e5fd51a57e5b7cce0ec"
dependencies:
buffer-alloc-unsafe "^1.1.0"
buffer-fill "^1.0.0"
buffer-crc32@~0.2.3:
version "0.2.13"
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
buffer-fill@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c"
buffer@^3.0.1:
version "3.6.0"
resolved "https://registry.yarnpkg.com/buffer/-/buffer-3.6.0.tgz#a72c936f77b96bf52f5f7e7b467180628551defb"
dependencies:
base64-js "0.0.8"
ieee754 "^1.1.4"
isarray "^1.0.0"
commander@~2.8.1:
version "2.8.1"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.8.1.tgz#06be367febfda0c330aa1e2a072d3dc9762425d4"
dependencies:
graceful-readlink ">= 1.0.0"
core-util-is@~1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
"dataprotocol-client@github:Microsoft/sqlops-dataprotocolclient#0.2.7":
version "0.2.6"
resolved "https://codeload.github.com/Microsoft/sqlops-dataprotocolclient/tar.gz/85653d8b305af8aef334728d71f07bdc240dfcb7"
dependencies:
vscode-languageclient "3.5.1"
debug@3.1.0, debug@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
dependencies:
ms "2.0.0"
decompress-tar@^4.0.0, decompress-tar@^4.1.0, decompress-tar@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/decompress-tar/-/decompress-tar-4.1.1.tgz#718cbd3fcb16209716e70a26b84e7ba4592e5af1"
dependencies:
file-type "^5.2.0"
is-stream "^1.1.0"
tar-stream "^1.5.2"
decompress-tarbz2@^4.0.0:
version "4.1.1"
resolved "https://registry.yarnpkg.com/decompress-tarbz2/-/decompress-tarbz2-4.1.1.tgz#3082a5b880ea4043816349f378b56c516be1a39b"
dependencies:
decompress-tar "^4.1.0"
file-type "^6.1.0"
is-stream "^1.1.0"
seek-bzip "^1.0.5"
unbzip2-stream "^1.0.9"
decompress-targz@^4.0.0:
version "4.1.1"
resolved "https://registry.yarnpkg.com/decompress-targz/-/decompress-targz-4.1.1.tgz#c09bc35c4d11f3de09f2d2da53e9de23e7ce1eee"
dependencies:
decompress-tar "^4.1.1"
file-type "^5.2.0"
is-stream "^1.1.0"
decompress-unzip@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/decompress-unzip/-/decompress-unzip-4.0.1.tgz#deaaccdfd14aeaf85578f733ae8210f9b4848f69"
dependencies:
file-type "^3.8.0"
get-stream "^2.2.0"
pify "^2.3.0"
yauzl "^2.4.2"
decompress@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/decompress/-/decompress-4.2.0.tgz#7aedd85427e5a92dacfe55674a7c505e96d01f9d"
dependencies:
decompress-tar "^4.0.0"
decompress-tarbz2 "^4.0.0"
decompress-targz "^4.0.0"
decompress-unzip "^4.0.1"
graceful-fs "^4.1.10"
make-dir "^1.0.0"
pify "^2.3.0"
strip-dirs "^2.0.0"
end-of-stream@^1.0.0:
version "1.4.1"
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43"
dependencies:
once "^1.4.0"
es6-promise@^4.0.3:
version "4.2.4"
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.4.tgz#dc4221c2b16518760bd8c39a52d8f356fc00ed29"
es6-promisify@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203"
dependencies:
es6-promise "^4.0.3"
eventemitter2@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-5.0.1.tgz#6197a095d5fb6b57e8942f6fd7eaad63a09c9452"
fd-slicer@~1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e"
dependencies:
pend "~1.2.0"
file-type@^3.8.0:
version "3.9.0"
resolved "https://registry.yarnpkg.com/file-type/-/file-type-3.9.0.tgz#257a078384d1db8087bc449d107d52a52672b9e9"
file-type@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/file-type/-/file-type-5.2.0.tgz#2ddbea7c73ffe36368dfae49dc338c058c2b8ad6"
file-type@^6.1.0:
version "6.2.0"
resolved "https://registry.yarnpkg.com/file-type/-/file-type-6.2.0.tgz#e50cd75d356ffed4e306dc4f5bcf52a79903a919"
fs-constants@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
get-stream@^2.2.0:
version "2.3.1"
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-2.3.1.tgz#5f38f93f346009666ee0150a054167f91bdd95de"
dependencies:
object-assign "^4.0.1"
pinkie-promise "^2.0.0"
graceful-fs@^4.1.10:
version "4.1.11"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658"
"graceful-readlink@>= 1.0.0":
version "1.0.1"
resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725"
http-proxy-agent@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz#e4821beef5b2142a2026bd73926fe537631c5405"
dependencies:
agent-base "4"
debug "3.1.0"
https-proxy-agent@^2.1.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz#51552970fa04d723e04c56d04178c3f92592bbc0"
dependencies:
agent-base "^4.1.0"
debug "^3.1.0"
ieee754@^1.1.4:
version "1.1.12"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.12.tgz#50bf24e5b9c8bb98af4964c941cdb0918da7b60b"
inherits@~2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
is-natural-number@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/is-natural-number/-/is-natural-number-4.0.1.tgz#ab9d76e1db4ced51e35de0c72ebecf09f734cde8"
is-stream@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
isarray@^1.0.0, isarray@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
make-dir@^1.0.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c"
dependencies:
pify "^3.0.0"
minimist@0.0.8:
version "0.0.8"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
mkdirp@^0.5.1:
version "0.5.1"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
dependencies:
minimist "0.0.8"
ms@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
object-assign@^4.0.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
once@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
dependencies:
wrappy "1"
opener@^1.4.3:
version "1.4.3"
resolved "https://registry.yarnpkg.com/opener/-/opener-1.4.3.tgz#5c6da2c5d7e5831e8ffa3964950f8d6674ac90b8"
os-tmpdir@~1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
pend@~1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
pify@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
pify@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176"
pinkie-promise@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa"
dependencies:
pinkie "^2.0.0"
pinkie@^2.0.0:
version "2.0.4"
resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"
process-nextick-args@~2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa"
readable-stream@^2.3.0, readable-stream@^2.3.5:
version "2.3.6"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf"
dependencies:
core-util-is "~1.0.0"
inherits "~2.0.3"
isarray "~1.0.0"
process-nextick-args "~2.0.0"
safe-buffer "~5.1.1"
string_decoder "~1.1.1"
util-deprecate "~1.0.1"
safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
version "5.1.2"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
seek-bzip@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/seek-bzip/-/seek-bzip-1.0.5.tgz#cfe917cb3d274bcffac792758af53173eb1fabdc"
dependencies:
commander "~2.8.1"
"service-downloader@github:anthonydresser/service-downloader#0.1.4":
version "0.1.4"
resolved "https://codeload.github.com/anthonydresser/service-downloader/tar.gz/3c0abdf8603aca85d2eacfac3c547173e41bf0c7"
dependencies:
decompress "^4.2.0"
eventemitter2 "^5.0.1"
http-proxy-agent "^2.0.0"
https-proxy-agent "^2.1.1"
mkdirp "^0.5.1"
tmp "^0.0.33"
string_decoder@~1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
dependencies:
safe-buffer "~5.1.0"
strip-dirs@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/strip-dirs/-/strip-dirs-2.1.0.tgz#4987736264fc344cf20f6c34aca9d13d1d4ed6c5"
dependencies:
is-natural-number "^4.0.1"
tar-stream@^1.5.2:
version "1.6.1"
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.6.1.tgz#f84ef1696269d6223ca48f6e1eeede3f7e81f395"
dependencies:
bl "^1.0.0"
buffer-alloc "^1.1.0"
end-of-stream "^1.0.0"
fs-constants "^1.0.0"
readable-stream "^2.3.0"
to-buffer "^1.1.0"
xtend "^4.0.0"
through@^2.3.6:
version "2.3.8"
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
tmp@^0.0.33:
version "0.0.33"
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
dependencies:
os-tmpdir "~1.0.2"
to-buffer@^1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/to-buffer/-/to-buffer-1.1.1.tgz#493bd48f62d7c43fcded313a03dcadb2e1213a80"
unbzip2-stream@^1.0.9:
version "1.2.5"
resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.2.5.tgz#73a033a567bbbde59654b193c44d48a7e4f43c47"
dependencies:
buffer "^3.0.1"
through "^2.3.6"
util-deprecate@~1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
vscode-extension-telemetry@^0.0.5:
version "0.0.5"
resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.0.5.tgz#21e2abb4cbce3326e469ddbb322123b3702f3f85"
dependencies:
applicationinsights "0.15.6"
winreg "0.0.13"
vscode-jsonrpc@3.5.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-3.5.0.tgz#87239d9e166b2d7352245b8a813597804c1d63aa"
vscode-languageclient@3.5.1:
version "3.5.1"
resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-3.5.1.tgz#c78e582459c24e58f88020dfa34065e976186a98"
dependencies:
vscode-languageserver-protocol "3.5.1"
vscode-languageserver-protocol@3.5.1:
version "3.5.1"
resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.5.1.tgz#5144a3a9eeccbd83fe2745bd4ed75fad6cc45f0d"
dependencies:
vscode-jsonrpc "3.5.0"
vscode-languageserver-types "3.5.0"
vscode-languageserver-types@3.5.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.5.0.tgz#e48d79962f0b8e02de955e3f524908e2b19c0374"
vscode-nls@^3.2.1:
version "3.2.4"
resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.4.tgz#2166b4183c8aea884d20727f5449e62be69fd398"
winreg@0.0.13:
version "0.0.13"
resolved "https://registry.yarnpkg.com/winreg/-/winreg-0.0.13.tgz#76bfe02e1dd0c9c8275fb9fdf17a9f36846e3483"
wrappy@1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
xtend@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
yauzl@^2.4.2:
version "2.10.0"
resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"
dependencies:
buffer-crc32 "~0.2.3"
fd-slicer "~1.1.0"

View File

@@ -0,0 +1,4 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1

View File

@@ -0,0 +1,4 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1

View File

@@ -17,6 +17,14 @@
"compile": "gulp compile-extension:mssql-client",
"update-grammar": "node ../../build/npm/update-grammar.js Microsoft/vscode-mssql syntaxes/SQL.plist ./syntaxes/sql.tmLanguage.json"
},
"dependencies": {
"dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#0.2.7",
"opener": "^1.4.3",
"service-downloader": "github:anthonydresser/service-downloader#0.1.4",
"vscode-extension-telemetry": "^0.0.15"
},
"devDependencies": {
},
"contributes": {
"languages": [
{
@@ -656,19 +664,5 @@
}
]
}
},
"dependencies": {
"dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#0.1.9",
"opener": "^1.4.3",
"service-downloader": "github:anthonydresser/service-downloader#0.1.2",
"vscode-extension-telemetry": "^0.0.15"
},
"devDependencies": {
},
"resolutions": {
"vscode-jsonrpc": "3.5.0",
"vscode-languageclient": "3.5.0",
"vscode-languageserver-protocol": "3.5.0",
"vscode-languageserver-types": "3.5.0"
}
}

View File

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

View File

@@ -3,8 +3,8 @@
agent-base@4, agent-base@^4.1.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.0.tgz#9838b5c3392b962bad031e6a4c5e1024abec45ce"
version "4.2.1"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9"
dependencies:
es6-promisify "^5.0.0"
@@ -27,10 +27,25 @@ bl@^1.0.0:
readable-stream "^2.3.5"
safe-buffer "^5.1.1"
buffer-alloc-unsafe@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0"
buffer-alloc@^1.1.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/buffer-alloc/-/buffer-alloc-1.2.0.tgz#890dd90d923a873e08e10e5fd51a57e5b7cce0ec"
dependencies:
buffer-alloc-unsafe "^1.1.0"
buffer-fill "^1.0.0"
buffer-crc32@~0.2.3:
version "0.2.13"
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
buffer-fill@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c"
buffer@^3.0.1:
version "3.6.0"
resolved "https://registry.yarnpkg.com/buffer/-/buffer-3.6.0.tgz#a72c936f77b96bf52f5f7e7b467180628551defb"
@@ -49,11 +64,11 @@ core-util-is@~1.0.0:
version "1.0.2"
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":
version "0.1.9"
resolved "https://codeload.github.com/Microsoft/sqlops-dataprotocolclient/tar.gz/a1a79895cb79658b75d78aa5cfd745855019148d"
"dataprotocol-client@github:Microsoft/sqlops-dataprotocolclient#0.2.7":
version "0.2.6"
resolved "https://codeload.github.com/Microsoft/sqlops-dataprotocolclient/tar.gz/85653d8b305af8aef334728d71f07bdc240dfcb7"
dependencies:
vscode-languageclient "3.5.0"
vscode-languageclient "3.5.1"
debug@3.1.0, debug@^3.1.0:
version "3.1.0"
@@ -139,9 +154,9 @@ eventemitter2@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-5.0.1.tgz#6197a095d5fb6b57e8942f6fd7eaad63a09c9452"
fd-slicer@~1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.0.1.tgz#8b5bcbd9ec327c5041bf9ab023fd6750f1177e65"
fd-slicer@~1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e"
dependencies:
pend "~1.2.0"
@@ -157,6 +172,10 @@ file-type@^6.1.0:
version "6.2.0"
resolved "https://registry.yarnpkg.com/file-type/-/file-type-6.2.0.tgz#e50cd75d356ffed4e306dc4f5bcf52a79903a919"
fs-constants@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
get-stream@^2.2.0:
version "2.3.1"
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-2.3.1.tgz#5f38f93f346009666ee0150a054167f91bdd95de"
@@ -180,15 +199,15 @@ http-proxy-agent@^2.0.0:
debug "3.1.0"
https-proxy-agent@^2.1.1:
version "2.2.0"
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.0.tgz#7fbba856be8cd677986f42ebd3664f6317257887"
version "2.2.1"
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz#51552970fa04d723e04c56d04178c3f92592bbc0"
dependencies:
agent-base "^4.1.0"
debug "^3.1.0"
ieee754@^1.1.4:
version "1.1.11"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.11.tgz#c16384ffe00f5b7835824e67b6f2bd44a5229455"
version "1.1.12"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.12.tgz#50bf24e5b9c8bb98af4964c941cdb0918da7b60b"
inherits@~2.0.3:
version "2.0.3"
@@ -207,8 +226,8 @@ isarray@^1.0.0, isarray@~1.0.0:
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
make-dir@^1.0.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.2.0.tgz#6d6a49eead4aae296c53bbf3a1a008bd6c89469b"
version "1.3.0"
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c"
dependencies:
pify "^3.0.0"
@@ -270,21 +289,21 @@ process-nextick-args@~2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa"
readable-stream@^2.0.0, readable-stream@^2.3.5:
version "2.3.5"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.5.tgz#b4f85003a938cbb6ecbce2a124fb1012bd1a838d"
readable-stream@^2.3.0, readable-stream@^2.3.5:
version "2.3.6"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf"
dependencies:
core-util-is "~1.0.0"
inherits "~2.0.3"
isarray "~1.0.0"
process-nextick-args "~2.0.0"
safe-buffer "~5.1.1"
string_decoder "~1.0.3"
string_decoder "~1.1.1"
util-deprecate "~1.0.1"
safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853"
version "5.1.2"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
seek-bzip@^1.0.5:
version "1.0.5"
@@ -296,9 +315,9 @@ semver@^5.3.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab"
"service-downloader@github:anthonydresser/service-downloader#0.1.2":
version "0.1.2"
resolved "https://codeload.github.com/anthonydresser/service-downloader/tar.gz/2aa9b336b6442e17e24693ddc907030575539798"
"service-downloader@github:anthonydresser/service-downloader#0.1.4":
version "0.1.4"
resolved "https://codeload.github.com/anthonydresser/service-downloader/tar.gz/3c0abdf8603aca85d2eacfac3c547173e41bf0c7"
dependencies:
decompress "^4.2.0"
eventemitter2 "^5.0.1"
@@ -307,9 +326,9 @@ semver@^5.3.0:
mkdirp "^0.5.1"
tmp "^0.0.33"
string_decoder@~1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab"
string_decoder@~1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
dependencies:
safe-buffer "~5.1.0"
@@ -320,12 +339,15 @@ strip-dirs@^2.0.0:
is-natural-number "^4.0.1"
tar-stream@^1.5.2:
version "1.5.5"
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.5.5.tgz#5cad84779f45c83b1f2508d96b09d88c7218af55"
version "1.6.1"
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.6.1.tgz#f84ef1696269d6223ca48f6e1eeede3f7e81f395"
dependencies:
bl "^1.0.0"
buffer-alloc "^1.1.0"
end-of-stream "^1.0.0"
readable-stream "^2.0.0"
fs-constants "^1.0.0"
readable-stream "^2.3.0"
to-buffer "^1.1.0"
xtend "^4.0.0"
through@^2.3.6:
@@ -338,6 +360,10 @@ tmp@^0.0.33:
dependencies:
os-tmpdir "~1.0.2"
to-buffer@^1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/to-buffer/-/to-buffer-1.1.1.tgz#493bd48f62d7c43fcded313a03dcadb2e1213a80"
unbzip2-stream@^1.0.9:
version "1.2.5"
resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.2.5.tgz#73a033a567bbbde59654b193c44d48a7e4f43c47"
@@ -355,24 +381,24 @@ vscode-extension-telemetry@^0.0.15:
dependencies:
applicationinsights "1.0.1"
vscode-jsonrpc@3.5.0, vscode-jsonrpc@^3.5.0:
vscode-jsonrpc@3.5.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-3.5.0.tgz#87239d9e166b2d7352245b8a813597804c1d63aa"
vscode-languageclient@3.5.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-3.5.0.tgz#36d02cc186a8365a4467719a290fb200a9ae490a"
vscode-languageclient@3.5.1:
version "3.5.1"
resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-3.5.1.tgz#c78e582459c24e58f88020dfa34065e976186a98"
dependencies:
vscode-languageserver-protocol "^3.5.0"
vscode-languageserver-protocol "3.5.1"
vscode-languageserver-protocol@3.5.0, vscode-languageserver-protocol@^3.5.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.5.0.tgz#067c5cbe27709795398d119692c97ebba1452209"
vscode-languageserver-protocol@3.5.1:
version "3.5.1"
resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.5.1.tgz#5144a3a9eeccbd83fe2745bd4ed75fad6cc45f0d"
dependencies:
vscode-jsonrpc "^3.5.0"
vscode-languageserver-types "^3.5.0"
vscode-jsonrpc "3.5.0"
vscode-languageserver-types "3.5.0"
vscode-languageserver-types@3.5.0, vscode-languageserver-types@^3.5.0:
vscode-languageserver-types@3.5.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.5.0.tgz#e48d79962f0b8e02de955e3f524908e2b19c0374"
@@ -385,11 +411,11 @@ xtend@^4.0.0:
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
yauzl@^2.4.2:
version "2.9.1"
resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.9.1.tgz#a81981ea70a57946133883f029c5821a89359a7f"
version "2.10.0"
resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"
dependencies:
buffer-crc32 "~0.2.3"
fd-slicer "~1.0.1"
fd-slicer "~1.1.0"
zone.js@0.7.6:
version "0.7.6"

View File

@@ -10,6 +10,17 @@ Common SQL Profiler use-cases taken from https://docs.microsoft.com/en-us/sql/to
- Monitoring the performance of SQL Server to tune workloads.
- Correlating performance counters to diagnose problems.
## Getting Started:
To launch SQL Server Profiler, you have to first make a connection to a server.
Open Profiler by pressing **Alt+P** on Windows, and **Ctrl+Alt+P** on macOS.
To Start/Stop Profiler, click the Start button or press **Alt+S** on Windows, or **Ctrl+Alt+S** on macOS.
Otherwise, open the command palette and type 'Profiler.'
For more info, [check out our documentation.](https://docs.microsoft.com/en-us/sql/sql-operations-studio/sql-server-profiler-extension?view=sql-server-2017)
## SQL Server Profiler 0.1.1 Release
The SQL Server Profiler for SQL Operations Studio *Preview* extension is now available. This is the initial preview release for a new lightweight XEvent-based profiler. The SQL Server Profiler extension tries to make it simple to quickly trace server activity for troubleshooting and monitoring.

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';
import * as vscode from 'vscode';
import * as data from 'sqlops';
import * as sqlops from 'sqlops';
import { ApiWrapper } from './apiWrapper';
import { CreateSessionDialog } from './dialogs/profilerCreateSessionDialog';
/**
* The main controller class that initializes the extension
*/
export class MainController {
protected _apiWrapper: ApiWrapper;
protected _context: vscode.ExtensionContext;
export class MainController {
protected _apiWrapper: ApiWrapper;
protected _context: vscode.ExtensionContext;
// PUBLIC METHODS //////////////////////////////////////////////////////
public constructor(context: vscode.ExtensionContext, apiWrapper?: ApiWrapper) {
this._apiWrapper = apiWrapper || new ApiWrapper();
this._context = context;
}
// PUBLIC METHODS
public constructor(context: vscode.ExtensionContext, apiWrapper?: ApiWrapper) {
this._apiWrapper = apiWrapper || new ApiWrapper();
this._context = context;
}
/**
* 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/sql/sqlops.d.ts'/>
/// <reference path='../../../../../src/sql/sqlops.proposed.d.ts'/>
/// <reference types='@types/node'/>

View File

@@ -2,7 +2,7 @@
"name": "profiler",
"displayName": "SQL Server Profiler",
"description": "SQL Server Profiler for SQL Operations Studio",
"version": "0.1.2",
"version": "0.1.5",
"publisher": "Microsoft",
"preview": true,
"license": "https://raw.githubusercontent.com/Microsoft/sqlopsstudio/master/LICENSE.txt",
@@ -27,7 +27,7 @@
],
"contributes": {
"commands": [
"commands": [
{
"command": "profiler.newProfiler",
"title": "New Profiler",
@@ -42,12 +42,20 @@
"command": "profiler.stop",
"title": "Stop",
"category": "Profiler"
},
{
"command": "profiler.openCreateSessionDialog",
"title": "Create Profiler Session",
"category": "Profiler"
}
],
"outputChannels": [
"sqlprofiler"
]
},
"dependencies": {
"vscode-nls": "^3.2.1"
},
"devDependencies": {
"vscode": "1.0.1"
}

View File

@@ -2035,6 +2035,10 @@ vinyl@~2.0.1:
remove-trailing-separator "^1.0.1"
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:
version "1.0.1"
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": [
["{", "}"],

View File

@@ -232,11 +232,11 @@
},
"_db_light": {
"fontCharacter": "\\E01C",
"fontColor": "#dd4b78"
"fontColor": "#bfc2c1"
},
"_db": {
"fontCharacter": "\\E01C",
"fontColor": "#f55385"
"fontColor": "#d4d7d6"
},
"_default_light": {
"fontCharacter": "\\E01D",
@@ -936,11 +936,11 @@
},
"_shell_light": {
"fontCharacter": "\\E073",
"fontColor": "#455155"
"fontColor": "#bfc2c1"
},
"_shell": {
"fontCharacter": "\\E073",
"fontColor": "#4d5a5e"
"fontColor": "#d4d7d6"
},
"_slim_light": {
"fontCharacter": "\\E074",
@@ -1374,7 +1374,9 @@
"cmakelists.txt": "_makefile_3",
"procfile": "_heroku",
"todo": "_todo",
"npm-debug.log": "_npm_ignored"
"npm-debug.log": "_npm_ignored",
"dashboard": "_shell",
"profiler": "_csv"
},
"languageIds": {
"bat": "_windows",
@@ -1660,7 +1662,9 @@
"omakefile": "_makefile_2_light",
"cmakelists.txt": "_makefile_3_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"

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
[Desktop Entry]
Name=@@NAME_LONG@@
Comment=SQL Operations Studio
Comment=Data Management Tool that enables you to work with SQL Server, Azure SQL DB and SQL DW from Windows, macOS and Linux.
GenericName=Text Editor
Exec=/usr/share/@@NAME@@/@@NAME@@ --unity-launch %F
Icon=@@ICON@@

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,589 @@
<?xml version="1.0" encoding="utf-8"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file original="extensions/agent/out/dialogs/scheduleDialog" source-language="en" datatype="plaintext"><body>
<trans-unit id="scheduleDialog.newSchedule">
<source xml:lang="en">New Schedule</source>
</trans-unit>
<trans-unit id="scheduleDialog.ok">
<source xml:lang="en">OK</source>
</trans-unit>
<trans-unit id="scheduleDialog.cancel">
<source xml:lang="en">Cancel</source>
</trans-unit>
<trans-unit id="scheduleDialog.scheduleName">
<source xml:lang="en">Schedule Name</source>
</trans-unit>
<trans-unit id="scheduleDialog.schedules">
<source xml:lang="en">Schedules</source>
</trans-unit>
</body></file>
<file original="extensions/agent/out/dialogs/proxyDialog" source-language="en" datatype="plaintext"><body>
<trans-unit id="createProxy.createProxy">
<source xml:lang="en">Create Proxy</source>
</trans-unit>
<trans-unit id="createProxy.editProxy">
<source xml:lang="en">Edit Proxy</source>
</trans-unit>
<trans-unit id="createProxy.General">
<source xml:lang="en">General</source>
</trans-unit>
<trans-unit id="createProxy.ProxyName">
<source xml:lang="en">Proxy name</source>
</trans-unit>
<trans-unit id="createProxy.CredentialName">
<source xml:lang="en">Credential name</source>
</trans-unit>
<trans-unit id="createProxy.Description">
<source xml:lang="en">Description</source>
</trans-unit>
<trans-unit id="createProxy.SubsystemName">
<source xml:lang="en">Subsystem</source>
</trans-unit>
<trans-unit id="createProxy.OperatingSystem">
<source xml:lang="en">Operating system (CmdExec)</source>
</trans-unit>
<trans-unit id="createProxy.ReplicationSnapshot">
<source xml:lang="en">Replication Snapshot</source>
</trans-unit>
<trans-unit id="createProxy.ReplicationTransactionLog">
<source xml:lang="en">Replication Transaction-Log Reader</source>
</trans-unit>
<trans-unit id="createProxy.ReplicationDistributor">
<source xml:lang="en">Replication Distributor</source>
</trans-unit>
<trans-unit id="createProxy.ReplicationMerge">
<source xml:lang="en">Replication Merge</source>
</trans-unit>
<trans-unit id="createProxy.ReplicationQueueReader">
<source xml:lang="en">Replication Queue Reader</source>
</trans-unit>
<trans-unit id="createProxy.SSASQueryLabel">
<source xml:lang="en">SQL Server Analysis Services Query</source>
</trans-unit>
<trans-unit id="createProxy.SSASCommandLabel">
<source xml:lang="en">SQL Server Analysis Services Command</source>
</trans-unit>
<trans-unit id="createProxy.SSISPackage">
<source xml:lang="en">SQL Server Integration Services Package</source>
</trans-unit>
<trans-unit id="createProxy.PowerShell">
<source xml:lang="en">PowerShell</source>
</trans-unit>
<trans-unit id="createProxy.subSystemHeading">
<source xml:lang="en">Active to the following subsytems</source>
</trans-unit>
</body></file>
<file original="extensions/agent/out/dialogs/pickScheduleDialog" source-language="en" datatype="plaintext"><body>
<trans-unit id="pickSchedule.jobSchedules">
<source xml:lang="en">Job Schedules</source>
</trans-unit>
<trans-unit id="pickSchedule.ok">
<source xml:lang="en">OK</source>
</trans-unit>
<trans-unit id="pickSchedule.cancel">
<source xml:lang="en">Cancel</source>
</trans-unit>
<trans-unit id="pickSchedule.scheduleName">
<source xml:lang="en">Schedule Name</source>
</trans-unit>
<trans-unit id="pickSchedule.schedules">
<source xml:lang="en">Schedules</source>
</trans-unit>
</body></file>
<file original="extensions/agent/out/dialogs/operatorDialog" source-language="en" datatype="plaintext"><body>
<trans-unit id="createOperator.createOperator">
<source xml:lang="en">Create Operator</source>
</trans-unit>
<trans-unit id="createOperator.editOperator">
<source xml:lang="en">Edit Operator</source>
</trans-unit>
<trans-unit id="createOperator.General">
<source xml:lang="en">General</source>
</trans-unit>
<trans-unit id="createOperator.Notifications">
<source xml:lang="en">Notifications</source>
</trans-unit>
<trans-unit id="createOperator.Name">
<source xml:lang="en">Name</source>
</trans-unit>
<trans-unit id="createOperator.Enabled">
<source xml:lang="en">Enabled</source>
</trans-unit>
<trans-unit id="createOperator.EmailName">
<source xml:lang="en">E-mail Name</source>
</trans-unit>
<trans-unit id="createOperator.PagerEmailName">
<source xml:lang="en">Pager E-mail Name</source>
</trans-unit>
<trans-unit id="createOperator.PagerMondayCheckBox">
<source xml:lang="en">Monday</source>
</trans-unit>
<trans-unit id="createOperator.PagerTuesdayCheckBox">
<source xml:lang="en">Tuesday</source>
</trans-unit>
<trans-unit id="createOperator.PagerWednesdayCheckBox">
<source xml:lang="en">Wednesday</source>
</trans-unit>
<trans-unit id="createOperator.PagerThursdayCheckBox">
<source xml:lang="en">Thursday</source>
</trans-unit>
<trans-unit id="createOperator.PagerFridayCheckBox">
<source xml:lang="en">Friday </source>
</trans-unit>
<trans-unit id="createOperator.PagerSaturdayCheckBox">
<source xml:lang="en">Saturday</source>
</trans-unit>
<trans-unit id="createOperator.PagerSundayCheckBox">
<source xml:lang="en">Sunday</source>
</trans-unit>
<trans-unit id="createOperator.workdayBegin">
<source xml:lang="en">Workday begin</source>
</trans-unit>
<trans-unit id="createOperator.workdayEnd">
<source xml:lang="en">Workday end</source>
</trans-unit>
<trans-unit id="createOperator.PagerDutySchedule">
<source xml:lang="en">Pager on duty schdule</source>
</trans-unit>
<trans-unit id="createOperator.AlertListHeading">
<source xml:lang="en">Alert list</source>
</trans-unit>
<trans-unit id="createOperator.AlertNameColumnLabel">
<source xml:lang="en">Alert name</source>
</trans-unit>
<trans-unit id="createOperator.AlertEmailColumnLabel">
<source xml:lang="en">E-mail</source>
</trans-unit>
<trans-unit id="createOperator.AlertPagerColumnLabel">
<source xml:lang="en">Pager</source>
</trans-unit>
</body></file>
<file original="extensions/agent/out/dialogs/jobStepDialog" source-language="en" datatype="plaintext"><body>
<trans-unit id="jobStepDialog.newJobStep">
<source xml:lang="en">New Job Step</source>
</trans-unit>
<trans-unit id="jobStepDialog.fileBrowserTitle">
<source xml:lang="en">Locate Database Files - </source>
</trans-unit>
<trans-unit id="jobStepDialog.ok">
<source xml:lang="en">OK</source>
</trans-unit>
<trans-unit id="jobStepDialog.cancel">
<source xml:lang="en">Cancel</source>
</trans-unit>
<trans-unit id="jobStepDialog.general">
<source xml:lang="en">General</source>
</trans-unit>
<trans-unit id="jobStepDialog.advanced">
<source xml:lang="en">Advanced</source>
</trans-unit>
<trans-unit id="jobStepDialog.open">
<source xml:lang="en">Open...</source>
</trans-unit>
<trans-unit id="jobStepDialog.parse">
<source xml:lang="en">Parse</source>
</trans-unit>
<trans-unit id="jobStepDialog.next">
<source xml:lang="en">Next</source>
</trans-unit>
<trans-unit id="jobStepDialog.previous">
<source xml:lang="en">Previous</source>
</trans-unit>
<trans-unit id="jobStepDialog.successParse">
<source xml:lang="en">The command was successfully parsed.</source>
</trans-unit>
<trans-unit id="jobStepDialog.failParse">
<source xml:lang="en">The command failed.</source>
</trans-unit>
<trans-unit id="jobStepDialog.blankStepName">
<source xml:lang="en">The step name cannot be left blank</source>
</trans-unit>
<trans-unit id="jobStepDialog.stepNameLabel">
<source xml:lang="en">Step Name</source>
</trans-unit>
<trans-unit id="jobStepDialog.typeLabel">
<source xml:lang="en">Type</source>
</trans-unit>
<trans-unit id="jobStepDialog.runAsLabel">
<source xml:lang="en">Run as</source>
</trans-unit>
<trans-unit id="jobStepDialog.databaseLabel">
<source xml:lang="en">Database</source>
</trans-unit>
<trans-unit id="jobStepDialog.commandLabel">
<source xml:lang="en">Command</source>
</trans-unit>
<trans-unit id="jobStepDialog.successAction">
<source xml:lang="en">On success action</source>
</trans-unit>
<trans-unit id="jobStepDialog.failureAction">
<source xml:lang="en">On failure action</source>
</trans-unit>
<trans-unit id="jobStepDialog.runAsUser">
<source xml:lang="en">Run as user</source>
</trans-unit>
<trans-unit id="jobStepDialog.retryAttempts">
<source xml:lang="en">Retry Attempts</source>
</trans-unit>
<trans-unit id="jobStepDialog.retryInterval">
<source xml:lang="en">Retry Interval (minutes)</source>
</trans-unit>
<trans-unit id="jobStepDialog.logToTable">
<source xml:lang="en">Log to table</source>
</trans-unit>
<trans-unit id="jobStepDialog.appendExistingTableEntry">
<source xml:lang="en">Append output to exisiting entry in table</source>
</trans-unit>
<trans-unit id="jobStepDialog.includeStepOutputHistory">
<source xml:lang="en">Include step output in history</source>
</trans-unit>
<trans-unit id="jobStepDialog.outputFile">
<source xml:lang="en">Output File</source>
</trans-unit>
<trans-unit id="jobStepDialog.appendOutputToFile">
<source xml:lang="en">Append output to existing file</source>
</trans-unit>
<trans-unit id="jobStepDialog.selectedPath">
<source xml:lang="en">Selected path</source>
</trans-unit>
<trans-unit id="jobStepDialog.filesOfType">
<source xml:lang="en">Files of type</source>
</trans-unit>
<trans-unit id="jobStepDialog.fileName">
<source xml:lang="en">File name</source>
</trans-unit>
<trans-unit id="jobStepDialog.allFiles">
<source xml:lang="en">All Files (*)</source>
</trans-unit>
<trans-unit id="jobStepDialog.TSQL">
<source xml:lang="en">Transact-SQL script (T-SQL)</source>
</trans-unit>
<trans-unit id="jobStepDialog.agentServiceAccount">
<source xml:lang="en">SQL Server Agent Service Account</source>
</trans-unit>
<trans-unit id="jobStepDialog.nextStep">
<source xml:lang="en">Go to the next step</source>
</trans-unit>
<trans-unit id="jobStepDialog.quitJobSuccess">
<source xml:lang="en">Quit the job reporting success</source>
</trans-unit>
<trans-unit id="jobStepDialog.quitJobFailure">
<source xml:lang="en">Quit the job reporting failure</source>
</trans-unit>
</body></file>
<file original="extensions/agent/out/dialogs/jobDialog" source-language="en" datatype="plaintext"><body>
<trans-unit id="jobDialog.general">
<source xml:lang="en">General</source>
</trans-unit>
<trans-unit id="jobDialog.steps">
<source xml:lang="en">Steps</source>
</trans-unit>
<trans-unit id="jobDialog.schedules">
<source xml:lang="en">Schedules</source>
</trans-unit>
<trans-unit id="jobDialog.alerts">
<source xml:lang="en">Alerts</source>
</trans-unit>
<trans-unit id="jobDialog.notifications">
<source xml:lang="en">Notifications</source>
</trans-unit>
<trans-unit id="jobDialog.blankJobNameError">
<source xml:lang="en">The name of the job cannot be blank.</source>
</trans-unit>
<trans-unit id="jobDialog.name">
<source xml:lang="en">Name</source>
</trans-unit>
<trans-unit id="jobDialog.owner">
<source xml:lang="en">Owner</source>
</trans-unit>
<trans-unit id="jobDialog.category">
<source xml:lang="en">Category</source>
</trans-unit>
<trans-unit id="jobDialog.description">
<source xml:lang="en">Description</source>
</trans-unit>
<trans-unit id="jobDialog.enabled">
<source xml:lang="en">Enabled</source>
</trans-unit>
<trans-unit id="jobDialog.jobStepList">
<source xml:lang="en">Job step list</source>
</trans-unit>
<trans-unit id="jobDialog.step">
<source xml:lang="en">Step</source>
</trans-unit>
<trans-unit id="jobDialog.type">
<source xml:lang="en">Type</source>
</trans-unit>
<trans-unit id="jobDialog.onSuccess">
<source xml:lang="en">On Success</source>
</trans-unit>
<trans-unit id="jobDialog.onFailure">
<source xml:lang="en">On Failure</source>
</trans-unit>
<trans-unit id="jobDialog.new">
<source xml:lang="en">New...</source>
</trans-unit>
<trans-unit id="jobDialog.edit">
<source xml:lang="en">Edit</source>
</trans-unit>
<trans-unit id="jobDialog.delete">
<source xml:lang="en">Delete</source>
</trans-unit>
<trans-unit id="jobDialog.moveUp">
<source xml:lang="en">Move Step Up</source>
</trans-unit>
<trans-unit id="jobDialog.moveDown">
<source xml:lang="en">Move Step Up</source>
</trans-unit>
<trans-unit id="jobDialog.notificationsTabTop">
<source xml:lang="en">Actions to perform when the job completes</source>
</trans-unit>
<trans-unit id="jobDialog.email">
<source xml:lang="en">Email</source>
</trans-unit>
<trans-unit id="jobDialog.page">
<source xml:lang="en">Page</source>
</trans-unit>
<trans-unit id="jobDialog.eventLogCheckBoxLabel">
<source xml:lang="en">Write to the Windows Application event log</source>
</trans-unit>
<trans-unit id="jobDialog.deleteJobLabel">
<source xml:lang="en">Automatically delete job</source>
</trans-unit>
<trans-unit id="jobDialog.schedulesaLabel">
<source xml:lang="en">Schedules list</source>
</trans-unit>
<trans-unit id="jobDialog.pickSchedule">
<source xml:lang="en">Pick Schedule</source>
</trans-unit>
<trans-unit id="jobDialog.scheduleNameLabel">
<source xml:lang="en">Schedule Name</source>
</trans-unit>
<trans-unit id="jobDialog.alertsList">
<source xml:lang="en">Alerts list</source>
</trans-unit>
<trans-unit id="jobDialog.newAlert">
<source xml:lang="en">New Alert</source>
</trans-unit>
<trans-unit id="jobDialog.alertNameLabel">
<source xml:lang="en">Alert Name</source>
</trans-unit>
<trans-unit id="jobDialog.newJob">
<source xml:lang="en">New Job</source>
</trans-unit>
<trans-unit id="jobDialog.editJob">
<source xml:lang="en">Edit Job</source>
</trans-unit>
</body></file>
<file original="extensions/agent/out/dialogs/alertDialog" source-language="en" datatype="plaintext"><body>
<trans-unit id="alertDialog.createAlert">
<source xml:lang="en">Create Alert</source>
</trans-unit>
<trans-unit id="alertDialog.editAlert">
<source xml:lang="en">Edit Alert</source>
</trans-unit>
<trans-unit id="alertDialog.General">
<source xml:lang="en">General</source>
</trans-unit>
<trans-unit id="alertDialog.Response">
<source xml:lang="en">Response</source>
</trans-unit>
<trans-unit id="alertDialog.Options">
<source xml:lang="en">Options</source>
</trans-unit>
<trans-unit id="alertDialog.eventAlert">
<source xml:lang="en">Event alert definition</source>
</trans-unit>
<trans-unit id="alertDialog.Name">
<source xml:lang="en">Name</source>
</trans-unit>
<trans-unit id="alertDialog.Type">
<source xml:lang="en">Type</source>
</trans-unit>
<trans-unit id="alertDialog.Enabled">
<source xml:lang="en">Enabled</source>
</trans-unit>
<trans-unit id="alertDialog.DatabaseName">
<source xml:lang="en">Database name</source>
</trans-unit>
<trans-unit id="alertDialog.ErrorNumber">
<source xml:lang="en">Error number</source>
</trans-unit>
<trans-unit id="alertDialog.Severity">
<source xml:lang="en">Severity</source>
</trans-unit>
<trans-unit id="alertDialog.RaiseAlertContains">
<source xml:lang="en">Raise alert when message contains</source>
</trans-unit>
<trans-unit id="alertDialog.MessageText">
<source xml:lang="en">Message text</source>
</trans-unit>
<trans-unit id="alertDialog.Severity001">
<source xml:lang="en">001 - Miscellaneous System Information</source>
</trans-unit>
<trans-unit id="alertDialog.Severity002">
<source xml:lang="en">002 - Reserved</source>
</trans-unit>
<trans-unit id="alertDialog.Severity003">
<source xml:lang="en">003 - Reserved</source>
</trans-unit>
<trans-unit id="alertDialog.Severity004">
<source xml:lang="en">004 - Reserved</source>
</trans-unit>
<trans-unit id="alertDialog.Severity005">
<source xml:lang="en">005 - Reserved</source>
</trans-unit>
<trans-unit id="alertDialog.Severity006">
<source xml:lang="en">006 - Reserved</source>
</trans-unit>
<trans-unit id="alertDialog.Severity007">
<source xml:lang="en">007 - Notification: Status Information</source>
</trans-unit>
<trans-unit id="alertDialog.Severity008">
<source xml:lang="en">008 - Notification: User Intervention Required</source>
</trans-unit>
<trans-unit id="alertDialog.Severity009">
<source xml:lang="en">009 - User Defined</source>
</trans-unit>
<trans-unit id="alertDialog.Severity010">
<source xml:lang="en">010 - Information</source>
</trans-unit>
<trans-unit id="alertDialog.Severity011">
<source xml:lang="en">011 - Specified Database Object Not Found</source>
</trans-unit>
<trans-unit id="alertDialog.Severity012">
<source xml:lang="en">012 - Unused</source>
</trans-unit>
<trans-unit id="alertDialog.Severity013">
<source xml:lang="en">013 - User Transaction Syntax Error</source>
</trans-unit>
<trans-unit id="alertDialog.Severity014">
<source xml:lang="en">014 - Insufficient Permission</source>
</trans-unit>
<trans-unit id="alertDialog.Severity015">
<source xml:lang="en">015 - Syntax Error in SQL Statements</source>
</trans-unit>
<trans-unit id="alertDialog.Severity016">
<source xml:lang="en">016 - Miscellaneous User Error</source>
</trans-unit>
<trans-unit id="alertDialog.Severity017">
<source xml:lang="en">017 - Insufficient Resources</source>
</trans-unit>
<trans-unit id="alertDialog.Severity018">
<source xml:lang="en">018 - Nonfatal Internal Error</source>
</trans-unit>
<trans-unit id="alertDialog.Severity019">
<source xml:lang="en">019 - Fatal Error in Resource</source>
</trans-unit>
<trans-unit id="alertDialog.Severity020">
<source xml:lang="en">020 - Fatal Error in Current Process</source>
</trans-unit>
<trans-unit id="alertDialog.Severity021">
<source xml:lang="en">021 - Fatal Error in Database Processes</source>
</trans-unit>
<trans-unit id="alertDialog.Severity022">
<source xml:lang="en">022 - Fatal Error: Table Integrity Suspect</source>
</trans-unit>
<trans-unit id="alertDialog.Severity023">
<source xml:lang="en">023 - Fatal Error: Database Integrity Suspect</source>
</trans-unit>
<trans-unit id="alertDialog.Severity024">
<source xml:lang="en">024 - Fatal Error: Hardware Error</source>
</trans-unit>
<trans-unit id="alertDialog.Severity025">
<source xml:lang="en">025 - Fatal Error</source>
</trans-unit>
<trans-unit id="alertDialog.AllDatabases">
<source xml:lang="en">&lt;all databases&gt;</source>
</trans-unit>
<trans-unit id="alertDialog.ExecuteJob">
<source xml:lang="en">Execute Job</source>
</trans-unit>
<trans-unit id="alertDialog.ExecuteJobName">
<source xml:lang="en">Job Name</source>
</trans-unit>
<trans-unit id="alertDialog.NotifyOperators">
<source xml:lang="en">Notify Operators</source>
</trans-unit>
<trans-unit id="alertDialog.NewJob">
<source xml:lang="en">New Job</source>
</trans-unit>
<trans-unit id="alertDialog.OperatorList">
<source xml:lang="en">Operator List</source>
</trans-unit>
<trans-unit id="alertDialog.OperatorName">
<source xml:lang="en">Operator</source>
</trans-unit>
<trans-unit id="alertDialog.OperatorEmail">
<source xml:lang="en">E-mail</source>
</trans-unit>
<trans-unit id="alertDialog.OperatorPager">
<source xml:lang="en">Pager</source>
</trans-unit>
<trans-unit id="alertDialog.NewOperator">
<source xml:lang="en">New Operator</source>
</trans-unit>
<trans-unit id="alertDialog.IncludeErrorInEmail">
<source xml:lang="en">Include alert error text in e-mail</source>
</trans-unit>
<trans-unit id="alertDialog.IncludeErrorInPager">
<source xml:lang="en">Include alert error text in pager</source>
</trans-unit>
<trans-unit id="alertDialog.AdditionalNotification">
<source xml:lang="en">Additional notification message to send</source>
</trans-unit>
<trans-unit id="alertDialog.DelayBetweenResponse">
<source xml:lang="en">Delay between responses</source>
</trans-unit>
<trans-unit id="alertDialog.DelayMinutes">
<source xml:lang="en">Delay Minutes</source>
</trans-unit>
<trans-unit id="alertDialog.DelaySeconds">
<source xml:lang="en">Delay Seconds</source>
</trans-unit>
</body></file>
<file original="extensions/agent/out/dialogs/agentDialog" source-language="en" datatype="plaintext"><body>
<trans-unit id="agentDialog.OK">
<source xml:lang="en">OK</source>
</trans-unit>
<trans-unit id="agentDialog.Cancel">
<source xml:lang="en">Cancel</source>
</trans-unit>
</body></file>
<file original="extensions/agent/out/data/jobData" source-language="en" datatype="plaintext"><body>
<trans-unit id="jobData.whenJobCompletes">
<source xml:lang="en">When the job completes</source>
</trans-unit>
<trans-unit id="jobData.whenJobFails">
<source xml:lang="en">When the job fails</source>
</trans-unit>
<trans-unit id="jobData.whenJobSucceeds">
<source xml:lang="en">When the job succeeds</source>
</trans-unit>
<trans-unit id="jobData.jobNameRequired">
<source xml:lang="en">Job name must be provided</source>
</trans-unit>
<trans-unit id="jobData.saveErrorMessage">
<source xml:lang="en">Job update failed '{0}'</source>
</trans-unit>
</body></file>
<file original="extensions/agent/out/data/alertData" source-language="en" datatype="plaintext"><body>
<trans-unit id="alertData.saveErrorMessage">
<source xml:lang="en">Alert update failed '{0}'</source>
</trans-unit>
<trans-unit id="alertData.DefaultAlertTypString">
<source xml:lang="en">SQL Server event alert</source>
</trans-unit>
<trans-unit id="alertDialog.PerformanceCondition">
<source xml:lang="en">SQL Server performance condition alert</source>
</trans-unit>
<trans-unit id="alertDialog.WmiEvent">
<source xml:lang="en">WMI event alert</source>
</trans-unit>
</body></file>
<file original="extensions/agent/out/mainController" source-language="en" datatype="plaintext"><body>
<trans-unit id="mainController.notImplemented">
<source xml:lang="en">This feature is under development. Check-out the latest insiders build if you'd like to try out the most recent changes!</source>
</trans-unit>
</body></file>
</xliff>

View File

@@ -0,0 +1,140 @@
<?xml version="1.0" encoding="utf-8"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file original="extensions/import/out/wizard/pages/summaryPage" source-language="en" datatype="plaintext"><body>
<trans-unit id="flatFileImport.importInformation">
<source xml:lang="en">Import information</source>
</trans-unit>
<trans-unit id="flatFileImport.importStatus">
<source xml:lang="en">Import status</source>
</trans-unit>
<trans-unit id="flatFileImport.serverName">
<source xml:lang="en">Server name</source>
</trans-unit>
<trans-unit id="flatFileImport.databaseName">
<source xml:lang="en">Database name</source>
</trans-unit>
<trans-unit id="flatFileImport.tableName">
<source xml:lang="en">Table name</source>
</trans-unit>
<trans-unit id="flatFileImport.tableSchema">
<source xml:lang="en">Table schema</source>
</trans-unit>
<trans-unit id="flatFileImport.fileImport">
<source xml:lang="en">File to be imported</source>
</trans-unit>
<trans-unit id="flatFileImport.success.norows">
<source xml:lang="en">✔ You have successfully inserted the data into a table.</source>
</trans-unit>
</body></file>
<file original="extensions/import/out/wizard/pages/prosePreviewPage" source-language="en" datatype="plaintext"><body>
<trans-unit id="flatFileImport.refresh">
<source xml:lang="en">Refresh</source>
</trans-unit>
<trans-unit id="flatFileImport.prosePreviewMessage">
<source xml:lang="en">This operation analyzed the input file structure to generate the preview below for up to the first 50 rows.</source>
</trans-unit>
</body></file>
<file original="extensions/import/out/wizard/pages/modifyColumnsPage" source-language="en" datatype="plaintext"><body>
<trans-unit id="flatFileImport.importData">
<source xml:lang="en">Import Data</source>
</trans-unit>
<trans-unit id="flatFileImport.next">
<source xml:lang="en">Next</source>
</trans-unit>
<trans-unit id="flatFileImport.columnName">
<source xml:lang="en">Column Name</source>
</trans-unit>
<trans-unit id="flatFileImport.dataType">
<source xml:lang="en">Data Type</source>
</trans-unit>
<trans-unit id="flatFileImport.primaryKey">
<source xml:lang="en">Primary Key</source>
</trans-unit>
<trans-unit id="flatFileImport.allowNulls">
<source xml:lang="en">Allow Nulls</source>
</trans-unit>
</body></file>
<file original="extensions/import/out/wizard/pages/fileConfigPage" source-language="en" datatype="plaintext"><body>
<trans-unit id="flatFileImport.serverDropdownTitle">
<source xml:lang="en">Server the database is in</source>
</trans-unit>
<trans-unit id="flatFileImport.databaseDropdownTitle">
<source xml:lang="en">Database the table is created in</source>
</trans-unit>
<trans-unit id="flatFileImport.browseFiles">
<source xml:lang="en">Browse</source>
</trans-unit>
<trans-unit id="flatFileImport.openFile">
<source xml:lang="en">Open</source>
</trans-unit>
<trans-unit id="flatFileImport.fileTextboxTitle">
<source xml:lang="en">Location of the file to be imported</source>
</trans-unit>
<trans-unit id="flatFileImport.tableTextboxTitle">
<source xml:lang="en">New table name</source>
</trans-unit>
<trans-unit id="flatFileImport.schemaTextboxTitle">
<source xml:lang="en">Table schema</source>
</trans-unit>
</body></file>
<file original="extensions/import/out/services/telemetry" source-language="en" datatype="plaintext"><body>
<trans-unit id="import.serviceCrashButton">
<source xml:lang="en">Give Feedback</source>
</trans-unit>
<trans-unit id="serviceCrashMessage">
<source xml:lang="en">service component could not start</source>
</trans-unit>
</body></file>
<file original="extensions/import/out/services/serviceClient" source-language="en" datatype="plaintext"><body>
<trans-unit id="serviceStarted">
<source xml:lang="en">Service Started</source>
</trans-unit>
<trans-unit id="serviceStarting">
<source xml:lang="en">Starting service</source>
</trans-unit>
<trans-unit id="flatFileImport.serviceStartFailed">
<source xml:lang="en">Failed to start Import service{0}</source>
</trans-unit>
<trans-unit id="installingServiceDetailed">
<source xml:lang="en">Installing {0} service to {1}</source>
</trans-unit>
<trans-unit id="installingService">
<source xml:lang="en">Installing Service</source>
</trans-unit>
<trans-unit id="serviceInstalled">
<source xml:lang="en">Installed</source>
</trans-unit>
<trans-unit id="downloadingService">
<source xml:lang="en">Downloading {0}</source>
</trans-unit>
<trans-unit id="downloadingServiceStatus">
<source xml:lang="en">Downloading Service</source>
</trans-unit>
<trans-unit id="downloadingServiceComplete">
<source xml:lang="en">Done!</source>
</trans-unit>
</body></file>
<file original="extensions/import/out/wizard/flatFileWizard" source-language="en" datatype="plaintext"><body>
<trans-unit id="import.needConnection">
<source xml:lang="en">Please connect to a server before using this wizard.</source>
</trans-unit>
<trans-unit id="flatFileImport.wizardName">
<source xml:lang="en">Import flat file wizard</source>
</trans-unit>
<trans-unit id="flatFileImport.page1Name">
<source xml:lang="en">Specify Input File</source>
</trans-unit>
<trans-unit id="flatFileImport.page2Name">
<source xml:lang="en">Preview Data</source>
</trans-unit>
<trans-unit id="flatFileImport.page3Name">
<source xml:lang="en">Modify Columns</source>
</trans-unit>
<trans-unit id="flatFileImport.page4Name">
<source xml:lang="en">Summary</source>
</trans-unit>
<trans-unit id="flatFileImport.importNewFile">
<source xml:lang="en">Import new file</source>
</trans-unit>
</body></file>
</xliff>

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file original="extensions/profiler/client\out/dialogs/profilerCreateSessionDialog" source-language="en" datatype="plaintext"><body>
<trans-unit id="createSessionDialog.newSession">
<source xml:lang="en">New Session</source>
</trans-unit>
<trans-unit id="createSessionDialog.cancel">
<source xml:lang="en">Cancel</source>
</trans-unit>
<trans-unit id="createSessionDialog.create">
<source xml:lang="en">Create</source>
</trans-unit>
<trans-unit id="createSessionDialog.title">
<source xml:lang="en">Create New Profiler Session</source>
</trans-unit>
<trans-unit id="createSessionDialog.templatesInvalid">
<source xml:lang="en">Invalid templates list, cannot open dialog</source>
</trans-unit>
<trans-unit id="createSessionDialog.dialogOwnerInvalid">
<source xml:lang="en">Invalid dialog owner, cannot open dialog</source>
</trans-unit>
<trans-unit id="createSessionDialog.selectTemplates">
<source xml:lang="en">Select session template:</source>
</trans-unit>
<trans-unit id="createSessionDialog.enterSessionName">
<source xml:lang="en">Enter session name:</source>
</trans-unit>
</body></file>
</xliff>

File diff suppressed because it is too large Load Diff

View File

@@ -172,8 +172,7 @@
"dependencies": {
"fs-extra": "^5.0.0",
"handlebars": "^4.0.11",
"vscode-nls": "2.0.2",
"sqlops": "github:anthonydresser/sqlops-extension-sqlops"
"vscode-nls": "2.0.2"
},
"devDependencies": {
"@types/handlebars": "^4.0.11",
@@ -192,6 +191,7 @@
"typemoq": "^2.1.0",
"typescript": "^2.6.1",
"vscode": "^1.1.6",
"sqlops": "github:anthonydresser/sqlops-extension-sqlops",
"vsce": "1.36.2"
}
}

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:
* 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?
* Created left nav bar and added 2 categories for insight widgets: monitor and performance

View File

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

View File

@@ -21,6 +21,10 @@
"command": "sqlservices.openDialog",
"title": "sqlservices.openDialog"
},
{
"command": "sqlservices.openConnectionDialog",
"title": "sqlservices.openConnectionDialog"
},
{
"command": "sqlservices.openEditor",
"title": "sqlservices.openEditor"
@@ -100,7 +104,7 @@
"sqlops": "github:anthonydresser/sqlops-extension-sqlops",
"tslint": "^3.14.0",
"typescript": "^2.6.1",
"vscode": "^1.1.14",
"vscode": "^1.1.18",
"@types/handlebars": "^4.0.11",
"vsce": "1.36.2"
},

View File

@@ -11,6 +11,7 @@ import * as vscode from 'vscode';
import SplitPropertiesPanel from './splitPropertiesPanel';
import * as fs from 'fs';
import * as path from 'path';
import {TreeNode, TreeDataProvider} from './treeDataProvider';
/**
* The main controller class that initializes the extension
@@ -48,6 +49,13 @@ export default class MainController implements vscode.Disposable {
this.openDialog();
});
vscode.commands.registerCommand('sqlservices.openConnectionDialog', async () => {
let connection = await sqlops.connection.openConnectionDialog();
if (connection) {
console.info('Connection Opened: ' + connection.options['server']);
}
});
vscode.commands.registerCommand('sqlservices.openEditor', () => {
this.openEditor();
});
@@ -67,12 +75,83 @@ export default class MainController implements vscode.Disposable {
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> {
let inputBox = view.modelBuilder.inputBox()
.withProperties({
multiline: true,
height: 100
}).component();
.withProperties({
multiline: true,
height: 100
}).component();
let inputBoxWrapper = view.modelBuilder.loadingComponent().withItem(inputBox).component();
inputBoxWrapper.loading = false;
customButton1.onClick(() => {
@@ -88,7 +167,7 @@ export default class MainController implements vscode.Disposable {
})
.component();
checkbox.onChanged(e => {
console.info("inputBox.enabled " + inputBox.enabled);
console.info('inputBox.enabled ' + inputBox.enabled);
inputBox.enabled = !inputBox.enabled;
});
let button = view.modelBuilder.button()
@@ -145,8 +224,8 @@ export default class MainController implements vscode.Disposable {
component: inputBox4,
title: 'inputBox4'
}], {
horizontal: true
}).component();
horizontal: true
}).component();
let groupModel1 = view.modelBuilder.groupContainer()
.withLayout({
}).withItems([
@@ -178,8 +257,8 @@ export default class MainController implements vscode.Disposable {
}).component();
let declarativeTable = view.modelBuilder.declarativeTable()
.withProperties({
columns: [{
.withProperties({
columns: [{
displayName: 'Column 1',
valueType: sqlops.DeclarativeDataType.string,
width: '20px',
@@ -204,12 +283,12 @@ export default class MainController implements vscode.Disposable {
{ name: 'options2', displayName: 'option 2' }
]
}
],
data: [
['Data00', 'Data01', false, 'options2'],
['Data10', 'Data11', true, 'options1']
]
}).component();
],
data: [
['Data00', 'Data01', false, 'options2'],
['Data10', 'Data11', true, 'options1']
]
}).component();
declarativeTable.onDataChanged(e => {
inputBox2.value = e.row.toString() + ' ' + e.column.toString() + ' ' + e.value.toString();
@@ -223,7 +302,7 @@ export default class MainController implements vscode.Disposable {
height: 150
}).withItems([
radioButton, groupModel1, radioButton2]
, { flex: '1 1 50%' }).component();
, { flex: '1 1 50%' }).component();
let formModel = view.modelBuilder.formContainer()
.withFormItems([{
component: inputBoxWrapper,
@@ -254,9 +333,9 @@ export default class MainController implements vscode.Disposable {
component: listBox,
title: 'List Box'
}], {
horizontal: false,
componentWidth: componentWidth
}).component();
horizontal: false,
componentWidth: componentWidth
}).component();
let formWrapper = view.modelBuilder.loadingComponent().withItem(formModel).component();
formWrapper.loading = false;
customButton2.onClick(() => {
@@ -271,8 +350,9 @@ export default class MainController implements vscode.Disposable {
let tab1 = sqlops.window.modelviewdialog.createTab('Test tab 1');
let tab2 = sqlops.window.modelviewdialog.createTab('Test tab 2');
let tab3 = sqlops.window.modelviewdialog.createTab('Test tab 3');
tab2.content = 'sqlservices';
dialog.content = [tab1, tab2];
dialog.content = [tab1, tab2, tab3];
dialog.okButton.onClick(() => console.log('ok clicked!'));
dialog.cancelButton.onClick(() => console.log('cancel clicked!'));
dialog.okButton.label = 'ok';
@@ -285,6 +365,10 @@ export default class MainController implements vscode.Disposable {
tab1.registerContent(async (view) => {
await this.getTabContent(view, customButton1, customButton2, 400);
});
tab3.registerContent(async (view) => {
await this.getTab3Content(view);
});
sqlops.window.modelviewdialog.openDialog(dialog);
}
@@ -301,6 +385,20 @@ export default class MainController implements vscode.Disposable {
page1.registerContent(async (view) => {
await this.getTabContent(view, customButton1, customButton2, 800);
});
wizard.registerOperation({
displayName: 'test task',
description: 'task description',
isCancelable: true,
connection: undefined,
operation: 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.open();
}
@@ -343,15 +441,32 @@ export default class MainController implements vscode.Disposable {
webview2.message = count;
});
let flexModel = view.modelBuilder.flexContainer()
.withLayout({
flexFlow: 'column',
alignItems: 'flex-start',
height: 500
}).withItems([
webview1, webview2
], { flex: '1 1 50%' })
let editor1 = view.modelBuilder.editor()
.withProperties({
content: 'select * from sys.tables'
})
.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);
});
editor.openEditor();
@@ -378,13 +493,14 @@ export default class MainController implements vscode.Disposable {
let monitorLightPath = vscode.Uri.file(path.join(__dirname, '..', 'media', 'monitor.svg'));
let monitorIcon = {
light: monitorLightPath,
dark: path.join(__dirname, '..', 'media', 'monitor_inverse.svg') };
dark: path.join(__dirname, '..', 'media', 'monitor_inverse.svg')
};
let monitorButton = view.modelBuilder.button()
.withProperties({
label: 'Monitor',
iconPath: monitorIcon
}).component();
.withProperties({
label: 'Monitor',
iconPath: monitorIcon
}).component();
let toolbarModel = view.modelBuilder.toolbarContainer()
.withToolbarItems([{
component: inputBox,

View File

@@ -0,0 +1,303 @@
/*---------------------------------------------------------------------------------------------
* 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 collapsibleState(): vscode.TreeItemCollapsibleState {
if (!this._isAlwaysLeaf) {
return vscode.TreeItemCollapsibleState.Expanded;
} else {
vscode.TreeItemCollapsibleState.None;
}
}
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.collapsibleState = element.collapsibleState;
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

@@ -20,7 +20,7 @@ export class Checkbox implements OnInit, OnChanges {
@Input() label: string;
@Input() enabled = true;
@Input() checked = true;
@Input() private ariaLabel: string;
@Input('aria-label') private ariaLabel: string;
@Output() onChange = new EventEmitter<boolean>();

View File

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

@@ -15,7 +15,6 @@ import { AngularDisposable } from 'sql/base/common/lifecycle';
import { attachInputBoxStyler } from 'vs/platform/theme/common/styler';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
@Component({
@@ -29,7 +28,7 @@ export class InputBox extends AngularDisposable implements OnInit, OnChanges {
@Input() max: string;
@Input() type: string;
@Input() placeholder: string;
@Input() ariaLabel: string;
@Input('aria-label') ariaLabel: string;
@Input() value: string;
@Output() onDidChange = new EventEmitter<string | number>();

View File

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

View File

@@ -135,7 +135,7 @@ export abstract class Modal extends Disposable implements IThemable {
private _name: string,
private _partService: IPartService,
private _telemetryService: ITelemetryService,
private _contextKeyService: IContextKeyService,
_contextKeyService: IContextKeyService,
options?: IModalOptions
) {
super();
@@ -228,7 +228,7 @@ export abstract class Modal extends Disposable implements IThemable {
}
// The builder builds the dialog. It append header, body and footer sections.
this._builder = $().div({ class: builderClass, 'role': 'dialog' }, (dialogContainer) => {
this._builder = $().div({ class: builderClass, 'role': 'dialog', 'aria-label': this._title }, (dialogContainer) => {
this._modalDialog = dialogContainer.div({ class: 'modal-dialog ', role: 'document' }, (modalDialog) => {
modalDialog.div({ class: 'modal-content' }, (modelContent) => {
parts.forEach((part) => {
@@ -357,6 +357,21 @@ export abstract class Modal extends Disposable implements IThemable {
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
* @param err Text to show in the error message

View File

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

View File

@@ -49,7 +49,7 @@ export function createOptionElement(option: sqlops.ServiceOption, rowContainer:
optionWidget.value = optionValue;
inputElement = findElement(rowContainer, 'input');
} else if (option.valueType === ServiceOptionType.category || option.valueType === ServiceOptionType.boolean) {
optionWidget = new SelectBox(possibleInputs, optionValue.toString(), contextViewService);
optionWidget = new SelectBox(possibleInputs, optionValue.toString(), contextViewService, undefined, { ariaLabel: option.displayName });
DialogHelper.appendInputSelectBox(rowContainer, optionWidget);
inputElement = findElement(rowContainer, 'monaco-select-box');
} else if (option.valueType === ServiceOptionType.string || option.valueType === ServiceOptionType.password) {

View File

@@ -4,11 +4,9 @@
*--------------------------------------------------------------------------------------------*/
import { IThemable } from 'vs/platform/theme/common/styler';
import * as objects from 'sql/base/common/objects';
import { Event, Emitter } from 'vs/base/common/event';
import { Dimension } from 'vs/base/browser/dom';
import { Dimension, EventType } from 'vs/base/browser/dom';
import { $, Builder } from 'vs/base/browser/builder';
import { EventType } from 'vs/base/browser/dom';
import { IAction } from 'vs/base/common/actions';
import { IActionOptions, ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
@@ -17,12 +15,16 @@ import './panelStyles';
import { Disposable } from 'vs/base/common/lifecycle';
export interface IPanelStyles {
}
export interface IPanelOptions {
showHeaderWhenSingleView?: boolean;
}
export interface IPanelView {
render(container: HTMLElement): void;
layout(dimension: Dimension): void;
remove?(): void;
}
export interface IPanelTab {
@@ -36,6 +38,10 @@ interface IInternalPanelTab extends IPanelTab {
label: Builder;
}
const defaultOptions: IPanelOptions = {
showHeaderWhenSingleView: true
};
export type PanelTabIdentifier = string;
export class TabbedPanel extends Disposable implements IThemable {
@@ -49,11 +55,12 @@ export class TabbedPanel extends Disposable implements IThemable {
private _actionbar: ActionBar;
private _currentDimensions: Dimension;
private _collapsed = false;
private _headerVisible: boolean;
private _onTabChange = new Emitter<PanelTabIdentifier>();
public onTabChange: Event<PanelTabIdentifier> = this._onTabChange.event;
constructor(private container: HTMLElement) {
constructor(private container: HTMLElement, private options: IPanelOptions = defaultOptions) {
super();
this.$parent = this._register($('.tabbedPanel'));
this.$parent.appendTo(container);
@@ -65,7 +72,12 @@ export class TabbedPanel extends Disposable implements IThemable {
let actionbarcontainer = $('.title-actions');
this._actionbar = new ActionBar(actionbarcontainer.getHTMLElement());
this.$header.append(actionbarcontainer);
this.$parent.append(this.$header);
if (options.showHeaderWhenSingleView) {
this._headerVisible = true;
this.$parent.append(this.$header);
} else {
this._headerVisible = false;
}
this.$body = $('tabBody');
this.$body.attr('role', 'tabpanel');
this.$body.attr('tabindex', '0');
@@ -73,12 +85,16 @@ export class TabbedPanel extends Disposable implements IThemable {
}
public pushTab(tab: IPanelTab): PanelTabIdentifier {
let internalTab = objects.clone(tab) as IInternalPanelTab;
let internalTab = tab as IInternalPanelTab;
this._tabMap.set(tab.identifier, internalTab);
this._createTab(internalTab);
if (!this._shownTab) {
this.showTab(tab.identifier);
}
if (this._tabMap.size > 1 && !this._headerVisible) {
this.$parent.append(this.$header, 0);
this._headerVisible = true;
}
return tab.identifier as PanelTabIdentifier;
}
@@ -139,6 +155,11 @@ export class TabbedPanel extends Disposable implements IThemable {
}
public removeTab(tab: PanelTabIdentifier) {
let actualTab = this._tabMap.get(tab);
actualTab.header.destroy();
if (actualTab.view.remove) {
actualTab.view.remove();
}
this._tabMap.get(tab).header.destroy();
this._tabMap.delete(tab);
}
@@ -151,8 +172,9 @@ export class TabbedPanel extends Disposable implements IThemable {
this._currentDimensions = dimension;
this.$header.style('width', dimension.width + 'px');
this.$body.style('width', dimension.width + 'px');
this.$body.style('height', (dimension.height - this.headersize) + 'px');
this._layoutCurrentTab(new Dimension(dimension.width, dimension.height - this.headersize));
const bodyHeight = dimension.height - (this._headerVisible ? this.headersize : 0);
this.$body.style('height', bodyHeight + 'px');
this._layoutCurrentTab(new Dimension(dimension.width, bodyHeight));
}
private _layoutCurrentTab(dimension: Dimension): void {

View File

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

View File

@@ -0,0 +1,212 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { INextIterator } from 'vs/base/common/iterator';
export interface IView {
id: string;
}
export interface IViewItem {
view: IView;
top: number;
height: number;
width: number;
}
export class HeightMap {
private heightMap: IViewItem[];
private indexes: { [item: string]: number; };
constructor() {
this.heightMap = [];
this.indexes = {};
}
public getContentHeight(): number {
let last = this.heightMap[this.heightMap.length - 1];
return !last ? 0 : last.top + last.height;
}
public onInsertItems(iterator: INextIterator<IViewItem>, afterItemId: string = null): number {
let viewItem: IViewItem;
let i: number, j: number;
let totalSize: number;
let sizeDiff = 0;
if (afterItemId === null) {
i = 0;
totalSize = 0;
} else {
i = this.indexes[afterItemId] + 1;
viewItem = this.heightMap[i - 1];
if (!viewItem) {
console.error('view item doesnt exist');
return undefined;
}
totalSize = viewItem.top + viewItem.height;
}
let boundSplice = this.heightMap.splice.bind(this.heightMap, i, 0);
let itemsToInsert: IViewItem[] = [];
while (viewItem = iterator.next()) {
viewItem.top = totalSize + sizeDiff;
this.indexes[viewItem.view.id] = i++;
itemsToInsert.push(viewItem);
sizeDiff += viewItem.height;
}
boundSplice.apply(this.heightMap, itemsToInsert);
for (j = i; j < this.heightMap.length; j++) {
viewItem = this.heightMap[j];
viewItem.top += sizeDiff;
this.indexes[viewItem.view.id] = j;
}
for (j = itemsToInsert.length - 1; j >= 0; j--) {
this.onInsertItem(itemsToInsert[j]);
}
for (j = this.heightMap.length - 1; j >= i; j--) {
this.onRefreshItem(this.heightMap[j]);
}
return sizeDiff;
}
public onInsertItem(item: IViewItem): void {
// noop
}
// Contiguous items
public onRemoveItems(iterator: INextIterator<string>): void {
let itemId: string;
let viewItem: IViewItem;
let startIndex: number = null;
let i: number;
let sizeDiff = 0;
while (itemId = iterator.next()) {
i = this.indexes[itemId];
viewItem = this.heightMap[i];
if (!viewItem) {
console.error('view item doesnt exist');
return;
}
sizeDiff -= viewItem.height;
delete this.indexes[itemId];
this.onRemoveItem(viewItem);
if (startIndex === null) {
startIndex = i;
}
}
if (sizeDiff === 0) {
return;
}
this.heightMap.splice(startIndex, i - startIndex + 1);
for (i = startIndex; i < this.heightMap.length; i++) {
viewItem = this.heightMap[i];
viewItem.top += sizeDiff;
this.indexes[viewItem.view.id] = i;
this.onRefreshItem(viewItem);
}
}
public onRemoveItem(item: IViewItem): void {
// noop
}
public onRefreshItem(item: IViewItem, needsRender: boolean = false): void {
// noop
}
protected updateSize(item: string, size: number): void {
let i = this.indexes[item];
let viewItem = this.heightMap[i];
viewItem.height = size;
}
protected updateTop(item: string, top: number): void {
let i = this.indexes[item];
let viewItem = this.heightMap[i];
viewItem.top = top;
}
public itemsCount(): number {
return this.heightMap.length;
}
public itemAt(position: number): string {
return this.heightMap[this.indexAt(position)].view.id;
}
public withItemsInRange(start: number, end: number, fn: (item: string) => void): void {
start = this.indexAt(start);
end = this.indexAt(end);
for (let i = start; i <= end; i++) {
fn(this.heightMap[i].view.id);
}
}
public indexAt(position: number): number {
let left = 0;
let right = this.heightMap.length;
let center: number;
let item: IViewItem;
// Binary search
while (left < right) {
center = Math.floor((left + right) / 2);
item = this.heightMap[center];
if (position < item.top) {
right = center;
} else if (position >= item.top + item.height) {
if (left === center) {
break;
}
left = center;
} else {
return center;
}
}
return this.heightMap.length;
}
public indexAfter(position: number): number {
return Math.min(this.indexAt(position) + 1, this.heightMap.length);
}
public itemAtIndex(index: number): IViewItem {
return this.heightMap[index];
}
public itemAfter(item: IViewItem): IViewItem {
return this.heightMap[this.indexes[item.view.id] + 1] || null;
}
public dispose(): void {
this.heightMap = null;
this.indexes = null;
}
}

View File

@@ -0,0 +1,8 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-scroll-split-view {
position: relative;
}

View File

@@ -0,0 +1,622 @@
/*---------------------------------------------------------------------------------------------
* 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 'vs/css!./scrollableSplitview';
import { IDisposable, combinedDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { mapEvent, Emitter, Event, debounceEvent } from 'vs/base/common/event';
import * as types from 'vs/base/common/types';
import * as dom from 'vs/base/browser/dom';
import { clamp } from 'vs/base/common/numbers';
import { range, firstIndex } from 'vs/base/common/arrays';
import { Sash, Orientation, ISashEvent as IBaseSashEvent } from 'vs/base/browser/ui/sash/sash';
import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
import { HeightMap, IView as HeightIView, IViewItem as HeightIViewItem } from './heightMap';
import { ArrayIterator } from 'vs/base/common/iterator';
import { mixin } from 'vs/base/common/objects';
export { Orientation } from 'vs/base/browser/ui/sash/sash';
export interface ISplitViewOptions {
orientation?: Orientation; // default Orientation.VERTICAL
enableResizing?: boolean;
}
const defaultOptions: ISplitViewOptions = {
enableResizing: true
};
export interface IView extends HeightIView {
readonly minimumSize: number;
readonly maximumSize: number;
readonly onDidChange: Event<number | undefined>;
render(container: HTMLElement, orientation: Orientation): void;
layout(size: number, orientation: Orientation): void;
}
interface ISashEvent {
sash: Sash;
start: number;
current: number;
}
interface IViewItem extends HeightIViewItem {
view: IView;
size: number;
container: HTMLElement;
disposable: IDisposable;
layout(): void;
}
interface ISashItem {
sash: Sash;
disposable: IDisposable;
}
interface ISashDragState {
index: number;
start: number;
sizes: number[];
}
enum State {
Idle,
Busy
}
function pushToEnd<T>(arr: T[], value: T): T[] {
let didFindValue = false;
const result = arr.filter(v => {
if (v === value) {
didFindValue = true;
return false;
}
return true;
});
if (didFindValue) {
result.push(value);
}
return result;
}
export class ScrollableSplitView extends HeightMap implements IDisposable {
private orientation: Orientation;
private el: HTMLElement;
private size = 0;
private contentSize = 0;
private viewItems: IViewItem[] = [];
private sashItems: ISashItem[] = [];
private sashDragState: ISashDragState;
private state: State = State.Idle;
private scrollable: ScrollableElement;
private options: ISplitViewOptions;
private dirtyState = false;
private lastRenderTop: number;
private lastRenderHeight: number;
private _onDidSashChange = new Emitter<void>();
readonly onDidSashChange = this._onDidSashChange.event;
private _onDidSashReset = new Emitter<void>();
readonly onDidSashReset = this._onDidSashReset.event;
get length(): number {
return this.viewItems.length;
}
constructor(container: HTMLElement, options: ISplitViewOptions = {}) {
super();
this.orientation = types.isUndefined(options.orientation) ? Orientation.VERTICAL : options.orientation;
this.options = mixin(options, defaultOptions, false);
this.el = document.createElement('div');
this.scrollable = new ScrollableElement(this.el, {});
debounceEvent(this.scrollable.onScroll, (l, e) => e, 25)(e => {
this.render(e.scrollTop, e.height);
this.relayout();
});
let domNode = this.scrollable.getDomNode();
dom.addClass(this.el, 'monaco-scroll-split-view');
dom.addClass(domNode, 'monaco-split-view2');
dom.addClass(domNode, this.orientation === Orientation.VERTICAL ? 'vertical' : 'horizontal');
container.appendChild(domNode);
}
addViews(views: IView[], sizes: number[], index = this.viewItems.length): void {
if (this.state !== State.Idle) {
throw new Error('Cant modify splitview');
}
this.state = State.Busy;
for (let i = 0; i < views.length; i++) {
let view = views[i], size = sizes[i];
// Add view
const container = dom.$('.split-view-view');
const onChangeDisposable = view.onDidChange(size => this.onViewChange(item, size));
const containerDisposable = toDisposable(() => {
if (container.parentElement) {
this.el.removeChild(container);
}
this.onRemoveItems(new ArrayIterator([item.view.id]));
});
const disposable = combinedDisposable([onChangeDisposable, containerDisposable]);
const layoutContainer = this.orientation === Orientation.VERTICAL
? size => item.container.style.height = `${item.size}px`
: size => item.container.style.width = `${item.size}px`;
const layout = () => {
layoutContainer(item.size);
item.view.layout(item.size, this.orientation);
};
size = Math.round(size);
const item: IViewItem = { view, container, size, layout, disposable, height: size, top: 0, width: 0 };
this.viewItems.splice(index, 0, item);
this.onInsertItems(new ArrayIterator([item]), index > 0 ? this.viewItems[index - 1].view.id : undefined);
// Add sash
if (this.options.enableResizing && this.viewItems.length > 1) {
const orientation = this.orientation === Orientation.VERTICAL ? Orientation.HORIZONTAL : Orientation.VERTICAL;
const layoutProvider = this.orientation === Orientation.VERTICAL ? { getHorizontalSashTop: sash => this.getSashPosition(sash) } : { getVerticalSashLeft: sash => this.getSashPosition(sash) };
const sash = new Sash(this.el, layoutProvider, { orientation });
const sashEventMapper = this.orientation === Orientation.VERTICAL
? (e: IBaseSashEvent) => ({ sash, start: e.startY, current: e.currentY })
: (e: IBaseSashEvent) => ({ sash, start: e.startX, current: e.currentX });
const onStart = mapEvent(sash.onDidStart, sashEventMapper);
const onStartDisposable = onStart(this.onSashStart, this);
const onChange = mapEvent(sash.onDidChange, sashEventMapper);
const onSashChangeDisposable = onChange(this.onSashChange, this);
const onEnd = mapEvent<void, void>(sash.onDidEnd, () => null);
const onEndDisposable = onEnd(() => this._onDidSashChange.fire());
const onDidReset = mapEvent<void, void>(sash.onDidReset, () => null);
const onDidResetDisposable = onDidReset(() => this._onDidSashReset.fire());
const disposable = combinedDisposable([onStartDisposable, onSashChangeDisposable, onEndDisposable, onDidResetDisposable, sash]);
const sashItem: ISashItem = { sash, disposable };
this.sashItems.splice(index - 1, 0, sashItem);
}
view.render(container, this.orientation);
}
this.relayout(index);
this.state = State.Idle;
}
addView(view: IView, size: number, index = this.viewItems.length): void {
if (this.state !== State.Idle) {
throw new Error('Cant modify splitview');
}
this.state = State.Busy;
// Add view
const container = dom.$('.split-view-view');
const onChangeDisposable = view.onDidChange(size => this.onViewChange(item, size));
const containerDisposable = toDisposable(() => {
if (container.parentElement) {
this.el.removeChild(container);
}
this.onRemoveItems(new ArrayIterator([item.view.id]));
});
const disposable = combinedDisposable([onChangeDisposable, containerDisposable]);
const layoutContainer = this.orientation === Orientation.VERTICAL
? size => item.container.style.height = `${item.size}px`
: size => item.container.style.width = `${item.size}px`;
const layout = () => {
layoutContainer(item.size);
item.view.layout(item.size, this.orientation);
};
size = Math.round(size);
const item: IViewItem = { view, container, size, layout, disposable, height: size, top: 0, width: 0 };
this.viewItems.splice(index, 0, item);
this.onInsertItems(new ArrayIterator([item]), index > 0 ? this.viewItems[index - 1].view.id : undefined);
// Add sash
if (this.options.enableResizing && this.viewItems.length > 1) {
const orientation = this.orientation === Orientation.VERTICAL ? Orientation.HORIZONTAL : Orientation.VERTICAL;
const layoutProvider = this.orientation === Orientation.VERTICAL ? { getHorizontalSashTop: sash => this.getSashPosition(sash) } : { getVerticalSashLeft: sash => this.getSashPosition(sash) };
const sash = new Sash(this.el, layoutProvider, { orientation });
const sashEventMapper = this.orientation === Orientation.VERTICAL
? (e: IBaseSashEvent) => ({ sash, start: e.startY, current: e.currentY })
: (e: IBaseSashEvent) => ({ sash, start: e.startX, current: e.currentX });
const onStart = mapEvent(sash.onDidStart, sashEventMapper);
const onStartDisposable = onStart(this.onSashStart, this);
const onChange = mapEvent(sash.onDidChange, sashEventMapper);
const onSashChangeDisposable = onChange(this.onSashChange, this);
const onEnd = mapEvent<void, void>(sash.onDidEnd, () => null);
const onEndDisposable = onEnd(() => this._onDidSashChange.fire());
const onDidReset = mapEvent<void, void>(sash.onDidReset, () => null);
const onDidResetDisposable = onDidReset(() => this._onDidSashReset.fire());
const disposable = combinedDisposable([onStartDisposable, onSashChangeDisposable, onEndDisposable, onDidResetDisposable, sash]);
const sashItem: ISashItem = { sash, disposable };
sash.hide();
this.sashItems.splice(index - 1, 0, sashItem);
}
view.render(container, this.orientation);
this.relayout(index);
this.state = State.Idle;
}
removeView(index: number): void {
if (this.state !== State.Idle) {
throw new Error('Cant modify splitview');
}
this.state = State.Busy;
if (index < 0 || index >= this.viewItems.length) {
return;
}
// Remove view
const viewItem = this.viewItems.splice(index, 1)[0];
viewItem.disposable.dispose();
// Remove sash
if (this.options.enableResizing && this.viewItems.length >= 1) {
const sashIndex = Math.max(index - 1, 0);
const sashItem = this.sashItems.splice(sashIndex, 1)[0];
sashItem.disposable.dispose();
} else {
this.lastRenderHeight = NaN, this.lastRenderTop = NaN;
}
this.relayout();
this.state = State.Idle;
}
moveView(from: number, to: number): void {
if (this.state !== State.Idle) {
throw new Error('Cant modify splitview');
}
this.state = State.Busy;
if (from < 0 || from >= this.viewItems.length) {
return;
}
if (to < 0 || to >= this.viewItems.length) {
return;
}
if (from === to) {
return;
}
const viewItem = this.viewItems.splice(from, 1)[0];
this.viewItems.splice(to, 0, viewItem);
if (to + 1 < this.viewItems.length) {
this.el.insertBefore(viewItem.container, this.viewItems[to + 1].container);
} else {
this.el.appendChild(viewItem.container);
}
this.layoutViews();
this.state = State.Idle;
}
private relayout(lowPriorityIndex?: number): void {
const contentSize = this.viewItems.reduce((r, i) => r + i.size, 0);
this.resize(this.viewItems.length - 1, this.size - contentSize, undefined, lowPriorityIndex);
}
layout(size: number): void {
const previousSize = Math.max(this.size, this.contentSize);
this.size = size;
this.resize(this.viewItems.length - 1, size - previousSize);
}
private render(scrollTop: number, viewHeight: number): void {
let i: number;
let stop: number;
let renderTop = scrollTop;
let renderBottom = scrollTop + viewHeight;
let thisRenderBottom = this.lastRenderTop + this.lastRenderHeight;
// when view scrolls down, start rendering from the renderBottom
for (i = this.indexAfter(renderBottom) - 1, stop = this.indexAt(Math.max(thisRenderBottom, renderTop)); i >= stop; i--) {
if (this.insertItemInDOM(<IViewItem>this.itemAtIndex(i))) {
this.dirtyState = true;
}
}
// when view scrolls up, start rendering from either this.renderTop or renderBottom
for (i = Math.min(this.indexAt(this.lastRenderTop), this.indexAfter(renderBottom)) - 1, stop = this.indexAt(renderTop); i >= stop; i--) {
if (this.insertItemInDOM(<IViewItem>this.itemAtIndex(i))) {
this.dirtyState = true;
}
}
// when view scrolls down, start unrendering from renderTop
for (i = this.indexAt(this.lastRenderTop), stop = Math.min(this.indexAt(renderTop), this.indexAfter(thisRenderBottom)); i < stop; i++) {
if (this.removeItemFromDOM(<IViewItem>this.itemAtIndex(i))) {
this.dirtyState = true;
}
}
// when view scrolls up, start unrendering from either renderBottom this.renderTop
for (i = Math.max(this.indexAfter(renderBottom), this.indexAt(this.lastRenderTop)), stop = this.indexAfter(thisRenderBottom); i < stop; i++) {
if (this.removeItemFromDOM(<IViewItem>this.itemAtIndex(i))) {
this.dirtyState = true;
}
}
let topItem = this.itemAtIndex(this.indexAt(renderTop));
if (topItem) {
this.el.style.top = (topItem.top - renderTop) + 'px';
}
this.lastRenderTop = renderTop;
this.lastRenderHeight = renderBottom - renderTop;
}
private onSashStart({ sash, start }: ISashEvent): void {
const index = firstIndex(this.sashItems, item => item.sash === sash);
const sizes = this.viewItems.map(i => i.size);
// const upIndexes = range(index, -1);
// const collapseUp = upIndexes.reduce((r, i) => r + (sizes[i] - this.viewItems[i].view.minimumSize), 0);
// const expandUp = upIndexes.reduce((r, i) => r + (this.viewItems[i].view.maximumSize - sizes[i]), 0);
// const downIndexes = range(index + 1, this.viewItems.length);
// const collapseDown = downIndexes.reduce((r, i) => r + (sizes[i] - this.viewItems[i].view.minimumSize), 0);
// const expandDown = downIndexes.reduce((r, i) => r + (this.viewItems[i].view.maximumSize - sizes[i]), 0);
// const minDelta = -Math.min(collapseUp, expandDown);
// const maxDelta = Math.min(collapseDown, expandUp);
this.sashDragState = { start, index, sizes };
}
private onSashChange({ sash, current }: ISashEvent): void {
const { index, start, sizes } = this.sashDragState;
const delta = current - start;
this.resize(index, delta, sizes);
}
private onViewChange(item: IViewItem, size: number | undefined): void {
const index = this.viewItems.indexOf(item);
if (index < 0 || index >= this.viewItems.length) {
return;
}
size = typeof size === 'number' ? size : item.size;
size = clamp(size, item.view.minimumSize, item.view.maximumSize);
item.size = size;
this.relayout(index);
}
resizeView(index: number, size: number): void {
if (this.state !== State.Idle) {
throw new Error('Cant modify splitview');
}
this.state = State.Busy;
if (index < 0 || index >= this.viewItems.length) {
return;
}
const item = this.viewItems[index];
size = Math.round(size);
size = clamp(size, item.view.minimumSize, item.view.maximumSize);
let delta = size - item.size;
if (delta !== 0 && index < this.viewItems.length - 1) {
const downIndexes = range(index + 1, this.viewItems.length);
const collapseDown = downIndexes.reduce((r, i) => r + (this.viewItems[i].size - this.viewItems[i].view.minimumSize), 0);
const expandDown = downIndexes.reduce((r, i) => r + (this.viewItems[i].view.maximumSize - this.viewItems[i].size), 0);
const deltaDown = clamp(delta, -expandDown, collapseDown);
this.resize(index, deltaDown);
delta -= deltaDown;
}
if (delta !== 0 && index > 0) {
const upIndexes = range(index - 1, -1);
const collapseUp = upIndexes.reduce((r, i) => r + (this.viewItems[i].size - this.viewItems[i].view.minimumSize), 0);
const expandUp = upIndexes.reduce((r, i) => r + (this.viewItems[i].view.maximumSize - this.viewItems[i].size), 0);
const deltaUp = clamp(-delta, -collapseUp, expandUp);
this.resize(index - 1, deltaUp);
}
this.state = State.Idle;
}
// DOM changes
private insertItemInDOM(item: IViewItem): boolean {
if (item.container.parentElement) {
return false;
}
let elementAfter: HTMLElement = null;
let itemAfter = <IViewItem>this.itemAfter(item);
if (itemAfter && itemAfter.container) {
elementAfter = itemAfter.container;
}
if (elementAfter === null) {
this.el.appendChild(item.container);
} else {
try {
this.el.insertBefore(item.container, elementAfter);
} catch (e) {
// console.warn('Failed to locate previous tree element');
this.el.appendChild(item.container);
}
}
item.layout();
return true;
}
private removeItemFromDOM(item: IViewItem): boolean {
if (!item || !item.container || !item.container.parentElement) {
return false;
}
this.el.removeChild(item.container);
return true;
}
getViewSize(index: number): number {
if (index < 0 || index >= this.viewItems.length) {
return -1;
}
return this.viewItems[index].size;
}
private resize(index: number, delta: number, sizes = this.viewItems.map(i => i.size), lowPriorityIndex?: number): void {
if (index < 0 || index >= this.viewItems.length) {
return;
}
if (delta !== 0) {
let upIndexes = range(index, -1);
let downIndexes = range(index + 1, this.viewItems.length);
if (typeof lowPriorityIndex === 'number') {
upIndexes = pushToEnd(upIndexes, lowPriorityIndex);
downIndexes = pushToEnd(downIndexes, lowPriorityIndex);
}
const upItems = upIndexes.map(i => this.viewItems[i]);
const upSizes = upIndexes.map(i => sizes[i]);
const downItems = downIndexes.map(i => this.viewItems[i]);
const downSizes = downIndexes.map(i => sizes[i]);
for (let i = 0, deltaUp = delta; deltaUp !== 0 && i < upItems.length; i++) {
const item = upItems[i];
const size = clamp(upSizes[i] + deltaUp, item.view.minimumSize, item.view.maximumSize);
const viewDelta = size - upSizes[i];
deltaUp -= viewDelta;
item.size = size;
}
for (let i = 0, deltaDown = delta; deltaDown !== 0 && i < downItems.length; i++) {
const item = downItems[i];
const size = clamp(downSizes[i] - deltaDown, item.view.minimumSize, item.view.maximumSize);
const viewDelta = size - downSizes[i];
deltaDown += viewDelta;
item.size = size;
}
}
let contentSize = this.viewItems.reduce((r, i) => r + i.size, 0);
let emptyDelta = this.size - contentSize;
for (let i = this.viewItems.length - 1; emptyDelta > 0 && i >= 0; i--) {
const item = this.viewItems[i];
const size = clamp(item.size + emptyDelta, item.view.minimumSize, item.view.maximumSize);
const viewDelta = size - item.size;
emptyDelta -= viewDelta;
item.size = size;
}
this.contentSize = this.viewItems.reduce((r, i) => r + i.size, 0);
this.scrollable.setScrollDimensions({
scrollHeight: this.contentSize,
height: this.size
});
this.layoutViews();
}
private layoutViews(): void {
if (this.dirtyState) {
for (let i = this.indexAt(this.lastRenderTop); i <= this.indexAfter(this.lastRenderTop + this.lastRenderHeight) - 1; i++) {
this.viewItems[i].layout();
if (this.options.enableResizing) {
this.sashItems[i].sash.layout();
}
}
this.dirtyState = false;
}
// Update sashes enablement
// let previous = false;
// const collapsesDown = this.viewItems.map(i => previous = (i.size - i.view.minimumSize > 0) || previous);
// previous = false;
// const expandsDown = this.viewItems.map(i => previous = (i.view.maximumSize - i.size > 0) || previous);
// const reverseViews = [...this.viewItems].reverse();
// previous = false;
// const collapsesUp = reverseViews.map(i => previous = (i.size - i.view.minimumSize > 0) || previous).reverse();
// previous = false;
// const expandsUp = reverseViews.map(i => previous = (i.view.maximumSize - i.size > 0) || previous).reverse();
// this.sashItems.forEach((s, i) => {
// if ((collapsesDown[i] && expandsUp[i + 1]) || (expandsDown[i] && collapsesUp[i + 1])) {
// s.sash.enable();
// } else {
// s.sash.disable();
// }
// });
}
private getSashPosition(sash: Sash): number {
let position = 0;
for (let i = 0; i < this.sashItems.length; i++) {
position += this.viewItems[i].size;
if (this.sashItems[i].sash === sash) {
return position;
}
}
return 0;
}
dispose(): void {
this.viewItems.forEach(i => i.disposable.dispose());
this.viewItems = [];
this.sashItems.forEach(i => i.disposable.dispose());
this.sashItems = [];
}
}

View File

@@ -28,6 +28,7 @@ export class SelectBox extends AngularDisposable implements OnInit, OnChanges {
@Input() options: string[];
@Input() selectedOption: string;
@Input() onlyEmitOnChange = false;
@Input('aria-label') ariaLabel: string;
@Output() onDidSelect = new EventEmitter<ISelectData>();
@@ -42,7 +43,7 @@ export class SelectBox extends AngularDisposable implements OnInit, OnChanges {
}
ngOnInit(): void {
this._selectbox = new vsSelectBox(this.options, this.selectedOption, this.contextViewService);
this._selectbox = new vsSelectBox(this.options, this.selectedOption, this.contextViewService, undefined, { ariaLabel: this.ariaLabel });
this._selectbox.render(this._el.nativeElement);
this._selectbox.onDidSelect(e => {
if (this.onlyEmitOnChange) {

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import { SelectBox as vsSelectBox, ISelectBoxStyles as vsISelectBoxStyles } from 'vs/base/browser/ui/selectBox/selectBox';
import { SelectBox as vsSelectBox, ISelectBoxStyles as vsISelectBoxStyles, ISelectBoxOptions } from 'vs/base/browser/ui/selectBox/selectBox';
import { Color } from 'vs/base/common/color';
import { IContextViewProvider, AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview';
import * as dom from 'vs/base/browser/dom';
@@ -46,8 +46,8 @@ export class SelectBox extends vsSelectBox {
private inputValidationErrorBackground: Color;
private element: HTMLElement;
constructor(options: string[], selectedOption: string, contextViewProvider: IContextViewProvider, container?: HTMLElement) {
super(options, 0, contextViewProvider);
constructor(options: string[], selectedOption: string, contextViewProvider: IContextViewProvider, container?: HTMLElement, selectBoxOptions?: ISelectBoxOptions) {
super(options, 0, contextViewProvider, undefined, selectBoxOptions);
this._optionsDictionary = new Array();
for (var i = 0; i < options.length; i++) {
this._optionsDictionary[options[i]] = i;
@@ -84,6 +84,7 @@ export class SelectBox extends vsSelectBox {
this.inputValidationWarningBackground = styles.inputValidationWarningBackground;
this.inputValidationErrorBorder = styles.inputValidationErrorBorder;
this.inputValidationErrorBackground = styles.inputValidationErrorBackground;
this.applyStyles();
}
public selectWithOptionName(optionName: string): void {

View File

@@ -0,0 +1,215 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export interface IObservableCollection<T> {
getLength(): number;
at(index: number): T;
getRange(start: number, end: number): T[];
setCollectionChangedCallback(callback: (change: CollectionChange, startIndex: number, count: number) => void): void;
}
export interface IGridDataRow {
row?: number;
values: any[];
}
export enum CollectionChange {
ItemsReplaced
}
class LoadCancellationToken {
isCancelled: boolean;
}
class DataWindow<TData> {
private _dataSourceLength: number;
private _data: TData[];
private _length: number = 0;
private _offsetFromDataSource: number = -1;
private loadFunction: (offset: number, count: number) => Thenable<TData[]>;
private lastLoadCancellationToken: LoadCancellationToken;
private loadCompleteCallback: (start: number, end: number) => void;
private placeholderItemGenerator: (index: number) => TData;
constructor(dataSourceLength: number,
loadFunction: (offset: number, count: number) => Thenable<TData[]>,
placeholderItemGenerator: (index: number) => TData,
loadCompleteCallback: (start: number, end: number) => void) {
this._dataSourceLength = dataSourceLength;
this.loadFunction = loadFunction;
this.placeholderItemGenerator = placeholderItemGenerator;
this.loadCompleteCallback = loadCompleteCallback;
}
getStartIndex(): number {
return this._offsetFromDataSource;
}
getEndIndex(): number {
return this._offsetFromDataSource + this._length;
}
contains(dataSourceIndex: number): boolean {
return dataSourceIndex >= this.getStartIndex() && dataSourceIndex < this.getEndIndex();
}
getItem(index: number): TData {
if (!this._data) {
return this.placeholderItemGenerator(index);
}
return this._data[index - this._offsetFromDataSource];
}
positionWindow(offset: number, length: number): void {
this._offsetFromDataSource = offset;
this._length = length;
this._data = undefined;
if (this.lastLoadCancellationToken) {
this.lastLoadCancellationToken.isCancelled = true;
}
if (length === 0) {
return;
}
let cancellationToken = new LoadCancellationToken();
this.lastLoadCancellationToken = cancellationToken;
this.loadFunction(offset, length).then(data => {
if (!cancellationToken.isCancelled) {
this._data = data;
this.loadCompleteCallback(this._offsetFromDataSource, this._offsetFromDataSource + this._length);
}
});
}
}
export class VirtualizedCollection<TData> implements IObservableCollection<TData> {
private _length: number;
private _windowSize: number;
private _bufferWindowBefore: DataWindow<TData>;
private _window: DataWindow<TData>;
private _bufferWindowAfter: DataWindow<TData>;
private collectionChangedCallback: (change: CollectionChange, startIndex: number, count: number) => void;
constructor(windowSize: number,
length: number,
loadFn: (offset: number, count: number) => Thenable<TData[]>,
private _placeHolderGenerator: (index: number) => TData) {
this._windowSize = windowSize;
this._length = length;
let loadCompleteCallback = (start: number, end: number) => {
if (this.collectionChangedCallback) {
this.collectionChangedCallback(CollectionChange.ItemsReplaced, start, end - start);
}
};
this._bufferWindowBefore = new DataWindow(length, loadFn, _placeHolderGenerator, loadCompleteCallback);
this._window = new DataWindow(length, loadFn, _placeHolderGenerator, loadCompleteCallback);
this._bufferWindowAfter = new DataWindow(length, loadFn, _placeHolderGenerator, loadCompleteCallback);
}
setCollectionChangedCallback(callback: (change: CollectionChange, startIndex: number, count: number) => void): void {
this.collectionChangedCallback = callback;
}
getLength(): number {
return this._length;
}
at(index: number): TData {
return this.getRange(index, index + 1)[0];
}
getRange(start: number, end: number): TData[] {
// current data may contain placeholders
let currentData = this.getRangeFromCurrent(start, end);
// only shift window and make promise of refreshed data in following condition:
if (start < this._bufferWindowBefore.getStartIndex() || end > this._bufferWindowAfter.getEndIndex()) {
// jump, reset
this.resetWindowsAroundIndex(start);
} else if (end <= this._bufferWindowBefore.getEndIndex()) {
// scroll up, shift up
let windowToRecycle = this._bufferWindowAfter;
this._bufferWindowAfter = this._window;
this._window = this._bufferWindowBefore;
this._bufferWindowBefore = windowToRecycle;
let newWindowOffset = Math.max(0, this._window.getStartIndex() - this._windowSize);
this._bufferWindowBefore.positionWindow(newWindowOffset, this._window.getStartIndex() - newWindowOffset);
} else if (start >= this._bufferWindowAfter.getStartIndex()) {
// scroll down, shift down
let windowToRecycle = this._bufferWindowBefore;
this._bufferWindowBefore = this._window;
this._window = this._bufferWindowAfter;
this._bufferWindowAfter = windowToRecycle;
let newWindowOffset = Math.min(this._window.getStartIndex() + this._windowSize, this._length);
let newWindowLength = Math.min(this._length - newWindowOffset, this._windowSize);
this._bufferWindowAfter.positionWindow(newWindowOffset, newWindowLength);
}
return currentData;
}
private getRangeFromCurrent(start: number, end: number): TData[] {
let currentData = [];
for (let i = 0; i < end - start; i++) {
currentData.push(this.getDataFromCurrent(start + i));
}
return currentData;
}
private getDataFromCurrent(index: number): TData {
if (this._bufferWindowBefore.contains(index)) {
return this._bufferWindowBefore.getItem(index);
} else if (this._bufferWindowAfter.contains(index)) {
return this._bufferWindowAfter.getItem(index);
} else if (this._window.contains(index)) {
return this._window.getItem(index);
}
return this._placeHolderGenerator(index);
}
private resetWindowsAroundIndex(index: number): void {
let bufferWindowBeforeStart = Math.max(0, index - this._windowSize * 1.5);
let bufferWindowBeforeEnd = Math.max(0, index - this._windowSize / 2);
this._bufferWindowBefore.positionWindow(bufferWindowBeforeStart, bufferWindowBeforeEnd - bufferWindowBeforeStart);
let mainWindowStart = bufferWindowBeforeEnd;
let mainWindowEnd = Math.min(mainWindowStart + this._windowSize, this._length);
this._window.positionWindow(mainWindowStart, mainWindowEnd - mainWindowStart);
let bufferWindowAfterStart = mainWindowEnd;
let bufferWindowAfterEnd = Math.min(bufferWindowAfterStart + this._windowSize, this._length);
this._bufferWindowAfter.positionWindow(bufferWindowAfterStart, bufferWindowAfterEnd - bufferWindowAfterStart);
}
}
export class AsyncDataProvider<TData extends IGridDataRow> implements Slick.DataProvider<TData> {
constructor(private dataRows: IObservableCollection<TData>) { }
public getLength(): number {
return this.dataRows ? this.dataRows.getLength() : 0;
}
public getItem(index: number): TData {
return !this.dataRows ? undefined : this.dataRows.at(index);
}
public getRange(start: number, end: number): TData[] {
return !this.dataRows ? undefined : this.dataRows.getRange(start, end);
}
}

View File

@@ -46,7 +46,8 @@
background-image: url('down.svg');
}
.vs-dark .slick-header-menubutton {
.vs-dark .slick-header-menubutton,
.hc-black .slick-header-menubutton {
background-image: url('down-inverse.svg');
}
@@ -54,7 +55,8 @@
background-image: url('filter.svg');
}
.vs-dark .slick-header-menubutton.filtered {
.vs-dark .slick-header-menubutton.filtered,
.hc-black .slick-header-menubutton.filtered {
background-image: url('filter_inverse.svg');
}
@@ -74,6 +76,10 @@
background: none repeat scroll 0 0 #333333;
}
.hc-black .slick-header-menu {
background: none repeat scroll 0 0 #000000;
}
.slick-header-menu a.monaco-button.monaco-text-button {
width: 60px;
margin: 6px 6px 6px 6px;
@@ -144,4 +150,8 @@ label {
.vs-dark .slick-header-menu > input.input {
color: #4a4a4a;
}
.hc-black .slick-header-menu > input.input {
color: #000000;
}

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

View File

@@ -0,0 +1,76 @@
/*---------------------------------------------------------------------------------------------
* 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 DOM from 'vs/base/browser/dom';
import { StandardMouseWheelEvent } from 'vs/base/browser/mouseEvent';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import { mixin } from 'vs/base/common/objects';
const SCROLL_WHEEL_SENSITIVITY = 50;
export interface IMouseWheelSupportOptions {
scrollSpeed?: number;
}
const defaultOptions: IMouseWheelSupportOptions = {
scrollSpeed: SCROLL_WHEEL_SENSITIVITY
};
export class MouseWheelSupport implements Slick.Plugin<any> {
private viewport: HTMLElement;
private canvas: HTMLElement;
private options: IMouseWheelSupportOptions;
private _disposables: IDisposable[] = [];
constructor(options: IMouseWheelSupportOptions = {}) {
this.options = mixin(options, defaultOptions);
}
public init(grid: Slick.Grid<any>): void {
this.canvas = grid.getCanvasNode();
this.viewport = this.canvas.parentElement;
let onMouseWheel = (browserEvent: MouseWheelEvent) => {
let e = new StandardMouseWheelEvent(browserEvent);
this._onMouseWheel(e);
};
this._disposables.push(DOM.addDisposableListener(this.viewport, 'mousewheel', onMouseWheel));
this._disposables.push(DOM.addDisposableListener(this.viewport, 'DOMMouseScroll', onMouseWheel));
}
private _onMouseWheel(event: StandardMouseWheelEvent) {
const scrollHeight = this.canvas.clientHeight;
const height = this.viewport.clientHeight;
const scrollDown = Math.sign(event.deltaY) === -1;
if (scrollDown) {
if ((this.viewport.scrollTop - (event.deltaY * this.options.scrollSpeed)) + height > scrollHeight) {
this.viewport.scrollTop = scrollHeight - height;
this.viewport.dispatchEvent(new Event('scroll'));
} else {
this.viewport.scrollTop = this.viewport.scrollTop - (event.deltaY * this.options.scrollSpeed);
this.viewport.dispatchEvent(new Event('scroll'));
event.stopPropagation();
event.preventDefault();
}
} else {
if ((this.viewport.scrollTop - (event.deltaY * this.options.scrollSpeed)) < 0) {
this.viewport.scrollTop = 0;
this.viewport.dispatchEvent(new Event('scroll'));
} else {
this.viewport.scrollTop = this.viewport.scrollTop - (event.deltaY * this.options.scrollSpeed);
this.viewport.dispatchEvent(new Event('scroll'));
event.stopPropagation();
event.preventDefault();
}
}
}
destroy() {
dispose(this._disposables);
}
}

View File

@@ -1,5 +1,6 @@
// Adopted and converted to typescript from https://github.com/6pac/SlickGrid/blob/master/plugins/slick.rowdetailview.js
// heavily modified
import { escape } from 'sql/base/common/strings';
import { mixin } from 'vs/base/common/objects';
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("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='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
return html.join('');

View File

@@ -0,0 +1,72 @@
/*---------------------------------------------------------------------------------------------
* 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) {
// row is zero-based, we need make it 1 based for display in the result grid
//
return `<span>${row + 1}</span>`;
}
return null;
}
}

View File

@@ -11,9 +11,17 @@ import { IListStyles } from 'vs/base/browser/ui/list/listWidget';
import * as DOM from 'vs/base/browser/dom';
import { Color } from 'vs/base/common/color';
import { mixin } from 'vs/base/common/objects';
import { IDisposable } from 'vs/base/common/lifecycle';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { Orientation } from 'vs/base/browser/ui/splitview/splitview';
import { Widget } from 'vs/base/browser/ui/widget';
import { isArray, isBoolean } from 'vs/base/common/types';
import { Event, Emitter } from 'vs/base/common/event';
import { range } from 'vs/base/common/arrays';
export interface ITableContextMenuEvent {
anchor: HTMLElement | { x: number, y: number };
cell?: { row: number, cell: number };
}
export interface ITableStyles extends IListStyles {
tableHeaderBackground?: Color;
@@ -27,30 +35,46 @@ function getDefaultOptions<T>(): Slick.GridOptions<T> {
};
}
export class Table<T extends Slick.SlickData> extends Widget implements IThemable {
export interface ITableSorter<T> {
sort(args: Slick.OnSortEventArgs<T>);
}
export interface ITableConfiguration<T> {
dataProvider?: Slick.DataProvider<T> | Array<T>;
columns?: Slick.Column<T>[];
sorter?: ITableSorter<T>;
}
export class Table<T extends Slick.SlickData> extends Widget implements IThemable, IDisposable {
private styleElement: HTMLStyleElement;
private idPrefix: string;
private _grid: Slick.Grid<T>;
private _columns: Slick.Column<T>[];
private _data: TableDataView<T>;
private _data: Slick.DataProvider<T>;
private _sorter: ITableSorter<T>;
private _autoscroll: boolean;
private _onRowCountChangeListener: IDisposable;
private _container: HTMLElement;
private _tableContainer: HTMLElement;
private _classChangeTimeout: number;
constructor(parent: HTMLElement, data?: Array<T> | TableDataView<T>, columns?: Slick.Column<T>[], options?: Slick.GridOptions<T>) {
private _disposables: IDisposable[] = [];
private _onContextMenu = new Emitter<ITableContextMenuEvent>();
public readonly onContextMenu: Event<ITableContextMenuEvent> = this._onContextMenu.event;
constructor(parent: HTMLElement, configuration?: ITableConfiguration<T>, options?: Slick.GridOptions<T>) {
super();
if (data instanceof TableDataView) {
this._data = data;
if (!configuration || isArray(configuration.dataProvider)) {
this._data = new TableDataView<T>(configuration && configuration.dataProvider as Array<T>);
} else {
this._data = new TableDataView<T>(data);
this._data = configuration.dataProvider;
}
if (columns) {
this._columns = columns;
if (configuration.columns) {
this._columns = configuration.columns;
} else {
this._columns = new Array<Slick.Column<T>>();
}
@@ -81,15 +105,33 @@ export class Table<T extends Slick.SlickData> extends Widget implements IThemabl
this._grid = new Slick.Grid<T>(this._tableContainer, this._data, this._columns, newOptions);
this.idPrefix = this._tableContainer.classList[0];
DOM.addClass(this._container, this.idPrefix);
this._onRowCountChangeListener = this._data.onRowCountChange(() => this._handleRowCountChange());
this._grid.onSort.subscribe((e, args) => {
this._data.sort(args);
this._grid.invalidate();
this._grid.render();
if (configuration.sorter) {
this._sorter = configuration.sorter;
this._grid.onSort.subscribe((e, args) => {
this._sorter.sort(args);
this._grid.invalidate();
this._grid.render();
});
}
this._grid.onContextMenu.subscribe((e: JQuery.Event) => {
const originalEvent = e.originalEvent;
const cell = this._grid.getCellFromEvent(originalEvent);
const anchor = originalEvent instanceof MouseEvent ? { x: originalEvent.x, y: originalEvent.y } : originalEvent.srcElement as HTMLElement;
this._onContextMenu.fire({ anchor, cell });
});
}
private _handleRowCountChange() {
public dispose() {
dispose(this._disposables);
}
public invalidateRows(rows: number[], keepEditor: boolean) {
this._grid.invalidateRows(rows, keepEditor);
this._grid.render();
}
public updateRowCount() {
this._grid.updateRowCount();
this._grid.render();
if (this._autoscroll) {
@@ -113,20 +155,22 @@ export class Table<T extends Slick.SlickData> extends Widget implements IThemabl
} else {
this._data = new TableDataView<T>(data);
}
this._onRowCountChangeListener.dispose();
this._grid.setData(this._data, true);
this._onRowCountChangeListener = this._data.onRowCountChange(() => this._handleRowCountChange());
}
get columns(): Slick.Column<T>[] {
return this._grid.getColumns();
}
setSelectedRows(rows: number[]) {
this._grid.setSelectedRows(rows);
public setSelectedRows(rows: number[] | boolean) {
if (isBoolean(rows)) {
this._grid.setSelectedRows(range(this._grid.getDataLength()));
} else {
this._grid.setSelectedRows(rows);
}
}
getSelectedRows(): number[] {
public getSelectedRows(): number[] {
return this._grid.getSelectedRows();
}
@@ -143,21 +187,6 @@ export class Table<T extends Slick.SlickData> extends Widget implements IThemabl
};
}
onContextMenu(fn: (e: Slick.EventData, data: Slick.OnContextMenuEventArgs<T>) => any): IDisposable;
onContextMenu(fn: (e: DOMEvent, data: Slick.OnContextMenuEventArgs<T>) => any): IDisposable;
onContextMenu(fn: any): IDisposable {
this._grid.onContextMenu.subscribe(fn);
return {
dispose: () => {
this._grid.onContextMenu.unsubscribe(fn);
}
};
}
getCellFromEvent(e: DOMEvent): Slick.Cell {
return this._grid.getCellFromEvent(e);
}
setSelectionModel(model: Slick.SelectionModel<T, Array<Slick.Range>>) {
this._grid.setSelectionModel(model);
}
@@ -194,7 +223,7 @@ export class Table<T extends Slick.SlickData> extends Widget implements IThemabl
this._tableContainer.style.width = sizing.width + 'px';
this._tableContainer.style.height = sizing.height + 'px';
} else {
if (orientation === Orientation.HORIZONTAL) {
if (orientation === Orientation.VERTICAL) {
this._container.style.width = '100%';
this._container.style.height = sizing + 'px';
this._tableContainer.style.width = '100%';
@@ -207,6 +236,7 @@ export class Table<T extends Slick.SlickData> extends Widget implements IThemabl
}
}
this.resizeCanvas();
this.autosizeColumns();
}
autosizeColumns() {

View File

@@ -14,7 +14,7 @@ export interface IFindPosition {
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 sign = args.sortAsc ? 1 : -1;
return data.sort((a, b) => (a[field] === b[field] ? 0 : (a[field] > b[field] ? 1 : -1)) * sign);

View File

@@ -25,7 +25,7 @@ export class TableBasicView<T> extends View {
super(undefined, viewOpts);
this._container = document.createElement('div');
this._container.className = 'table-view';
this._table = new Table<T>(this._container, data, columns, tableOpts);
this._table = new Table<T>(this._container, { dataProvider: data, columns }, tableOpts);
}
public get table(): Table<T> {
@@ -59,7 +59,7 @@ export class TableHeaderView<T> extends HeaderView {
super(undefined, viewOpts);
this._container = document.createElement('div');
this._container.className = 'table-view';
this._table = new Table<T>(this._container, data, columns, tableOpts);
this._table = new Table<T>(this._container, { dataProvider: data, columns }, tableOpts);
}
public get table(): Table<T> {
@@ -76,7 +76,7 @@ export class TableHeaderView<T> extends HeaderView {
}
protected layoutBody(size: number): void {
this._table.layout(size, Orientation.HORIZONTAL);
this._table.layout(size, Orientation.VERTICAL);
}
focus(): void {
@@ -99,7 +99,7 @@ export class TableCollapsibleView<T> extends AbstractCollapsibleView {
super(undefined, viewOpts);
this._container = document.createElement('div');
this._container.className = 'table-view';
this._table = new Table<T>(this._container, data, columns, tableOpts);
this._table = new Table<T>(this._container, { dataProvider: data, columns }, tableOpts);
}
public render(container: HTMLElement, orientation: Orientation): void {
@@ -143,6 +143,6 @@ export class TableCollapsibleView<T> extends AbstractCollapsibleView {
}
protected layoutBody(size: number): void {
this._table.layout(size, Orientation.HORIZONTAL);
this._table.layout(size, Orientation.VERTICAL);
}
}

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');
}
.vs .icon.add,
.vs-dark .icon.add,
.hc-black .icon.add {
background-image: url('add.svg');
}
.vs .icon.stop,
.vs-dark .icon.stop,
.hc-black .icon.stop {

View File

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

View File

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

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