Compare commits

..

175 Commits

Author SHA1 Message Date
Steve Faulkner
123ec2e45c Fix commas 2020-10-29 19:20:30 -05:00
Steve Faulkner
2f4abfa796 Remove rupm 2020-10-29 19:15:08 -05:00
Steve Faulkner
79769e9689 Remove AutoPilot v2 (#304)
* Remove AutoPilot v2

* Update DatabaseSettingsTab.ts

* Update DatabaseSettingsTab.ts

* Update src/Explorer/Tabs/DatabaseSettingsTab.ts

Co-authored-by: Laurent Nguyen <laurent.nguyen@microsoft.com>

* Update src/Explorer/Tabs/SettingsTab.ts

* Update src/Explorer/Tabs/DatabaseSettingsTab.ts

Co-authored-by: Laurent Nguyen <laurent.nguyen@microsoft.com>

* Update src/Explorer/Tabs/SettingsTab.ts

* Remove more unused code

* Remove import

Co-authored-by: Laurent Nguyen <laurent.nguyen@microsoft.com>
2020-10-29 11:26:37 -05:00
Srinath Narayanan
542abf4d3a Added extra logging for mongo index update RP call (#303)
* Added extra logging

* changes error -> error.message in traceFailure
2020-10-27 13:39:38 -07:00
Steve Faulkner
4bee46ce55 Add Tab Name to Telemetry (#302) 2020-10-27 10:36:31 -05:00
Steve Faulkner
fe58722002 Remove cached resource calls (#297) 2020-10-26 17:17:41 -05:00
Steve Faulkner
94ff6b3e81 Prevent JSON.stringify(error) (#298) 2020-10-26 16:50:31 -05:00
Srinath Narayanan
b4219e2994 Added Support for editing Mongo Indexing Policy from Settings tab (#284)
* added  SettingsV2 Tab

* lint changes

* foxed failing test

* Addressed PR comments

- removed dangerouslySetInnerHtml
- removed underscore dependency
- added AccessibleElement
- removed unnecesary exceptions to linting

* split render into separate functions

- removed sinon in test
- Added some enums to replace constant strings
- removed dangerously set inner html
- made autopilot input as StatefulValue

* add settingscomponent snapshot

* fixed linting errors

* fixed errors

* addressed PR comments

- Moved StatefulValue to new class
- Split render to more functions for throughputInputComponents

* Added sub components

- Added tests for SettingsRenderUtls
- Added empty test files for adding tests later

* Moved all inputs to fluent UI

- removed rupm
- added reusable styles

* Added Tabs

- Added ToolTipLabel component
- Removed toggleables for individual components
- Removed accessible elements
- Added IndexingPolicyComponent

* Added more tests

* Addressed PR comments

* Moved Label radio buttons to choicegroup

* fixed lint errors

* Removed StatefulValue

- Moved conflict res tab to the end
- Added styling for autpilot radiobuttons

* fixed linting errors

* fix bugs from merge to master

* fixed formatting issue

* Addressed PR comments

- Added unit tests for smaller methods within each component

* fixed linting errors

* removed redundant snapshots

* removed empty line

* made separate props objects for subcomponents

* Moved dirty checks to sub components

* Made indesing policy component height = 80% of view port

- modified auto pilot v3 messages
- Added Fluent UI tolltip
-

* Moved warning messages inline

* moved conflict res helpers out

* fixed bugs

* added stack style for message

* fixed tests

* Added tests

* fixed linting and format errors

* undid changes

* more edits

* fixed compile errors

* fixed compile errors

* fixed errors

* fixed bug with save and discard buttons

* fixed compile errors

* added MongoIndexingPolicy component

* addressed PR comments

* moved read indexes to scale context

* added add index feature

* added AddMongoIndexComponent

* Added collapsible portions and focus changes

* removed unnecessary imports

* finetuned UI

* more edits

* Added mongoindexeditor flight

- Moved add index UI to within current index pane

* minro edits

* Added separate warning messages for index refresh

* aligned items

* Fixed tests

* minor edits

* resolved PR comments

* modified refs usage

* compile errors fixed

* moved fetch of notifications and offer to within the tab activation

* fixed PR comments

* added error handling

* added AAD verification

* removed l empty line

* added back line

* deleted file

* added file

* addressed PR comments

* addressed PR comments

* fixed format error

* updated package.json

* updated package-lock.json
2020-10-26 14:17:40 -07:00
victor-meng
294270b6aa Allow users to switch between manual and autoscale for fixed collections (#299)
- removed the `isFixed` check in both new and old settings tab so that the options to switch between manual and autoscale shows up for fixed collections
- updated the message below the text box to inform the users that the max RU for fixed collections is 10000
- updated validation rule so that the max RU cannot exceed 10000 for fixed collections for both autoscale and manual
2020-10-26 19:00:21 +00:00
Laurent Nguyen
e4bab1de4b Bug fix: code complete dropdown background color in .main is accidentally overridden by hostedexplorer.less (#289) 2020-10-26 15:46:01 +01:00
victor-meng
703ceacd3f Remove extra error logging in SettingsComponent (#296)
Remove extra error logging for `updateOfferThroughputBeyondLimit` in SettingsComponent.
2020-10-23 18:25:32 +00:00
Tanuj Mittal
734df3dd18 Pass subscriptionId when publishing/accessing published notebooks (#288)
We need to record `subscriptionId` when publishing a notebook, also we want to restrict notebooks to only from a particular `subscriptionId` when accessing `My published work` tab. This change passes the `subscriptionId` as part of the URL when publishing or accessing published notebooks.
2020-10-22 00:01:22 +00:00
victor-meng
1e19f02fd7 Use SDK calls for stored procedure, trigger, and UDF operations for Gemlin API (#295)
RP doesn't support stored procedure, trigger, and UDF operations for Gremlin API so we have to use SDK for now.
2020-10-21 21:58:28 +00:00
victor-meng
24b5b754ca Fix error handling in DE (#294)
- Replaced `JSON.stringify(error)` with `error.message`
- Created `ErrorHandlingUtils` and moved all error logging actions in there
2020-10-21 21:28:30 +00:00
Steve Faulkner
e09730d782 Fixed Collections for Mongo can have 20gb (#293) 2020-10-20 22:58:36 -05:00
Chris-MS-896
09a95fded4 [ID: 833708][Screen Reader - CosmosDB – New KeySpace] Visual label(K… (#287)
* '[ID: 833708][Screen Reader - CosmosDB – New KeySpace] Visual label(Keyspace id) and aria-label(Database id) is not same for Keyspace id edit field.'

* 'update on aria-label'
2020-10-20 17:42:39 -05:00
Zachary Foster
7ffa18a190 Revert "Adds e2e tables test (#276)" (#292)
This reverts commit 30353c26f3.
2020-10-20 12:02:50 -05:00
Zachary Foster
30353c26f3 Adds e2e tables test (#276)
* Adds tables test

* Include .env var

* Adds asElement on again

* Add further loading states

* Format

* Hope to not lose focus

* Adds ID to shared key and modifies value of input directly

* Fix tables test

* Format

* Try uploading screenshots

* indent

* Fixes connection string

* Try wildcard upload path
2020-10-20 11:49:22 -04:00
Srinath Narayanan
34d8704071 Added error field in traceFailure for SettingsV2 Component (#290)
* added error field in tracrFailure for SettingsV2 Component

* more edits

* minor edits
2020-10-19 17:12:01 -07:00
Steve Faulkner
23714831bd Fix getDataExplorerWindow (#285) 2020-10-16 16:01:41 -05:00
victor-meng
9a5d46b6e0 Move UDF and trigger operations to RP (#283)
Move UDF and trigger operations to RP
2020-10-16 19:58:45 +00:00
victor-meng
b9245101bc Fix two bugs with tables database offer (#282)
1. After moving read databases call to RP for Tables API, we lose the `_rid` property. Since we are reading Table's database offer with SDK, we need the `_rid` of the database to find its offer. Without `_rid`, we won't be able to find the database offer and the Scale tab would disappear again. The fix is to use SDK to read databases for Table API so we have `_rid`.
2. There's a bug in the non-aad code path for updating offers which prevents users from switching from manual to autoscale and vice versa. The fix is to properly set the option headers.
2020-10-16 19:24:45 +00:00
Steve Faulkner
274deb13be Update azure-pipelines.cg.yml for Azure Pipelines 2020-10-15 16:20:38 -05:00
victor-meng
9d50577800 Move stored procedure operations to RP (#281)
- move read, delete, create, and update stored procedure calls to RP
- fixed a bug where the console message never clears when reading offers with SDK
2020-10-15 18:10:20 +00:00
Tanuj Mittal
bd00e5eb9b Support async notebook publishing (#275)
Handle cases for async notebook publishing. Now `Your published work` tab shows 3 sections - published, under review, and removed notebooks.

Note: The text labels are design placeholders

![image](https://user-images.githubusercontent.com/693092/95799994-3b5fb100-0cab-11eb-86fc-4ded0aeeddb1.png)
2020-10-15 03:49:18 +00:00
Steve Faulkner
821f665e78 Remove window.dataExplorerPlatform (#279)
More cleanup for #253 
- Remove window.dataExplorerPlatform
- Remove explorer factories
2020-10-15 03:25:13 +00:00
victor-meng
39f7ef331a Use RP to read databases for all API (#280)
Since I've cleaned up all the places that use `_rid` and `_self` of a database in my previous commit (d525afa142), we are safe to use RP to read databases for all APIs except Tables.

For Tables API, since RP doesn't support any database level operations, I have to hardcode the read database response. The only property we need in the Tables database is the id which is the string that we display in the resource tree.
2020-10-14 18:10:14 +00:00
victor-meng
9933a4988a Use SDK calls to read and update shared throughput for Tables API (#278)
RP does not supporting reading or updating database level throughput for Tables API so we have to switch back to using SDK calls for these operations.
2020-10-13 22:49:30 +00:00
victor-meng
d525afa142 Refactor code that uses the _rid and _self of a database or collection (#267) 2020-10-13 13:29:39 -07:00
Steve Faulkner
cfb9a0b321 Refactor NotificationsClient (#270) 2020-10-12 22:10:28 -05:00
Tanuj Mittal
3b64d75322 Add Report Abuse dialog for public gallery notebooks (#265)
![image](https://user-images.githubusercontent.com/693092/95408825-3975a680-08d5-11eb-812b-80f922ab9fc8.png)
2020-10-12 23:48:05 +00:00
Steve Faulkner
daba1c4ed4 Remove AutoMerge Github Action (#273)
Instead, we will be trying out the official Microsoft bot https://docs.opensource.microsoft.com/tools/fabricbot/index.html
2020-10-12 21:19:02 +00:00
victor-meng
a698e08638 Remove database offers cache and get offer directly from database (#268)
Currently we maintain a cache of all database offers which can be stale since we have moved to lazy loading the offers. Instead of reading the offer from the cache, we should just find the selected database and get the offer directly by calling `database.offer()`.
2020-10-12 21:00:47 +00:00
Garrett Ausfeldt
88d630fef4 Add summary to each table of the DataTable for narrator context (#238)
The DataTable control creates 3 tables in the DOM, one for the header, one for the body and one for the footer. Because of this when navigating through the tablem it says "leaving table", when it is really the same table. It seems this is not the default and because of the option **dom: "RZlfrtip"**, the DataTable is created this way. 

If I remove that setting, it will only create one table, BUT other things break, because there is a lot of custom DOM manipulation assuming the DOM was the way it was before (gross). This make me question if we wanted this on purpose to maybe solve a cross browser scrolling issue.

Instead I decided to leave it as is, until migrating to Microsoft's Fluent UI is prioritized. However I did add a summary attribute to each table, so that when listening to the narrator, it make more sense when leaving one part of the table into another part of the table. While not optimal, it should at least satisfy accessibility concern of it being confusing.
2020-10-12 20:30:37 +00:00
Armando Trejo Oliver
5ffa746adb Escape quotes in identifiers in CQL queries 2020-10-12 13:00:11 -05:00
Srinath Narayanan
a9a57f4ba9 Added telelmetry for settings v2 and v1 (#269)
* Added telelmetry for settings v2 and v1

* format errors fixed

* renamed actions
2020-10-12 09:01:00 -07:00
Steve Faulkner
47cc6fd7a8 Set default backend endpoint (#271) 2020-10-12 09:23:57 -05:00
Steve Faulkner
14cdf19efb Remove Explorer.isEmulator (#256) 2020-10-09 11:18:50 -05:00
Steve Faulkner
5191ae3f3a All events should trigger and ADO build 2020-10-09 09:47:52 -05:00
Steve Faulkner
ba862a8106 Remove jquery.contextMenu (#248) 2020-10-08 18:19:24 -05:00
Steve Faulkner
fe085b3e5a Restore AppInsights fetch telemetry (#263) 2020-10-08 17:55:02 -05:00
Srinath Narayanan
8028734cb0 Fixed settingsV2 bugs and added experimentation (#264)
* inital commit for flight tests

- FIxed bugs with settingstab v2

* minor edits

* removed console log

* fixed bug with autoscale throughput step increase

* resolved PR comments

* fixed compile error

* Added comment
2020-10-08 14:32:54 -07:00
Armando Trejo Oliver
444f663733 Update README.md (#255) 2020-10-08 15:57:38 -05:00
Laurent Nguyen
8c1ca35420 Fix double-scrollbar bug 2020-10-08 14:04:02 -05:00
Laurent Nguyen
b69174788d Add more Telemetry to Data Explorer (#242)
* Add Telemetry to command bar buttons

* Count and report # of files/notebooks/directories in myNotebook to telemetry

* Add resource tree clicks to Telemetry

* Log to Telemetry: opened notebook cell counts by type, kernelspec name

* Fix unit test

* Move Telemetry processor call in notebook traceNotebookTelemetry action from reducer to epic. Use action to trace other info.

* Fix react duplicate key error

* Log notebook cell context menu actions

* Reformat and cleanup

* Move resource tree tracing code out of render(). Only call once when tree is updated

* Fix build issues
2020-10-08 10:53:01 +02:00
Laurent Nguyen
ff03c79399 Fix horizontal scrollbar in notebook cell input issue (#260)
* Fix horizontal scrollbar in notebook cell input issue

* Cell input overflow visible
2020-10-08 09:17:46 +02:00
victor-meng
0382628249 Move update offers call to RP 2020-10-07 17:25:21 -05:00
Steve Faulkner
d346ebe054 Fix Blackforest Origin check (#261) 2020-10-07 15:11:11 -05:00
Zachary Foster
f5ecb8a04f Fixes e2e test input focus swapping (#262)
* Remove redundant E2E tests

* Remove deps

* Fixes e2e tests hopefully

Co-authored-by: Steve Faulkner <471400+southpolesteve@users.noreply.github.com>
Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
2020-10-07 15:41:38 -04:00
Laurent Nguyen
7dd046a15d Fix notebook kernel selection and auto-kernel-start (#254)
* Fix bug: cannot select kernel. Properly plug-in kernelspecs middleware to redux store configuration

* Properly auto-launch kernel with nteract's epic

* Keep kernel lazy start for notebook viewer

* Add unit tets
2020-10-07 08:39:04 +02:00
victor-meng
86d3f0d35d Fix database offers not loaded when add collection pane is opened 2020-10-06 22:22:54 -05:00
Tanuj Mittal
c08a1872cc Add correct content-type header to terminals calls (#258) 2020-10-06 16:55:13 -07:00
Jordi Bunster
0cc38868a6 Remove one indirection for ConfigContext
Things like ConfigContext.BACKEND_ENDPOINT are dynamically changed, and assigning them statically like this only bypasses that mechanism :(

Co-authored-by: Steve Faulkner <471400+southpolesteve@users.noreply.github.com>
2020-10-06 15:30:24 -07:00
Steve Faulkner
f5bbd52311 Test Output Cleanup 2020-10-05 23:21:58 -05:00
Steve Faulkner
b83f59ac31 Remove unused external files 2020-10-05 17:25:44 -05:00
Chris-MS-896
6b8c9f6961 [Azure][Screen Reader-CosmosDB – Data Explorer] Alt is not correctly defined for the console image on the data explorer page (#247)
* Update NotificationConsoleComponent.tsx

'Accessibiliy for alt image'

* Update NotificationConsoleComponent.tsx

'meet Unit test problems'

* Update NotificationConsoleComponent.test.tsx.snap

Update for snapshot

Co-authored-by: Chris896 <caoyiqun2017@gmail.com>
2020-10-05 16:14:51 -05:00
Steve Faulkner
2f978bac69 Use Github PAT for automerge 2020-10-05 14:34:17 -05:00
Steve Faulkner
d9d772f29c Add Automerge Action 2020-10-02 23:23:45 +00:00
victor-meng
0f82ed3749 Add checks for serverless account (#244) 2020-10-02 14:44:12 -07:00
Steve Faulkner
3112cf5573 Allow setting of ARM API version via query param (#241) 2020-10-02 10:45:32 -05:00
Laurent Nguyen
0ad5fb465b Switch notebook editors to monaco (#239)
* Upgrade nteract packages and related dependencies to make new stateful-component work

* Switch to new monacoEditor

* Configure store using nteract mythic configuration

* Replace CodeMirror with Monaco editor in NotebookReadOnlyRenderer

* Reformat

* Upgrade d3 to latest to resolve d3-selection conflicts with nteract/data-explorer that broke D3ForceGraph

* Upgrade jupyterlab terminal widget to work with latest version of react. Upgrade jupyterlab services to include latest fix for websocket auth

* Update jest test snapshots

* Upgrade packages to fix build issues

* Remove comment

* Fix unit tests

* Fix unit test snapshot

* Remove useless @types/node-fetch
2020-10-01 14:00:46 +02:00
Zachary Foster
4fe2098730 Adds mongo e2e spec (#207)
* Adds mongo e2e spec

* Adds mongo connection string

* Constantize sql spec

* Use shared login function

* Remove comment

* Remove login lines from cassandra

* Adds frame return tyoe

* test updates

* format

* adds unique name

* remove trivial type annotation
2020-09-30 16:42:33 -04:00
Srinath Narayanan
fc722e87be Refactored Settings Tab (#161)
* added  SettingsV2 Tab

* lint changes

* foxed failing test

* Addressed PR comments

- removed dangerouslySetInnerHtml
- removed underscore dependency
- added AccessibleElement
- removed unnecesary exceptions to linting

* split render into separate functions

- removed sinon in test
- Added some enums to replace constant strings
- removed dangerously set inner html
- made autopilot input as StatefulValue

* add settingscomponent snapshot

* fixed linting errors

* fixed errors

* addressed PR comments

- Moved StatefulValue to new class
- Split render to more functions for throughputInputComponents

* Added sub components

- Added tests for SettingsRenderUtls
- Added empty test files for adding tests later

* Moved all inputs to fluent UI

- removed rupm
- added reusable styles

* Added Tabs

- Added ToolTipLabel component
- Removed toggleables for individual components
- Removed accessible elements
- Added IndexingPolicyComponent

* Added more tests

* Addressed PR comments

* Moved Label radio buttons to choicegroup

* fixed lint errors

* Removed StatefulValue

- Moved conflict res tab to the end
- Added styling for autpilot radiobuttons

* fixed linting errors

* fix bugs from merge to master

* fixed formatting issue

* Addressed PR comments

- Added unit tests for smaller methods within each component

* fixed linting errors

* removed redundant snapshots

* removed empty line

* made separate props objects for subcomponents

* Moved dirty checks to sub components

* Made indesing policy component height = 80% of view port

- modified auto pilot v3 messages
- Added Fluent UI tolltip
-

* Moved warning messages inline

* moved conflict res helpers out

* fixed bugs

* added stack style for message

* fixed tests

* Added tests

* fixed linting and format errors

* undid changes

* more edits

* fixed compile errors

* fixed compile errors

* fixed errors

* fixed bug with save and discard buttons

* fixed compile errors

* addressed PR comments
2020-09-30 12:34:39 -07:00
Steve Faulkner
4ecdfe60eb Fix Parent Origin Regex (#237)
* Fix Parent Origin Regex

* Add another test case

* Handle more cases
2020-09-29 18:09:11 -05:00
Steve Faulkner
0c7a73e716 xxxRevert "More test cases"
This reverts commit b2c24fab4f.
2020-09-29 17:46:54 -05:00
Steve Faulkner
b2c24fab4f More test cases 2020-09-29 17:31:58 -05:00
Steve Faulkner
aa369760ad Remove option to delete/create root table database (#236) 2020-09-28 17:03:47 -05:00
victor-meng
23c5d2d7e0 Lazy load collection offer (#234) 2020-09-28 12:54:28 -07:00
Zachary Foster
f582887fd8 Fix Cassandra Endpoint URLs by adding trailing slash in construction (#235) 2020-09-28 15:53:27 -04:00
Srinath Narayanan
4b0b63b56b Users/srnara/mongo index (#229)
* added placeholder

* Added check box

* Added tolltip width constant

* Add telemetry

* formatting error

* formatting error

* support only for mongo v 3.6 accounts

* resolved comment
2020-09-28 01:36:10 -07:00
Tanuj Mittal
70c7d84bdb Do not fail when trying to find DE window with cross origin (#231)
* Do not fail when trying to find DE window with cross origin

* Fix lint errors
2020-09-25 14:49:11 -07:00
Tanuj Mittal
dcc2036793 Fix hotfix syntax in workflow (#232) 2020-09-25 14:44:15 -07:00
Tanuj Mittal
987368fe58 Disable endtoendpuppeteer tests (temporarily) (#233) 2020-09-25 14:38:49 -07:00
Steve Faulkner
91aa91d860 Cleanup extension endpoint loading (#224) 2020-09-24 18:10:54 -05:00
victor-meng
2e747a1a07 Fix create database for serverless accounts (#228) 2020-09-24 14:03:37 -07:00
Zachary Foster
290ca4aba5 Commits add table entity pane changes without steve's changes (#227) 2020-09-23 18:29:04 -04:00
victor-meng
28ceb18d73 Use SDK for reading database offer for Tables API (#226) 2020-09-23 13:32:47 -07:00
victor-meng
666a378b3b Fix resource tree refresh issue (#222) 2020-09-23 13:18:05 -07:00
Zachary Foster
13dafb9581 Adds cassandra e2e container CRUD test (#195)
* Starts cassandra

* Adds more cassandra

* wip

* Fix a few extra console.logs

* Format

* Adds cassandra connection string secret to ci

* Adds test name to failure screenshot

* Disable no-any on expect type, as it has getState() method

* Constantize some delays

* Accidentally deleted a brace
2020-09-22 16:21:57 -04:00
Tanuj Mittal
7c5c8ddb7a Fix incorrect usage of TelemetryProcessor (#221)
* Fix incorrect usage of TelemetryProcessor

* Address feedback
2020-09-22 12:19:06 -07:00
victor-meng
3f2c67af23 Fix some content becomes hidden after zooming to 200% (#223) 2020-09-22 11:59:44 -07:00
artrejo
3ae1f97ccc Remove AFEC check for Synapse Link and Mongo 2020-09-21 13:44:05 -07:00
Tanuj Mittal
92c4440d38 Skip refreshing pinned repos if token not available (#219) 2020-09-21 11:41:48 -07:00
victor-meng
1ccffab911 [Accessibility] Add horizontal scroll bar if tooltip box goes outside of panel boundary (#215) 2020-09-18 17:01:12 -07:00
victor-meng
dc56f7e154 Lazy load database offer in data explorer (#208)
Co-authored-by: zfoster <notzachfoster@gmail.com>
2020-09-18 16:00:21 -07:00
Laurent Nguyen
e62184a1f2 Fix inaccessible link by keyboard nav in info bubble (#216)
* Fix inaccessible link by keyboard nav in info bubble

* Fix shift-tab on link to focus previous element
2020-09-18 16:26:04 +02:00
Laurent Nguyen
26c832437b Support showing only notebook output scenario (#177)
* Enable hiding prompt when hiding inputs (so that only output is showing)

* Fix format

* Rename variable

* Use nteract prompt instead of copying source
2020-09-18 16:25:09 +02:00
Tanuj Mittal
832f8d560d Reset focus to trigger element when pane is closed (#217) 2020-09-17 15:08:46 -07:00
Steve Faulkner
d85c96d408 Allow remote config to set valid origins (#205) 2020-09-17 16:13:22 -05:00
Tanuj Mittal
bad6a60d07 Fix aria-labels in AddCollectionPane and DeleteCollectionConfirmationPane (#213)
* Fix aria-labels in AddCollectionPane and DeleteCollectionConfirmationPane

* Fix broken delete
2020-09-17 12:20:22 -07:00
Zachary Foster
b690fe18e6 Focuses header control element on Add Row click in Cassandra (#212) 2020-09-17 13:00:15 -04:00
Garrett Ausfeldt
1bbe08378c Fix focus when adding and removing a unique key (#214)
* fix focus when adding and removing a unique key

* cleanup

Co-authored-by: REDMOND\gaausfel <gaausfel@microsoft.com>
2020-09-17 09:48:42 -07:00
Laurent Nguyen
9b021b29b9 Fix a11y bugs in query stats table: too many columns, header info not passed to column contents (#209) 2020-09-17 10:43:01 +02:00
Laurent Nguyen
562ac38ff1 Fix contrast issue (blue hyperlink over black background) (#210) 2020-09-17 10:40:24 +02:00
DanielSPham
949f9203b8 Analytical storage label (#211)
Co-authored-by: Daniel Si Pham <v-danpha@microsoft.com>
2020-09-16 17:18:00 -07:00
DanielSPham
de7761ba4b Fixed aria label for autopilot throughput (#206)
Co-authored-by: Daniel Si Pham <v-danpha@microsoft.com>
2020-09-16 17:17:41 -07:00
DanielSPham
40f4efab7c Added heading role to pane title (#203)
Co-authored-by: Daniel Si Pham <v-danpha@microsoft.com>
2020-09-16 17:17:23 -07:00
Tanuj Mittal
34c41e1557 Add telemetry for OpenTerminal (#200) 2020-09-16 17:04:34 -07:00
Steve Faulkner
03b19fc875 Split all script data access methods (#197)
* Split all script data access methods

* More cleanup

* Fix Typo
2020-09-15 13:31:30 -05:00
Tanuj Mittal
d6a4924710 Add actionModifier to appInsights telemetry (#202) 2020-09-15 11:21:18 -07:00
DanielSPham
5ccf26e403 Change load more role (#190)
Co-authored-by: Daniel Si Pham <v-danpha@microsoft.com>
2020-09-14 17:02:29 -07:00
DanielSPham
ef7da10b6e Added alert role to query tables tab error (#199)
Co-authored-by: Daniel Si Pham <v-danpha@microsoft.com>
2020-09-14 16:53:39 -07:00
DanielSPham
dfd18152ca Fixed placeholder (#201)
* Fixed placeholder

* Clean up

Co-authored-by: Daniel Si Pham <v-danpha@microsoft.com>
2020-09-14 16:34:56 -07:00
DanielSPham
e22675bc40 Updated TreeComponent alt text (#188)
Co-authored-by: Daniel Si Pham <v-danpha@microsoft.com>
2020-09-14 16:27:12 -07:00
Tanuj Mittal
c4257bf4a9 Revert "Add telemetry for OpenTerminal success/failure (#192)" (#198)
This reverts commit 83b13de685.
2020-09-11 23:12:28 -07:00
Tanuj Mittal
728eeefa17 Fix file downloads from notebooks container (#196)
* Fix file downloads from notebooks container

* Add downloading message
2020-09-11 19:37:00 -07:00
Tanuj Mittal
83b13de685 Add telemetry for OpenTerminal success/failure (#192)
* Add telemetry for OpenTerminal success/failure

* Address review comments
2020-09-11 15:42:55 -07:00
Steve Faulkner
c401f88aae Improve ARM error parsing and display (#189) 2020-09-11 12:54:30 -05:00
DanielSPham
af820c0fbf Fixed notification console screen reader issue (#193)
* Fixed notification console screen reader issue

* Added aria expanded

Co-authored-by: Daniel Si Pham <v-danpha@microsoft.com>
2020-09-11 10:36:53 -07:00
swviswan
a2845a0102 Notebooks: Ability to download file in notebooks. (#194)
Today, if we have non notebook file in the container and we try to download the file, it fails. That is because, the filetype is always defined as notebook in the call to content provider's get method. Fixing it to use the right type in this change.
2020-09-10 23:39:33 -07:00
Tanuj Mittal
ed9b443bf6 Disable fetch tracking in appInsights (#187) 2020-09-10 12:15:24 -07:00
Zachary Foster
3fe63e88cb Adds e2e baseline SQL test in puppeteer (#160)
* Adds e2e baseline SQL test in puppeteer

* fix lint

* Skip index test

* add to ci

* Stop waiting for server

* Switch to headless mode

* Adds wait-for-server to package.json and uses it in puppeteer CI

* Adds waiton

* top level wait on

* Adds env var to secrets ci.yml

* use existing connection string

* redo tests

* nits and hopefully fix timeout issue

* Fix config file

* try waiting for delete container menu

* Removes statuseval

* Remove unused var

* Slow down and add quotes to selector

* Fix blocking point, remove wait

* Reduce to 50

* Adds database delete stuff

* remove logs, add back waitFors

* Finish container SQL spec test
2020-09-09 13:18:27 -04:00
DanielSPham
2de3c07f76 Fixed contrast ratio issues (#184)
Co-authored-by: Daniel Si Pham <v-danpha@microsoft.com>
2020-09-09 09:28:30 -07:00
DanielSPham
53bedb1641 Added descriptive aria label to autoscale throughput link (#185)
Co-authored-by: Daniel Si Pham <v-danpha@microsoft.com>
2020-09-08 11:03:48 -07:00
Steve Faulkner
e6ac5a7043 Telemetry Adjustments (#182) 2020-09-08 12:44:46 -05:00
Steve Faulkner
faf923f647 TypeScript 4.0 (#165) 2020-09-04 09:08:52 -05:00
Laurent Nguyen
d471cff77c Fix resource tree node selection bug (#170)
* Fix bug with resource tree selection for graphs

* Reformat and type fixes
2020-09-04 10:15:53 +02:00
DanielSPham
0a24a0b73e Updated aria label (#180)
Co-authored-by: Daniel Si Pham <v-danpha@microsoft.com>
2020-09-03 14:10:57 -07:00
DanielSPham
ab4753fd1d Fixed contrast ratio for links in notification area (#179)
Co-authored-by: Daniel Si Pham <v-danpha@microsoft.com>
2020-09-03 14:10:32 -07:00
victor-meng
6bc506b81f Clean up unused utility functions for creating databases and collections (#181) 2020-09-03 13:05:22 -07:00
victor-meng
efff26dbe7 Use edge instead of chrome for emulator e2e test (#178) 2020-09-02 12:01:32 -07:00
victor-meng
fae59d8754 Move create collection to RP (#173) 2020-09-02 10:02:29 -07:00
DanielSPham
c2cd383ece Added alert role for splash loader (#175)
Co-authored-by: Daniel Si Pham <v-danpha@microsoft.com>
2020-09-01 12:45:20 -07:00
DanielSPham
83c120a549 Added tooltips (#174)
Co-authored-by: Daniel Si Pham <v-danpha@microsoft.com>
2020-09-01 08:28:23 -07:00
DanielSPham
a28dede88d Screen reader changes (#171)
* Screen reader fix for splash screen

* Removing more stuff alt text

Co-authored-by: Daniel Si Pham <v-danpha@microsoft.com>
2020-09-01 08:27:51 -07:00
Laurent Nguyen
92073a5646 Start kernel when opening notebook tab (#168)
* Add Auto-start kernel epic

* Replace deprecated rxjs empty() with EMPTY constant

* Fix format
2020-08-28 09:51:44 +02:00
Jordi Bunster
5c84b3a7d4 Allow 'platform' only to be overriden (#167)
ConfigContext defines all kinds of URLs and what not, I'm not
sure about the security implications of allowing all this stuff to
be modifiable by just anyone.
2020-08-25 22:48:58 -07:00
victor-meng
3223ff7685 Move createDatabase to RP (#166) 2020-08-25 15:45:37 -07:00
Steve Faulkner
38732af907 Triple equal lint rule (#162) 2020-08-21 19:51:36 -05:00
Steve Faulkner
e837f574a8 Fix Telemetry for String Case (#163) 2020-08-21 14:38:30 -05:00
victor-meng
47a5c315b5 Move updateCollection to ARM (#152) 2020-08-21 11:24:01 -07:00
victor-meng
1c80ced259 Fix settings tab issue for cassandra and add feature flag (#159)
* Fix settings tab issue for cassandra and add feature flag

* Adding the rest of the changes....

Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
2020-08-19 18:11:43 -05:00
victor-meng
5e6ac78b7d Tables - ReadCollection: Switch back to using SDK (#157) 2020-08-19 17:54:23 -05:00
Steve Faulkner
999196193f Update Config context ARM endpoint (#158) 2020-08-19 13:20:10 -05:00
victor-meng
951289e190 Switch back to SDK for deleteDatabase and use RP for readCollections for table API (#155) 2020-08-18 11:04:37 -07:00
Tanuj Mittal
3279460cfd Update @azure/cosmos to 3.9.0 (#154) 2020-08-17 14:26:19 -07:00
victor-meng
07b9c1d1b7 Switch back to using SDK to read databases and collections for Mongo API (#153) 2020-08-17 11:42:02 -07:00
Tanuj Mittal
dde2ca75c4 Show submit button by default in GenericRightPaneComponent & other fixes (#144)
* Bug fixes

* Fix build
2020-08-17 11:39:11 -07:00
Laurent Nguyen
f44a3da568 Allow boolean partition key in graph visualization (#150)
* Allow boolean pk values to be displayed

* Add unit tests
2020-08-17 09:50:57 +02:00
Tanuj Mittal
22b2e1df48 Support empty offers for serverless accounts (#132)
* Add support for no offers when serverless accounts

* Add workaround for ignoring failed /offers when using connection string

* Revert @azure/cosmos upgrade
2020-08-14 17:45:13 -07:00
Steve Faulkner
2752d6af00 Create Cassandra Keyspace via ARM (#142) 2020-08-13 13:26:52 -05:00
Laurent Nguyen
cb5fe5316e Fix handling numeric partition keys (#113)
* Fix pk extraction from documentId in g.V() case

* Add unit test, cleanup pk related unit tests
2020-08-13 12:00:11 +02:00
Steve Faulkner
c0ce637eec Turn off HMR (#147) 2020-08-12 20:12:31 -05:00
victor-meng
b61a235bf6 Move readCollection, readCollections, and readDatabases to ARM (#134) 2020-08-12 17:13:43 -07:00
Steve Faulkner
0fa97c2ce9 Increase Cypress Timeout and Disable LiveReload in test (#145)
Co-authored-by: Tanuj Mittal <tamitta@microsoft.com>
2020-08-12 15:25:53 -05:00
victor-meng
fb71fb4e82 Refactor GenericRightPaneComponent to accept a ReactComponent as its content (#146) 2020-08-12 11:41:19 -07:00
victor-meng
455722c316 Fix deleteDatabase and deleteCollection with ARM (#143) 2020-08-11 18:36:42 -07:00
Tanuj Mittal
5886db81e9 Copy To functionality for notebooks (#141)
* Add Copy To functionality for notebooks

* Fix formatting

* Fix linting errors

* Fixes

* Fix build failure

* Rebase and address feedback

* Increase test coverage
2020-08-11 09:27:57 -07:00
Srinath Narayanan
7a3e54d43e Added support for acknowledging code of conduct for using public Notebook Gallery (#117)
* minro code edits

* Added support for acknowledging code of conduct

- Added CodeOfConduct component that shows links and a checkbox that must be acknwledged before seeing the public galley
- Added verbose message for notebook publish error (when another notebook with the same name exists in the gallery)
- Added a feature flag for enabling code of conduct acknowledgement

* Added Info Component

* minor edit

* fixed failign tests

* publish tab displayed only when code of conduct accepted

* added code of conduct fetch during publish

* fixed bug

* added test and addressed PR comments

* changed line endings

* added comment

* addressed PR comments
2020-08-11 00:37:05 -07:00
Steve Faulkner
3051961093 Add subscriptionId and authType to telemetry (#140) 2020-08-10 18:43:45 -05:00
Vignesh Rangaishenvi
abce15a6b2 Add init message when warming up notebook workspace (#139)
* Add init message

* Address comments
2020-08-10 15:02:24 -07:00
Steve Faulkner
a5b824ebb5 Fix master compile error 2020-08-10 12:02:18 -05:00
victor-meng
e28765d740 Add null check when reading offerAutopilotSettings (#138) 2020-08-10 11:55:43 -05:00
Srinath Narayanan
95f1efc03f Added support for notebook viewer link injection (#124)
* Added support for notebook viewer link injection

* updated tests
2020-08-10 01:53:51 -07:00
Steve Faulkner
455a6ac81b Fix update offer beyond throughput limit error (#135) 2020-08-06 18:15:40 -05:00
Vignesh Rangaishenvi
08ee86ecf1 Fix connection string renew token pane (#136)
* Fix IcM issue + conn string parsing

* format code

* Undo fix for IcM issue
2020-08-06 16:15:31 -07:00
Steve Faulkner
0011007d5f Refactor Global state into Context Files (#128) 2020-08-06 14:03:46 -05:00
vchske
d45af21996 Updating error message on mongo collection create (#118)
* 1) Updated mongo collection create pane to display a better error when a shard key is ill formed.
2) Updated an error message to use double quotes since no string interpolation is used. I thought it was included in a previoue PR but I guess the change didn't get staged.
2020-08-06 12:56:40 -05:00
Steve Faulkner
a64109ebaa Fix Generated ARM types (#131) 2020-08-06 09:27:41 -05:00
victor-meng
70f9b28499 Fix tabs manager test (#130) 2020-08-05 18:01:13 -07:00
Steve Faulkner
78e70cc7cc Refresh Caches only in Portal (#129) 2020-08-05 15:54:17 -05:00
Steve Faulkner
f132a8546c Move SQL database deletion to ARM (#126) 2020-08-04 18:03:14 -05:00
Tanuj Mittal
e6acf6686f Update NotebookReadOnlyRenderer.tsx (#127) 2020-08-04 10:24:25 -07:00
Steve Faulkner
2904a1a60d Move Delete Container call to use ARM when logged in with AAD (#110) 2020-08-03 17:11:07 -05:00
Tanuj Mittal
8c792fd147 Hide Azure Synapse Link button for Serverless accounts (#121) 2020-07-31 15:31:21 -07:00
Steve Faulkner
2a53dfabb5 Fix refresh resources button and remove portal specific notebooks call (#123) 2020-07-31 16:45:36 -05:00
Srinath Narayanan
14ef40029d Added session based view updation for gallery notebooks (#120) 2020-07-31 12:31:05 -07:00
Steve Faulkner
dab6e43d0d Hotfix: Remove extra JSON.stringify in Monogo update code path (#119) 2020-07-28 15:13:48 -05:00
Steve Faulkner
aea168c893 Add lint rule to prefer arror function (#114) 2020-07-27 16:40:04 -05:00
Steve Faulkner
fea321cd68 More ViewModel cleanup (#116) 2020-07-27 16:05:25 -05:00
Steve Faulkner
2e49ed45c3 Refactor DocumentClientUtilityBase to not be a class (#115) 2020-07-27 12:58:27 -05:00
Steve Faulkner
6d142f16f9 Refactor Data Access Utility (#112) 2020-07-24 16:45:48 -05:00
Steve Faulkner
6dcdacc8c4 Update CODEOWNERS 2020-07-24 15:45:42 -05:00
Tanuj Mittal
33969581ac Support serverless accounts (#109)
* Changes for serverless accounts

* Dont show upsell message for serverless accounts

* Update CassandraAddCollectionPane to support serverless
2020-07-24 13:13:54 -07:00
Srinath Narayanan
dc67c5f40b Added support for taking screenshot during Notebook publish to Gallery (#108)
* Added support for taking screenshot

- Screenshot is taken using html2canvas package
- Converted to base 64 and uploaded to metadata
- For Using first display output
  - Notebok object is passed instead of string, to publish pane
  - The first cell with output present is parsed out
  - The dom is also parsed to get corresponding div element to take screenshot of the first output

* fixed bug

* Addressed PR comments

- FIxed bug that didn't capture screenshot when mutiple notebook tabs are opened

* removed unnecessary dependencies

* fixed compile issues

* more edits
2020-07-23 00:43:53 -07:00
438 changed files with 30030 additions and 53165 deletions

View File

@@ -4,3 +4,4 @@ PORTAL_RUNNER_PASSWORD=
PORTAL_RUNNER_SUBSCRIPTION=
PORTAL_RUNNER_RESOURCE_GROUP=
PORTAL_RUNNER_DATABASE_ACCOUNT=
PORTAL_RUNNER_CONNECTION_STRING=

View File

@@ -266,10 +266,6 @@ src/ResourceProvider/ResourceProviderClientFactory.ts
src/RouteHandlers/RouteHandler.ts
src/RouteHandlers/TabRouteHandler.test.ts
src/RouteHandlers/TabRouteHandler.ts
src/Shared/AddCollectionUtility.test.ts
src/Shared/AddCollectionUtility.ts
src/Shared/AddDatabaseUtility.test.ts
src/Shared/AddDatabaseUtility.ts
src/Shared/Constants.ts
src/Shared/DefaultExperienceUtility.test.ts
src/Shared/DefaultExperienceUtility.ts
@@ -279,8 +275,6 @@ src/Shared/StorageUtility.test.ts
src/Shared/StorageUtility.ts
src/Shared/StringUtility.test.ts
src/Shared/StringUtility.ts
src/Shared/Telemetry/TelemetryConstants.ts
src/Shared/Telemetry/TelemetryProcessor.ts
src/Shared/appInsights.ts
src/SparkClusterManager/ArcadiaResourceManager.ts
src/SparkClusterManager/SparkClusterManager.ts
@@ -298,11 +292,9 @@ src/Utils/DatabaseAccountUtils.ts
src/Utils/JunoUtils.ts
src/Utils/MessageValidation.ts
src/Utils/NotebookConfigurationUtils.ts
src/Utils/NotificationConsoleUtils.ts
src/Utils/OfferUtils.test.ts
src/Utils/OfferUtils.ts
src/Utils/PricingUtils.test.ts
src/Utils/PricingUtils.ts
src/Utils/QueryUtils.test.ts
src/Utils/QueryUtils.ts
src/Utils/StringUtils.test.ts
@@ -420,6 +412,5 @@ cypress/integration/dataexplorer/SQL/addCollection.spec.ts
cypress/integration/dataexplorer/TABLE/addCollection.spec.ts
cypress/integration/notebook/newNotebook.spec.ts
cypress/integration/notebook/resourceTree.spec.ts
__mocks__/AddDatabaseUtility.ts
__mocks__/monaco-editor.ts
src/Explorer/Tree/ResourceTreeAdapterForResourceToken.test.tsx

View File

@@ -3,7 +3,7 @@ module.exports = {
browser: true,
es6: true
},
plugins: ["@typescript-eslint", "no-null"],
plugins: ["@typescript-eslint", "no-null", "prefer-arrow"],
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
globals: {
Atomics: "readonly",
@@ -27,7 +27,7 @@ module.exports = {
plugins: ["react"]
},
{
files: ["**/*.test.{ts,tsx}"],
files: ["**/*.{test,spec}.{ts,tsx}"],
env: {
jest: true
},
@@ -40,6 +40,15 @@ module.exports = {
"@typescript-eslint/no-unused-vars": "error",
"@typescript-eslint/no-extraneous-class": "error",
"no-null/no-null": "error",
"@typescript-eslint/no-explicit-any": "error"
"@typescript-eslint/no-explicit-any": "error",
"prefer-arrow/prefer-arrow-functions": ["error", { allowStandaloneDeclarations: true }],
eqeqeq: "error",
"no-restricted-syntax": [
"error",
{
selector: "CallExpression[callee.object.name='JSON'][callee.property.name='stringify'] Identifier[name=/$err/]",
message: "Do not use JSON.stringify(error). It will print '{}'"
}
]
}
};

2
.github/CODEOWNERS vendored
View File

@@ -1 +1 @@
* @Azure/cosmos-explorer-owners
* @Azure/cosmos-explorer-owners @Azure/azure-cosmos-explorer-developers

View File

@@ -1,9 +1,13 @@
name: CI
on:
push:
branches: [master]
branches:
- master
- hotfix/**
- release/**
pull_request:
branches: [master]
branches:
- master
jobs:
compile:
runs-on: ubuntu-latest
@@ -52,6 +56,7 @@ jobs:
- run: npm run test
build:
runs-on: ubuntu-latest
needs: [lint, format, compile, unittest]
name: "Build"
steps:
- uses: actions/checkout@v2
@@ -75,6 +80,7 @@ jobs:
path: dist/
endtoendemulator:
name: "End To End Tests | Emulator | SQL"
needs: [lint, format, compile, unittest]
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
@@ -99,64 +105,9 @@ jobs:
EMULATOR_ENDPOINT: https://0.0.0.0:8081/
NODE_TLS_REJECT_UNAUTHORIZED: 0
CYPRESS_CACHE_FOLDER: ~/.cache/Cypress
endtoendsql:
name: "End To End Tests | SQL"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Use Node.js 12.x
uses: actions/setup-node@v1
with:
node-version: 12.x
- name: Restore Cypress Binary Cache
uses: actions/cache@v2
with:
path: ~/.cache/Cypress
key: ${{ runner.os }}-cypress-binary-cache
- run: npm ci
- name: End to End Tests
run: |
npm start &
cd cypress
npm ci
node cleanup.js
npm run wait-for-server
npx cypress run --browser chrome --headless --spec "./integration/dataexplorer/SQL/*"
shell: bash
env:
NODE_TLS_REJECT_UNAUTHORIZED: 0
CYPRESS_CACHE_FOLDER: ~/.cache/Cypress
CYPRESS_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_SQL }}
endtoendmongo:
name: "End To End Tests | Mongo"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Use Node.js 12.x
uses: actions/setup-node@v1
with:
node-version: 12.x
- name: Restore Cypress Binary Cache
uses: actions/cache@v2
with:
path: ~/.cache/Cypress
key: ${{ runner.os }}-cypress-binary-cache
- name: End to End Tests
run: |
npm ci
npm start &
cd cypress
npm ci
node cleanup.js
npm run wait-for-server
npx cypress run --browser chrome --headless --spec "./integration/dataexplorer/MONGO/*"
shell: bash
env:
NODE_TLS_REJECT_UNAUTHORIZED: 0
CYPRESS_CACHE_FOLDER: ~/.cache/Cypress
CYPRESS_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_MONGO }}
accessibility:
name: "Accessibility | Hosted"
needs: [lint, format, compile, unittest]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
@@ -177,10 +128,32 @@ jobs:
shell: bash
env:
NODE_TLS_REJECT_UNAUTHORIZED: 0
endtoendpuppeteer:
name: "End to end puppeteer tests"
needs: [lint, format, compile, unittest]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Use Node.js 12.x
uses: actions/setup-node@v1
with:
node-version: 12.x
- name: End to End Puppeteer Tests
run: |
npm ci
npm start &
npm run wait-for-server
npm run test:e2e
shell: bash
env:
NODE_TLS_REJECT_UNAUTHORIZED: 0
PORTAL_RUNNER_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_SQL }}
MONGO_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_MONGO }}
CASSANDRA_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_CASSANDRA }}
nuget:
name: Publish Nuget
if: github.ref == 'refs/heads/master'
needs: [lint, format, compile, build, unittest, endtoendemulator, endtoendsql, endtoendmongo]
if: github.ref == 'refs/heads/master' || contains(github.ref, 'hotfix/') || contains(github.ref, 'release/')
needs: [lint, format, compile, build, unittest, endtoendemulator, endtoendpuppeteer]
runs-on: ubuntu-latest
env:
NUGET_SOURCE: ${{ secrets.NUGET_SOURCE }}
@@ -203,8 +176,8 @@ jobs:
path: "*.nupkg"
nugetmpac:
name: Publish Nuget MPAC
if: github.ref == 'refs/heads/master'
needs: [lint, format, compile, build, unittest, endtoendemulator, endtoendsql, endtoendmongo]
if: github.ref == 'refs/heads/master' || contains(github.ref, 'hotfix/') || contains(github.ref, 'release/')
needs: [lint, format, compile, build, unittest, endtoendemulator, endtoendpuppeteer]
runs-on: ubuntu-latest
env:
NUGET_SOURCE: ${{ secrets.NUGET_SOURCE }}

View File

@@ -1,4 +1,4 @@
# CosmosDB Explorer
# Cosmos DB Explorer
UI for Azure Cosmos DB. Powers the [Azure Portal](https://portal.azure.com/), https://cosmos.azure.com/, and the [Cosmos DB Emulator](https://docs.microsoft.com/en-us/azure/cosmos-db/local-emulator)

View File

@@ -1,5 +0,0 @@
export class AddDbUtilities {
createGremlinDatabase(params: any) {
return Promise.resolve(1)
}
}

View File

@@ -7,7 +7,7 @@ trigger:
- master
pool:
vmImage: 'ubuntu-latest'
vmImage: "ubuntu-latest"
steps:
- task: ComponentGovernanceComponentDetection@0
- task: ComponentGovernanceComponentDetection@0

View File

@@ -3,7 +3,7 @@
"pluginsFile": false,
"fixturesFolder": false,
"supportFile": "./support/index.js",
"defaultCommandTimeout": 60000,
"defaultCommandTimeout": 90000,
"chromeWebSecurity": false,
"reporter": "mochawesome",
"reporterOptions": {

View File

@@ -6,8 +6,8 @@
"scripts": {
"test": "cypress run",
"wait-for-server": "wait-on -t 240000 -i 5000 -v https-get://0.0.0.0:1234/",
"test:sql": "cypress run --browser chrome --headless --spec \"./integration/dataexplorer/SQL/*\"",
"test:ci": "wait-on -t 240000 -i 5000 -v https-get://0.0.0.0:1234/ https-get://0.0.0.0:8081/_explorer/index.html && cypress run --browser chrome --headless",
"test:sql": "cypress run --browser chrome --spec \"./integration/dataexplorer/SQL/*\"",
"test:ci": "wait-on -t 240000 -i 5000 -v https-get://0.0.0.0:1234/ https-get://0.0.0.0:8081/_explorer/index.html && cypress run --browser edge --headless",
"test:debug": "cypress open"
},
"devDependencies": {

View File

@@ -1,177 +0,0 @@
/*!
* jQuery contextMenu - Plugin for simple contextMenu handling
*
* Version: 1.6.6
*
* Authors: Rodney Rehm, Addy Osmani (patches for FF)
* Web: http://medialize.github.com/jQuery-contextMenu/
*
* Licensed under
* MIT License http://www.opensource.org/licenses/mit-license
* GPL v3 http://opensource.org/licenses/GPL-3.0
*
*/
.context-menu-list {
z-index: 1001;
position: fixed;
background: white;
border: solid 1px gainsboro;
box-shadow: 4px 4px 4px -2px #888888;
padding: 8px 0px 8px 0px;
line-height: 25px;
width: 254px;
list-style: none;
margin-left: -10px;
outline: 0px #fff;
}
.context-menu-item {
padding: 2px 2px 2px 31px;
background-color: #fff;
position: relative;
-webkit-user-select: none;
-moz-user-select: -moz-none;
-ms-user-select: none;
user-select: none;
}
.context-menu-separator {
padding-bottom: 0;
border-bottom: 1px solid #DDD;
}
.context-menu-item>label>input,
.context-menu-item>label>textarea {
-webkit-user-select: text;
-moz-user-select: text;
-ms-user-select: text;
user-select: text;
margin-left: -10px;
}
.context-menu-item:hover {
cursor: pointer;
background-color: #eeeeee;
}
.context-menu-item.disabled {
color: #666;
}
.context-menu-input.hover,
.context-menu-item.disabled.hover {
cursor: default;
background-color: #EEE;
}
.context-menu-submenu:after {
content: ">";
color: #666;
position: absolute;
top: 0;
right: 3px;
z-index: 1;
}
/* icons
#protip:
In case you want to use sprites for icons (which I would suggest you do) have a look at
http://css-tricks.com/13224-pseudo-spriting/ to get an idea of how to implement
.context-menu-item.icon:before {}
*/
.context-menu-item.icon {
min-height: 18px;
background-repeat: no-repeat;
background-position: 10px 7px;
}
.context-menu-item.icon:hover {
min-height: 18px;
background-repeat: no-repeat;
background-position: 10px 7px;
}
/*.context-menu-item.icon-edit {
background-image: url(images/page_white_edit.png);
}
.context-menu-item.icon-cut {
background-image: url(images/cut.png);
}
.context-menu-item.icon-copy {
background-image: url(images/page_white_copy.png);
}
.context-menu-item.icon-paste {
background-image: url(images/page_white_paste.png);
}
.context-menu-item.icon-delete {
background-image: url(images/page_white_delete.png);
}
.context-menu-item.icon-add {
background-image: url(images/page_white_add.png);
}
.context-menu-item.icon-quit {
background-image: url(images/door.png);
}*/
/* vertically align inside labels */
.context-menu-input>label>* {
vertical-align: top;
}
/* position checkboxes and radios as icons */
.context-menu-input>label>input[type="checkbox"],
.context-menu-input>label>input[type="radio"] {
margin-left: -17px;
}
.context-menu-input>label>span {
margin-left: 5px;
}
.context-menu-input>label,
.context-menu-input>label>input[type="text"],
.context-menu-input>label>textarea,
.context-menu-input>label>select {
display: block;
width: 100%;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
-ms-box-sizing: border-box;
-o-box-sizing: border-box;
box-sizing: border-box;
}
.context-menu-input>label>textarea {
height: 100px;
}
.context-menu-item>.context-menu-list {
display: none;
/* re-positioned by js */
right: -5px;
top: 5px;
}
/*.context-menu-item.hover>.context-menu-list {
display: block;
padding-left: 5px;
}*/
.context-menu-accesskey {
text-decoration: underline;
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,163 +0,0 @@
/*!
DataTables 1.10.9
©2008-2015 SpryMedia Ltd - datatables.net/license
*/
(function(Fa,T,k){var S=function(h){function X(a){var b,c,d={};h.each(a,function(e){if((b=e.match(/^([^A-Z]+?)([A-Z])/))&&-1!=="a aa ai ao as b fn i m o s ".indexOf(b[1]+" "))c=e.replace(b[0],b[2].toLowerCase()),d[c]=e,"o"===b[1]&&X(a[e])});a._hungarianMap=d}function I(a,b,c){a._hungarianMap||X(a);var d;h.each(b,function(e){d=a._hungarianMap[e];if(d!==k&&(c||b[d]===k))"o"===d.charAt(0)?(b[d]||(b[d]={}),h.extend(!0,b[d],b[e]),I(a[d],b[d],c)):b[d]=b[e]})}function S(a){var b=m.defaults.oLanguage,c=a.sZeroRecords;
!a.sEmptyTable&&(c&&"No data available in table"===b.sEmptyTable)&&F(a,a,"sZeroRecords","sEmptyTable");!a.sLoadingRecords&&(c&&"Loading..."===b.sLoadingRecords)&&F(a,a,"sZeroRecords","sLoadingRecords");a.sInfoThousands&&(a.sThousands=a.sInfoThousands);(a=a.sDecimal)&&cb(a)}function db(a){A(a,"ordering","bSort");A(a,"orderMulti","bSortMulti");A(a,"orderClasses","bSortClasses");A(a,"orderCellsTop","bSortCellsTop");A(a,"order","aaSorting");A(a,"orderFixed","aaSortingFixed");A(a,"paging","bPaginate");
A(a,"pagingType","sPaginationType");A(a,"pageLength","iDisplayLength");A(a,"searching","bFilter");"boolean"===typeof a.sScrollX&&(a.sScrollX=a.sScrollX?"100%":"");if(a=a.aoSearchCols)for(var b=0,c=a.length;b<c;b++)a[b]&&I(m.models.oSearch,a[b])}function eb(a){A(a,"orderable","bSortable");A(a,"orderData","aDataSort");A(a,"orderSequence","asSorting");A(a,"orderDataType","sortDataType");var b=a.aDataSort;b&&!h.isArray(b)&&(a.aDataSort=[b])}function fb(a){if(!m.__browser){var b={};m.__browser=b;var c=
h("<div/>").css({position:"fixed",top:0,left:0,height:1,width:1,overflow:"hidden"}).append(h("<div/>").css({position:"absolute",top:1,left:1,width:100,overflow:"scroll"}).append(h("<div/>").css({width:"100%",height:10}))).appendTo("body"),d=c.children(),e=d.children();b.barWidth=d[0].offsetWidth-d[0].clientWidth;b.bScrollOversize=100===e[0].offsetWidth&&100!==d[0].clientWidth;b.bScrollbarLeft=1!==Math.round(e.offset().left);b.bBounding=c[0].getBoundingClientRect().width?!0:!1;c.remove()}h.extend(a.oBrowser,
m.__browser);a.oScroll.iBarWidth=m.__browser.barWidth}function gb(a,b,c,d,e,f){var g,i=!1;c!==k&&(g=c,i=!0);for(;d!==e;)a.hasOwnProperty(d)&&(g=i?b(g,a[d],d,a):a[d],i=!0,d+=f);return g}function Ga(a,b){var c=m.defaults.column,d=a.aoColumns.length,c=h.extend({},m.models.oColumn,c,{nTh:b?b:T.createElement("th"),sTitle:c.sTitle?c.sTitle:b?b.innerHTML:"",aDataSort:c.aDataSort?c.aDataSort:[d],mData:c.mData?c.mData:d,idx:d});a.aoColumns.push(c);c=a.aoPreSearchCols;c[d]=h.extend({},m.models.oSearch,c[d]);
la(a,d,h(b).data())}function la(a,b,c){var b=a.aoColumns[b],d=a.oClasses,e=h(b.nTh);if(!b.sWidthOrig){b.sWidthOrig=e.attr("width")||null;var f=(e.attr("style")||"").match(/width:\s*(\d+[pxem%]+)/);f&&(b.sWidthOrig=f[1])}c!==k&&null!==c&&(eb(c),I(m.defaults.column,c),c.mDataProp!==k&&!c.mData&&(c.mData=c.mDataProp),c.sType&&(b._sManualType=c.sType),c.className&&!c.sClass&&(c.sClass=c.className),h.extend(b,c),F(b,c,"sWidth","sWidthOrig"),c.iDataSort!==k&&(b.aDataSort=[c.iDataSort]),F(b,c,"aDataSort"));
var g=b.mData,i=P(g),j=b.mRender?P(b.mRender):null,c=function(a){return"string"===typeof a&&-1!==a.indexOf("@")};b._bAttrSrc=h.isPlainObject(g)&&(c(g.sort)||c(g.type)||c(g.filter));b.fnGetData=function(a,b,c){var d=i(a,b,k,c);return j&&b?j(d,b,a,c):d};b.fnSetData=function(a,b,c){return Q(g)(a,b,c)};"number"!==typeof g&&(a._rowReadObject=!0);a.oFeatures.bSort||(b.bSortable=!1,e.addClass(d.sSortableNone));a=-1!==h.inArray("asc",b.asSorting);c=-1!==h.inArray("desc",b.asSorting);!b.bSortable||!a&&!c?
(b.sSortingClass=d.sSortableNone,b.sSortingClassJUI=""):a&&!c?(b.sSortingClass=d.sSortableAsc,b.sSortingClassJUI=d.sSortJUIAscAllowed):!a&&c?(b.sSortingClass=d.sSortableDesc,b.sSortingClassJUI=d.sSortJUIDescAllowed):(b.sSortingClass=d.sSortable,b.sSortingClassJUI=d.sSortJUI)}function Y(a){if(!1!==a.oFeatures.bAutoWidth){var b=a.aoColumns;Ha(a);for(var c=0,d=b.length;c<d;c++)b[c].nTh.style.width=b[c].sWidth}b=a.oScroll;(""!==b.sY||""!==b.sX)&&Z(a);w(a,null,"column-sizing",[a])}function $(a,b){var c=
aa(a,"bVisible");return"number"===typeof c[b]?c[b]:null}function ba(a,b){var c=aa(a,"bVisible"),c=h.inArray(b,c);return-1!==c?c:null}function ca(a){return aa(a,"bVisible").length}function aa(a,b){var c=[];h.map(a.aoColumns,function(a,e){a[b]&&c.push(e)});return c}function Ia(a){var b=a.aoColumns,c=a.aoData,d=m.ext.type.detect,e,f,g,i,j,h,l,r,q;e=0;for(f=b.length;e<f;e++)if(l=b[e],q=[],!l.sType&&l._sManualType)l.sType=l._sManualType;else if(!l.sType){g=0;for(i=d.length;g<i;g++){j=0;for(h=c.length;j<
h;j++){q[j]===k&&(q[j]=B(a,j,e,"type"));r=d[g](q[j],a);if(!r&&g!==d.length-1)break;if("html"===r)break}if(r){l.sType=r;break}}l.sType||(l.sType="string")}}function hb(a,b,c,d){var e,f,g,i,j,n,l=a.aoColumns;if(b)for(e=b.length-1;0<=e;e--){n=b[e];var r=n.targets!==k?n.targets:n.aTargets;h.isArray(r)||(r=[r]);f=0;for(g=r.length;f<g;f++)if("number"===typeof r[f]&&0<=r[f]){for(;l.length<=r[f];)Ga(a);d(r[f],n)}else if("number"===typeof r[f]&&0>r[f])d(l.length+r[f],n);else if("string"===typeof r[f]){i=0;
for(j=l.length;i<j;i++)("_all"==r[f]||h(l[i].nTh).hasClass(r[f]))&&d(i,n)}}if(c){e=0;for(a=c.length;e<a;e++)d(e,c[e])}}function L(a,b,c,d){var e=a.aoData.length,f=h.extend(!0,{},m.models.oRow,{src:c?"dom":"data",idx:e});f._aData=b;a.aoData.push(f);for(var g=a.aoColumns,i=0,j=g.length;i<j;i++)g[i].sType=null;a.aiDisplayMaster.push(e);b=a.rowIdFn(b);b!==k&&(a.aIds[b]=f);(c||!a.oFeatures.bDeferRender)&&Ja(a,e,c,d);return e}function ma(a,b){var c;b instanceof h||(b=h(b));return b.map(function(b,e){c=
Ka(a,e);return L(a,c.data,e,c.cells)})}function B(a,b,c,d){var e=a.iDraw,f=a.aoColumns[c],g=a.aoData[b]._aData,i=f.sDefaultContent,c=f.fnGetData(g,d,{settings:a,row:b,col:c});if(c===k)return a.iDrawError!=e&&null===i&&(J(a,0,"Requested unknown parameter "+("function"==typeof f.mData?"{function}":"'"+f.mData+"'")+" for row "+b,4),a.iDrawError=e),i;if((c===g||null===c)&&null!==i)c=i;else if("function"===typeof c)return c.call(g);return null===c&&"display"==d?"":c}function ib(a,b,c,d){a.aoColumns[c].fnSetData(a.aoData[b]._aData,
d,{settings:a,row:b,col:c})}function La(a){return h.map(a.match(/(\\.|[^\.])+/g)||[""],function(a){return a.replace(/\\./g,".")})}function P(a){if(h.isPlainObject(a)){var b={};h.each(a,function(a,c){c&&(b[a]=P(c))});return function(a,c,f,g){var i=b[c]||b._;return i!==k?i(a,c,f,g):a}}if(null===a)return function(a){return a};if("function"===typeof a)return function(b,c,f,g){return a(b,c,f,g)};if("string"===typeof a&&(-1!==a.indexOf(".")||-1!==a.indexOf("[")||-1!==a.indexOf("("))){var c=function(a,b,
f){var g,i;if(""!==f){i=La(f);for(var j=0,n=i.length;j<n;j++){f=i[j].match(da);g=i[j].match(U);if(f){i[j]=i[j].replace(da,"");""!==i[j]&&(a=a[i[j]]);g=[];i.splice(0,j+1);i=i.join(".");if(h.isArray(a)){j=0;for(n=a.length;j<n;j++)g.push(c(a[j],b,i))}a=f[0].substring(1,f[0].length-1);a=""===a?g:g.join(a);break}else if(g){i[j]=i[j].replace(U,"");a=a[i[j]]();continue}if(null===a||a[i[j]]===k)return k;a=a[i[j]]}}return a};return function(b,e){return c(b,e,a)}}return function(b){return b[a]}}function Q(a){if(h.isPlainObject(a))return Q(a._);
if(null===a)return function(){};if("function"===typeof a)return function(b,d,e){a(b,"set",d,e)};if("string"===typeof a&&(-1!==a.indexOf(".")||-1!==a.indexOf("[")||-1!==a.indexOf("("))){var b=function(a,d,e){var e=La(e),f;f=e[e.length-1];for(var g,i,j=0,n=e.length-1;j<n;j++){g=e[j].match(da);i=e[j].match(U);if(g){e[j]=e[j].replace(da,"");a[e[j]]=[];f=e.slice();f.splice(0,j+1);g=f.join(".");if(h.isArray(d)){i=0;for(n=d.length;i<n;i++)f={},b(f,d[i],g),a[e[j]].push(f)}else a[e[j]]=d;return}i&&(e[j]=e[j].replace(U,
""),a=a[e[j]](d));if(null===a[e[j]]||a[e[j]]===k)a[e[j]]={};a=a[e[j]]}if(f.match(U))a[f.replace(U,"")](d);else a[f.replace(da,"")]=d};return function(c,d){return b(c,d,a)}}return function(b,d){b[a]=d}}function Ma(a){return D(a.aoData,"_aData")}function na(a){a.aoData.length=0;a.aiDisplayMaster.length=0;a.aiDisplay.length=0;a.aIds={}}function oa(a,b,c){for(var d=-1,e=0,f=a.length;e<f;e++)a[e]==b?d=e:a[e]>b&&a[e]--; -1!=d&&c===k&&a.splice(d,1)}function ea(a,b,c,d){var e=a.aoData[b],f,g=function(c,d){for(;c.childNodes.length;)c.removeChild(c.firstChild);
c.innerHTML=B(a,b,d,"display")};if("dom"===c||(!c||"auto"===c)&&"dom"===e.src)e._aData=Ka(a,e,d,d===k?k:e._aData).data;else{var i=e.anCells;if(i)if(d!==k)g(i[d],d);else{c=0;for(f=i.length;c<f;c++)g(i[c],c)}}e._aSortData=null;e._aFilterData=null;g=a.aoColumns;if(d!==k)g[d].sType=null;else{c=0;for(f=g.length;c<f;c++)g[c].sType=null;Na(a,e)}}function Ka(a,b,c,d){var e=[],f=b.firstChild,g,i,j=0,n,l=a.aoColumns,r=a._rowReadObject,d=d!==k?d:r?{}:[],q=function(a,b){if("string"===typeof a){var c=a.indexOf("@");
-1!==c&&(c=a.substring(c+1),Q(a)(d,b.getAttribute(c)))}},jb=function(a){if(c===k||c===j)i=l[j],n=h.trim(a.innerHTML),i&&i._bAttrSrc?(Q(i.mData._)(d,n),q(i.mData.sort,a),q(i.mData.type,a),q(i.mData.filter,a)):r?(i._setter||(i._setter=Q(i.mData)),i._setter(d,n)):d[j]=n;j++};if(f)for(;f;){g=f.nodeName.toUpperCase();if("TD"==g||"TH"==g)jb(f),e.push(f);f=f.nextSibling}else{e=b.anCells;g=0;for(var o=e.length;g<o;g++)jb(e[g])}if(b=f?b:b.nTr)(b=b.getAttribute("id"))&&Q(a.rowId)(d,b);return{data:d,cells:e}}
function Ja(a,b,c,d){var e=a.aoData[b],f=e._aData,g=[],i,j,h,l,r;if(null===e.nTr){i=c||T.createElement("tr");e.nTr=i;e.anCells=g;i._DT_RowIndex=b;Na(a,e);l=0;for(r=a.aoColumns.length;l<r;l++){h=a.aoColumns[l];j=c?d[l]:T.createElement(h.sCellType);g.push(j);if(!c||h.mRender||h.mData!==l)j.innerHTML=B(a,b,l,"display");h.sClass&&(j.className+=" "+h.sClass);h.bVisible&&!c?i.appendChild(j):!h.bVisible&&c&&j.parentNode.removeChild(j);h.fnCreatedCell&&h.fnCreatedCell.call(a.oInstance,j,B(a,b,l),f,b,l)}w(a,
"aoRowCreatedCallback",null,[i,f,b])}e.nTr.setAttribute("role","row")}function Na(a,b){var c=b.nTr,d=b._aData;if(c){var e=a.rowIdFn(d);e&&(c.id=e);d.DT_RowClass&&(e=d.DT_RowClass.split(" "),b.__rowc=b.__rowc?pa(b.__rowc.concat(e)):e,h(c).removeClass(b.__rowc.join(" ")).addClass(d.DT_RowClass));d.DT_RowAttr&&h(c).attr(d.DT_RowAttr);d.DT_RowData&&h(c).data(d.DT_RowData)}}function kb(a){var b,c,d,e,f,g=a.nTHead,i=a.nTFoot,j=0===h("th, td",g).length,n=a.oClasses,l=a.aoColumns;j&&(e=h("<tr/>").appendTo(g));
b=0;for(c=l.length;b<c;b++)f=l[b],d=h(f.nTh).addClass(f.sClass),j&&d.appendTo(e),a.oFeatures.bSort&&(d.addClass(f.sSortingClass),!1!==f.bSortable&&(d.attr("tabindex",a.iTabIndex).attr("aria-controls",a.sTableId),Oa(a,f.nTh,b))),f.sTitle!=d[0].innerHTML&&d.html(f.sTitle),Pa(a,"header")(a,d,f,n);j&&fa(a.aoHeader,g);h(g).find(">tr").attr("role","row");h(g).find(">tr>th, >tr>td").addClass(n.sHeaderTH);h(i).find(">tr>th, >tr>td").addClass(n.sFooterTH);if(null!==i){a=a.aoFooter[0];b=0;for(c=a.length;b<
c;b++)f=l[b],f.nTf=a[b].cell,f.sClass&&h(f.nTf).addClass(f.sClass)}}function ga(a,b,c){var d,e,f,g=[],i=[],j=a.aoColumns.length,n;if(b){c===k&&(c=!1);d=0;for(e=b.length;d<e;d++){g[d]=b[d].slice();g[d].nTr=b[d].nTr;for(f=j-1;0<=f;f--)!a.aoColumns[f].bVisible&&!c&&g[d].splice(f,1);i.push([])}d=0;for(e=g.length;d<e;d++){if(a=g[d].nTr)for(;f=a.firstChild;)a.removeChild(f);f=0;for(b=g[d].length;f<b;f++)if(n=j=1,i[d][f]===k){a.appendChild(g[d][f].cell);for(i[d][f]=1;g[d+j]!==k&&g[d][f].cell==g[d+j][f].cell;)i[d+
j][f]=1,j++;for(;g[d][f+n]!==k&&g[d][f].cell==g[d][f+n].cell;){for(c=0;c<j;c++)i[d+c][f+n]=1;n++}h(g[d][f].cell).attr("rowspan",j).attr("colspan",n)}}}}function M(a){var b=w(a,"aoPreDrawCallback","preDraw",[a]);if(-1!==h.inArray(!1,b))C(a,!1);else{var b=[],c=0,d=a.asStripeClasses,e=d.length,f=a.oLanguage,g=a.iInitDisplayStart,i="ssp"==y(a),j=a.aiDisplay;a.bDrawing=!0;g!==k&&-1!==g&&(a._iDisplayStart=i?g:g>=a.fnRecordsDisplay()?0:g,a.iInitDisplayStart=-1);var g=a._iDisplayStart,n=a.fnDisplayEnd();
if(a.bDeferLoading)a.bDeferLoading=!1,a.iDraw++,C(a,!1);else if(i){if(!a.bDestroying&&!lb(a))return}else a.iDraw++;if(0!==j.length){f=i?a.aoData.length:n;for(i=i?0:g;i<f;i++){var l=j[i],r=a.aoData[l];null===r.nTr&&Ja(a,l);l=r.nTr;if(0!==e){var q=d[c%e];r._sRowStripe!=q&&(h(l).removeClass(r._sRowStripe).addClass(q),r._sRowStripe=q)}w(a,"aoRowCallback",null,[l,r._aData,c,i]);b.push(l);c++}}else c=f.sZeroRecords,1==a.iDraw&&"ajax"==y(a)?c=f.sLoadingRecords:f.sEmptyTable&&0===a.fnRecordsTotal()&&(c=f.sEmptyTable),
b[0]=h("<tr/>",{"class":e?d[0]:""}).append(h("<td />",{valign:"top",colSpan:ca(a),"class":a.oClasses.sRowEmpty}).html(c))[0];w(a,"aoHeaderCallback","header",[h(a.nTHead).children("tr")[0],Ma(a),g,n,j]);w(a,"aoFooterCallback","footer",[h(a.nTFoot).children("tr")[0],Ma(a),g,n,j]);d=h(a.nTBody);d.children().detach();d.append(h(b));w(a,"aoDrawCallback","draw",[a]);a.bSorted=!1;a.bFiltered=!1;a.bDrawing=!1}}function R(a,b){var c=a.oFeatures,d=c.bFilter;c.bSort&&mb(a);d?ha(a,a.oPreviousSearch):a.aiDisplay=
a.aiDisplayMaster.slice();!0!==b&&(a._iDisplayStart=0);a._drawHold=b;M(a);a._drawHold=!1}function nb(a){var b=a.oClasses,c=h(a.nTable),c=h("<div/>").insertBefore(c),d=a.oFeatures,e=h("<div/>",{id:a.sTableId+"_wrapper","class":b.sWrapper+(a.nTFoot?"":" "+b.sNoFooter)});a.nHolding=c[0];a.nTableWrapper=e[0];a.nTableReinsertBefore=a.nTable.nextSibling;for(var f=a.sDom.split(""),g,i,j,n,l,r,q=0;q<f.length;q++){g=null;i=f[q];if("<"==i){j=h("<div/>")[0];n=f[q+1];if("'"==n||'"'==n){l="";for(r=2;f[q+r]!=n;)l+=
f[q+r],r++;"H"==l?l=b.sJUIHeader:"F"==l&&(l=b.sJUIFooter);-1!=l.indexOf(".")?(n=l.split("."),j.id=n[0].substr(1,n[0].length-1),j.className=n[1]):"#"==l.charAt(0)?j.id=l.substr(1,l.length-1):j.className=l;q+=r}e.append(j);e=h(j)}else if(">"==i)e=e.parent();else if("l"==i&&d.bPaginate&&d.bLengthChange)g=ob(a);else if("f"==i&&d.bFilter)g=pb(a);else if("r"==i&&d.bProcessing)g=qb(a);else if("t"==i)g=rb(a);else if("i"==i&&d.bInfo)g=sb(a);else if("p"==i&&d.bPaginate)g=tb(a);else if(0!==m.ext.feature.length){j=
m.ext.feature;r=0;for(n=j.length;r<n;r++)if(i==j[r].cFeature){g=j[r].fnInit(a);break}}g&&(j=a.aanFeatures,j[i]||(j[i]=[]),j[i].push(g),e.append(g))}c.replaceWith(e);a.nHolding=null}function fa(a,b){var c=h(b).children("tr"),d,e,f,g,i,j,n,l,r,q;a.splice(0,a.length);f=0;for(j=c.length;f<j;f++)a.push([]);f=0;for(j=c.length;f<j;f++){d=c[f];for(e=d.firstChild;e;){if("TD"==e.nodeName.toUpperCase()||"TH"==e.nodeName.toUpperCase()){l=1*e.getAttribute("colspan");r=1*e.getAttribute("rowspan");l=!l||0===l||
1===l?1:l;r=!r||0===r||1===r?1:r;g=0;for(i=a[f];i[g];)g++;n=g;q=1===l?!0:!1;for(i=0;i<l;i++)for(g=0;g<r;g++)a[f+g][n+i]={cell:e,unique:q},a[f+g].nTr=d}e=e.nextSibling}}}function qa(a,b,c){var d=[];c||(c=a.aoHeader,b&&(c=[],fa(c,b)));for(var b=0,e=c.length;b<e;b++)for(var f=0,g=c[b].length;f<g;f++)if(c[b][f].unique&&(!d[f]||!a.bSortCellsTop))d[f]=c[b][f].cell;return d}function ra(a,b,c){w(a,"aoServerParams","serverParams",[b]);if(b&&h.isArray(b)){var d={},e=/(.*?)\[\]$/;h.each(b,function(a,b){var c=
b.name.match(e);c?(c=c[0],d[c]||(d[c]=[]),d[c].push(b.value)):d[b.name]=b.value});b=d}var f,g=a.ajax,i=a.oInstance,j=function(b){w(a,null,"xhr",[a,b,a.jqXHR]);c(b)};if(h.isPlainObject(g)&&g.data){f=g.data;var n=h.isFunction(f)?f(b,a):f,b=h.isFunction(f)&&n?n:h.extend(!0,b,n);delete g.data}n={data:b,success:function(b){var c=b.error||b.sError;c&&J(a,0,c);a.json=b;j(b)},dataType:"json",cache:!1,type:a.sServerMethod,error:function(b,c){var d=w(a,null,"xhr",[a,null,a.jqXHR]);-1===h.inArray(!0,d)&&("parsererror"==
c?J(a,0,"Invalid JSON response",1):4===b.readyState&&J(a,0,"Ajax error",7));C(a,!1)}};a.oAjaxData=b;w(a,null,"preXhr",[a,b]);a.fnServerData?a.fnServerData.call(i,a.sAjaxSource,h.map(b,function(a,b){return{name:b,value:a}}),j,a):a.sAjaxSource||"string"===typeof g?a.jqXHR=h.ajax(h.extend(n,{url:g||a.sAjaxSource})):h.isFunction(g)?a.jqXHR=g.call(i,b,j,a):(a.jqXHR=h.ajax(h.extend(n,g)),g.data=f)}function lb(a){return a.bAjaxDataGet?(a.iDraw++,C(a,!0),ra(a,ub(a),function(b){vb(a,b)}),!1):!0}function ub(a){var b=
a.aoColumns,c=b.length,d=a.oFeatures,e=a.oPreviousSearch,f=a.aoPreSearchCols,g,i=[],j,n,l,r=V(a);g=a._iDisplayStart;j=!1!==d.bPaginate?a._iDisplayLength:-1;var q=function(a,b){i.push({name:a,value:b})};q("sEcho",a.iDraw);q("iColumns",c);q("sColumns",D(b,"sName").join(","));q("iDisplayStart",g);q("iDisplayLength",j);var k={draw:a.iDraw,columns:[],order:[],start:g,length:j,search:{value:e.sSearch,regex:e.bRegex}};for(g=0;g<c;g++)n=b[g],l=f[g],j="function"==typeof n.mData?"function":n.mData,k.columns.push({data:j,
name:n.sName,searchable:n.bSearchable,orderable:n.bSortable,search:{value:l.sSearch,regex:l.bRegex}}),q("mDataProp_"+g,j),d.bFilter&&(q("sSearch_"+g,l.sSearch),q("bRegex_"+g,l.bRegex),q("bSearchable_"+g,n.bSearchable)),d.bSort&&q("bSortable_"+g,n.bSortable);d.bFilter&&(q("sSearch",e.sSearch),q("bRegex",e.bRegex));d.bSort&&(h.each(r,function(a,b){k.order.push({column:b.col,dir:b.dir});q("iSortCol_"+a,b.col);q("sSortDir_"+a,b.dir)}),q("iSortingCols",r.length));b=m.ext.legacy.ajax;return null===b?a.sAjaxSource?
i:k:b?i:k}function vb(a,b){var c=sa(a,b),d=b.sEcho!==k?b.sEcho:b.draw,e=b.iTotalRecords!==k?b.iTotalRecords:b.recordsTotal,f=b.iTotalDisplayRecords!==k?b.iTotalDisplayRecords:b.recordsFiltered;if(d){if(1*d<a.iDraw)return;a.iDraw=1*d}na(a);a._iRecordsTotal=parseInt(e,10);a._iRecordsDisplay=parseInt(f,10);d=0;for(e=c.length;d<e;d++)L(a,c[d]);a.aiDisplay=a.aiDisplayMaster.slice();a.bAjaxDataGet=!1;M(a);a._bInitComplete||ta(a,b);a.bAjaxDataGet=!0;C(a,!1)}function sa(a,b){var c=h.isPlainObject(a.ajax)&&
a.ajax.dataSrc!==k?a.ajax.dataSrc:a.sAjaxDataProp;return"data"===c?b.aaData||b[c]:""!==c?P(c)(b):b}function pb(a){var b=a.oClasses,c=a.sTableId,d=a.oLanguage,e=a.oPreviousSearch,f=a.aanFeatures,g='<input type="search" class="'+b.sFilterInput+'"/>',i=d.sSearch,i=i.match(/_INPUT_/)?i.replace("_INPUT_",g):i+g,b=h("<div/>",{id:!f.f?c+"_filter":null,"class":b.sFilter}).append(h("<label/>").append(i)),f=function(){var b=!this.value?"":this.value;b!=e.sSearch&&(ha(a,{sSearch:b,bRegex:e.bRegex,bSmart:e.bSmart,
bCaseInsensitive:e.bCaseInsensitive}),a._iDisplayStart=0,M(a))},g=null!==a.searchDelay?a.searchDelay:"ssp"===y(a)?400:0,j=h("input",b).val(e.sSearch).attr("placeholder",d.sSearchPlaceholder).bind("keyup.DT search.DT input.DT paste.DT cut.DT",g?ua(f,g):f).bind("keypress.DT",function(a){if(13==a.keyCode)return!1}).attr("aria-controls",c);h(a.nTable).on("search.dt.DT",function(b,c){if(a===c)try{j[0]!==T.activeElement&&j.val(e.sSearch)}catch(d){}});return b[0]}function ha(a,b,c){var d=a.oPreviousSearch,
e=a.aoPreSearchCols,f=function(a){d.sSearch=a.sSearch;d.bRegex=a.bRegex;d.bSmart=a.bSmart;d.bCaseInsensitive=a.bCaseInsensitive};Ia(a);if("ssp"!=y(a)){wb(a,b.sSearch,c,b.bEscapeRegex!==k?!b.bEscapeRegex:b.bRegex,b.bSmart,b.bCaseInsensitive);f(b);for(b=0;b<e.length;b++)xb(a,e[b].sSearch,b,e[b].bEscapeRegex!==k?!e[b].bEscapeRegex:e[b].bRegex,e[b].bSmart,e[b].bCaseInsensitive);yb(a)}else f(b);a.bFiltered=!0;w(a,null,"search",[a])}function yb(a){for(var b=m.ext.search,c=a.aiDisplay,d,e,f=0,g=b.length;f<
g;f++){for(var i=[],j=0,n=c.length;j<n;j++)e=c[j],d=a.aoData[e],b[f](a,d._aFilterData,e,d._aData,j)&&i.push(e);c.length=0;h.merge(c,i)}}function xb(a,b,c,d,e,f){if(""!==b)for(var g=a.aiDisplay,d=Qa(b,d,e,f),e=g.length-1;0<=e;e--)b=a.aoData[g[e]]._aFilterData[c],d.test(b)||g.splice(e,1)}function wb(a,b,c,d,e,f){var d=Qa(b,d,e,f),e=a.oPreviousSearch.sSearch,f=a.aiDisplayMaster,g;0!==m.ext.search.length&&(c=!0);g=zb(a);if(0>=b.length)a.aiDisplay=f.slice();else{if(g||c||e.length>b.length||0!==b.indexOf(e)||
a.bSorted)a.aiDisplay=f.slice();b=a.aiDisplay;for(c=b.length-1;0<=c;c--)d.test(a.aoData[b[c]]._sFilterRow)||b.splice(c,1)}}function Qa(a,b,c,d){a=b?a:va(a);c&&(a="^(?=.*?"+h.map(a.match(/"[^"]+"|[^ ]+/g)||[""],function(a){if('"'===a.charAt(0))var b=a.match(/^"(.*)"$/),a=b?b[1]:a;return a.replace('"',"")}).join(")(?=.*?")+").*$");return RegExp(a,d?"i":"")}function va(a){return a.replace(Yb,"\\$1")}function zb(a){var b=a.aoColumns,c,d,e,f,g,i,j,h,l=m.ext.type.search;c=!1;d=0;for(f=a.aoData.length;d<
f;d++)if(h=a.aoData[d],!h._aFilterData){i=[];e=0;for(g=b.length;e<g;e++)c=b[e],c.bSearchable?(j=B(a,d,e,"filter"),l[c.sType]&&(j=l[c.sType](j)),null===j&&(j=""),"string"!==typeof j&&j.toString&&(j=j.toString())):j="",j.indexOf&&-1!==j.indexOf("&")&&(wa.innerHTML=j,j=Zb?wa.textContent:wa.innerText),j.replace&&(j=j.replace(/[\r\n]/g,"")),i.push(j);h._aFilterData=i;h._sFilterRow=i.join(" ");c=!0}return c}function Ab(a){return{search:a.sSearch,smart:a.bSmart,regex:a.bRegex,caseInsensitive:a.bCaseInsensitive}}
function Bb(a){return{sSearch:a.search,bSmart:a.smart,bRegex:a.regex,bCaseInsensitive:a.caseInsensitive}}function sb(a){var b=a.sTableId,c=a.aanFeatures.i,d=h("<div/>",{"class":a.oClasses.sInfo,id:!c?b+"_info":null});c||(a.aoDrawCallback.push({fn:Cb,sName:"information"}),d.attr("role","status").attr("aria-live","polite"),h(a.nTable).attr("aria-describedby",b+"_info"));return d[0]}function Cb(a){var b=a.aanFeatures.i;if(0!==b.length){var c=a.oLanguage,d=a._iDisplayStart+1,e=a.fnDisplayEnd(),f=a.fnRecordsTotal(),
g=a.fnRecordsDisplay(),i=g?c.sInfo:c.sInfoEmpty;g!==f&&(i+=" "+c.sInfoFiltered);i+=c.sInfoPostFix;i=Db(a,i);c=c.fnInfoCallback;null!==c&&(i=c.call(a.oInstance,a,d,e,f,g,i));h(b).html(i)}}function Db(a,b){var c=a.fnFormatNumber,d=a._iDisplayStart+1,e=a._iDisplayLength,f=a.fnRecordsDisplay(),g=-1===e;return b.replace(/_START_/g,c.call(a,d)).replace(/_END_/g,c.call(a,a.fnDisplayEnd())).replace(/_MAX_/g,c.call(a,a.fnRecordsTotal())).replace(/_TOTAL_/g,c.call(a,f)).replace(/_PAGE_/g,c.call(a,g?1:Math.ceil(d/
e))).replace(/_PAGES_/g,c.call(a,g?1:Math.ceil(f/e)))}function ia(a){var b,c,d=a.iInitDisplayStart,e=a.aoColumns,f;c=a.oFeatures;var g=a.bDeferLoading;if(a.bInitialised){nb(a);kb(a);ga(a,a.aoHeader);ga(a,a.aoFooter);C(a,!0);c.bAutoWidth&&Ha(a);b=0;for(c=e.length;b<c;b++)f=e[b],f.sWidth&&(f.nTh.style.width=u(f.sWidth));w(a,null,"preInit",[a]);R(a);e=y(a);if("ssp"!=e||g)"ajax"==e?ra(a,[],function(c){var f=sa(a,c);for(b=0;b<f.length;b++)L(a,f[b]);a.iInitDisplayStart=d;R(a);C(a,!1);ta(a,c)},a):(C(a,!1),
ta(a))}else setTimeout(function(){ia(a)},200)}function ta(a,b){a._bInitComplete=!0;(b||a.oInit.aaData)&&Y(a);w(a,"aoInitComplete","init",[a,b])}function Ra(a,b){var c=parseInt(b,10);a._iDisplayLength=c;Sa(a);w(a,null,"length",[a,c])}function ob(a){for(var b=a.oClasses,c=a.sTableId,d=a.aLengthMenu,e=h.isArray(d[0]),f=e?d[0]:d,d=e?d[1]:d,e=h("<select/>",{name:c+"_length","aria-controls":c,"class":b.sLengthSelect}),g=0,i=f.length;g<i;g++)e[0][g]=new Option(d[g],f[g]);var j=h("<div><label/></div>").addClass(b.sLength);
a.aanFeatures.l||(j[0].id=c+"_length");j.children().append(a.oLanguage.sLengthMenu.replace("_MENU_",e[0].outerHTML));h("select",j).val(a._iDisplayLength).bind("change.DT",function(){Ra(a,h(this).val());M(a)});h(a.nTable).bind("length.dt.DT",function(b,c,d){a===c&&h("select",j).val(d)});return j[0]}function tb(a){var b=a.sPaginationType,c=m.ext.pager[b],d="function"===typeof c,e=function(a){M(a)},b=h("<div/>").addClass(a.oClasses.sPaging+b)[0],f=a.aanFeatures;d||c.fnInit(a,b,e);f.p||(b.id=a.sTableId+
"_paginate",a.aoDrawCallback.push({fn:function(a){if(d){var b=a._iDisplayStart,j=a._iDisplayLength,h=a.fnRecordsDisplay(),l=-1===j,b=l?0:Math.ceil(b/j),j=l?1:Math.ceil(h/j),h=c(b,j),k,l=0;for(k=f.p.length;l<k;l++)Pa(a,"pageButton")(a,f.p[l],l,h,b,j)}else c.fnUpdate(a,e)},sName:"pagination"}));return b}function Ta(a,b,c){var d=a._iDisplayStart,e=a._iDisplayLength,f=a.fnRecordsDisplay();0===f||-1===e?d=0:"number"===typeof b?(d=b*e,d>f&&(d=0)):"first"==b?d=0:"previous"==b?(d=0<=e?d-e:0,0>d&&(d=0)):"next"==
b?d+e<f&&(d+=e):"last"==b?d=Math.floor((f-1)/e)*e:J(a,0,"Unknown paging action: "+b,5);b=a._iDisplayStart!==d;a._iDisplayStart=d;b&&(w(a,null,"page",[a]),c&&M(a));return b}function qb(a){return h("<div/>",{id:!a.aanFeatures.r?a.sTableId+"_processing":null,"class":a.oClasses.sProcessing}).html(a.oLanguage.sProcessing).insertBefore(a.nTable)[0]}function C(a,b){a.oFeatures.bProcessing&&h(a.aanFeatures.r).css("display",b?"block":"none");w(a,null,"processing",[a,b])}function rb(a){var b=h(a.nTable);b.attr("role",
"grid");var c=a.oScroll;if(""===c.sX&&""===c.sY)return a.nTable;var d=c.sX,e=c.sY,f=a.oClasses,g=b.children("caption"),i=g.length?g[0]._captionSide:null,j=h(b[0].cloneNode(!1)),n=h(b[0].cloneNode(!1)),l=b.children("tfoot");c.sX&&"100%"===b.attr("width")&&b.removeAttr("width");l.length||(l=null);j=h("<div/>",{"class":f.sScrollWrapper}).append(h("<div/>",{"class":f.sScrollHead}).css({overflow:"hidden",position:"relative",border:0,width:d?!d?null:u(d):"100%"}).append(h("<div/>",{"class":f.sScrollHeadInner}).css({"box-sizing":"content-box",
width:c.sXInner||"100%"}).append(j.removeAttr("id").css("margin-left",0).append("top"===i?g:null).append(b.children("thead"))))).append(h("<div/>",{"class":f.sScrollBody}).css({position:"relative",overflow:"auto",width:!d?null:u(d)}).append(b));l&&j.append(h("<div/>",{"class":f.sScrollFoot}).css({overflow:"hidden",border:0,width:d?!d?null:u(d):"100%"}).append(h("<div/>",{"class":f.sScrollFootInner}).append(n.removeAttr("id").css("margin-left",0).append("bottom"===i?g:null).append(b.children("tfoot")))));
var b=j.children(),k=b[0],f=b[1],q=l?b[2]:null;if(d)h(f).on("scroll.DT",function(){var a=this.scrollLeft;k.scrollLeft=a;l&&(q.scrollLeft=a)});h(f).css(e&&c.bCollapse?"max-height":"height",e);a.nScrollHead=k;a.nScrollBody=f;a.nScrollFoot=q;a.aoDrawCallback.push({fn:Z,sName:"scrolling"});return j[0]}function Z(a){var b=a.oScroll,c=b.sX,d=b.sXInner,e=b.sY,b=b.iBarWidth,f=h(a.nScrollHead),g=f[0].style,i=f.children("div"),j=i[0].style,n=i.children("table"),i=a.nScrollBody,l=h(i),k=i.style,q=h(a.nScrollFoot).children("div"),
m=q.children("table"),o=h(a.nTHead),E=h(a.nTable),p=E[0],t=p.style,N=a.nTFoot?h(a.nTFoot):null,Eb=a.oBrowser,w=Eb.bScrollOversize,s,v,O,x,y=[],z=[],A=[],B,C=function(a){a=a.style;a.paddingTop="0";a.paddingBottom="0";a.borderTopWidth="0";a.borderBottomWidth="0";a.height=0};E.children("thead, tfoot").remove();x=o.clone().prependTo(E);o=o.find("tr");v=x.find("tr");x.find("th, td").removeAttr("tabindex");N&&(O=N.clone().prependTo(E),s=N.find("tr"),O=O.find("tr"));c||(k.width="100%",f[0].style.width="100%");
h.each(qa(a,x),function(b,c){B=$(a,b);c.style.width=a.aoColumns[B].sWidth});N&&H(function(a){a.style.width=""},O);f=E.outerWidth();if(""===c){t.width="100%";if(w&&(E.find("tbody").height()>i.offsetHeight||"scroll"==l.css("overflow-y")))t.width=u(E.outerWidth()-b);f=E.outerWidth()}else""!==d&&(t.width=u(d),f=E.outerWidth());H(C,v);H(function(a){A.push(a.innerHTML);y.push(u(h(a).css("width")))},v);H(function(a,b){a.style.width=y[b]},o);h(v).height(0);N&&(H(C,O),H(function(a){z.push(u(h(a).css("width")))},
O),H(function(a,b){a.style.width=z[b]},s),h(O).height(0));H(function(a,b){a.innerHTML='<div class="dataTables_sizing" style="height:0;overflow:hidden;">'+A[b]+"</div>";a.style.width=y[b]},v);N&&H(function(a,b){a.innerHTML="";a.style.width=z[b]},O);if(E.outerWidth()<f){s=i.scrollHeight>i.offsetHeight||"scroll"==l.css("overflow-y")?f+b:f;if(w&&(i.scrollHeight>i.offsetHeight||"scroll"==l.css("overflow-y")))t.width=u(s-b);(""===c||""!==d)&&J(a,1,"Possible column misalignment",6)}else s="100%";k.width=
u(s);g.width=u(s);N&&(a.nScrollFoot.style.width=u(s));!e&&w&&(k.height=u(p.offsetHeight+b));c=E.outerWidth();n[0].style.width=u(c);j.width=u(c);d=E.height()>i.clientHeight||"scroll"==l.css("overflow-y");e="padding"+(Eb.bScrollbarLeft?"Left":"Right");j[e]=d?b+"px":"0px";N&&(m[0].style.width=u(c),q[0].style.width=u(c),q[0].style[e]=d?b+"px":"0px");l.scroll();if((a.bSorted||a.bFiltered)&&!a._drawHold)i.scrollTop=0}function H(a,b,c){for(var d=0,e=0,f=b.length,g,i;e<f;){g=b[e].firstChild;for(i=c?c[e].firstChild:
null;g;)1===g.nodeType&&(c?a(g,i,d):a(g,d),d++),g=g.nextSibling,i=c?i.nextSibling:null;e++}}function Ha(a){var b=a.nTable,c=a.aoColumns,d=a.oScroll,e=d.sY,f=d.sX,g=d.sXInner,i=c.length,j=aa(a,"bVisible"),n=h("th",a.nTHead),l=b.getAttribute("width"),k=b.parentNode,q=!1,m,o,p;p=a.oBrowser;d=p.bScrollOversize;(m=b.style.width)&&-1!==m.indexOf("%")&&(l=m);for(m=0;m<j.length;m++)o=c[j[m]],null!==o.sWidth&&(o.sWidth=Fb(o.sWidthOrig,k),q=!0);if(d||!q&&!f&&!e&&i==ca(a)&&i==n.length)for(m=0;m<i;m++){if(j=
$(a,m))c[j].sWidth=u(n.eq(m).width())}else{i=h(b).clone().css("visibility","hidden").removeAttr("id");i.find("tbody tr").remove();var t=h("<tr/>").appendTo(i.find("tbody"));i.find("thead, tfoot").remove();i.append(h(a.nTHead).clone()).append(h(a.nTFoot).clone());i.find("tfoot th, tfoot td").css("width","");n=qa(a,i.find("thead")[0]);for(m=0;m<j.length;m++)o=c[j[m]],n[m].style.width=null!==o.sWidthOrig&&""!==o.sWidthOrig?u(o.sWidthOrig):"";if(a.aoData.length)for(m=0;m<j.length;m++)q=j[m],o=c[q],h(Gb(a,
q)).clone(!1).append(o.sContentPadding).appendTo(t);q=h("<div/>").css(f||e?{position:"absolute",top:0,left:0,height:1,right:0,overflow:"hidden"}:{}).append(i).appendTo(k);f&&g?i.width(g):f?(i.css("width","auto"),i.width()<k.clientWidth&&i.width(k.clientWidth)):e?i.width(k.clientWidth):l&&i.width(l);if(f){for(m=g=0;m<j.length;m++)o=c[j[m]],e=p.bBounding?n[m].getBoundingClientRect().width:h(n[m]).outerWidth(),g+=null===o.sWidthOrig?e:parseInt(o.sWidth,10)+e-h(n[m]).width();i.width(u(g));b.style.width=
u(g)}for(m=0;m<j.length;m++)if(o=c[j[m]],p=h(n[m]).width())o.sWidth=u(p);b.style.width=u(i.css("width"));q.remove()}l&&(b.style.width=u(l));if((l||f)&&!a._reszEvt)b=function(){h(Fa).bind("resize.DT-"+a.sInstance,ua(function(){Y(a)}))},d?setTimeout(b,1E3):b(),a._reszEvt=!0}function ua(a,b){var c=b!==k?b:200,d,e;return function(){var b=this,g=+new Date,i=arguments;d&&g<d+c?(clearTimeout(e),e=setTimeout(function(){d=k;a.apply(b,i)},c)):(d=g,a.apply(b,i))}}function Fb(a,b){if(!a)return 0;var c=h("<div/>").css("width",
u(a)).appendTo(b||T.body),d=c[0].offsetWidth;c.remove();return d}function Gb(a,b){var c=Hb(a,b);if(0>c)return null;var d=a.aoData[c];return!d.nTr?h("<td/>").html(B(a,c,b,"display"))[0]:d.anCells[b]}function Hb(a,b){for(var c,d=-1,e=-1,f=0,g=a.aoData.length;f<g;f++)c=B(a,f,b,"display")+"",c=c.replace($b,""),c.length>d&&(d=c.length,e=f);return e}function u(a){return null===a?"0px":"number"==typeof a?0>a?"0px":a+"px":a.match(/\d$/)?a+"px":a}function V(a){var b,c,d=[],e=a.aoColumns,f,g,i,j;b=a.aaSortingFixed;
c=h.isPlainObject(b);var n=[];f=function(a){a.length&&!h.isArray(a[0])?n.push(a):h.merge(n,a)};h.isArray(b)&&f(b);c&&b.pre&&f(b.pre);f(a.aaSorting);c&&b.post&&f(b.post);for(a=0;a<n.length;a++){j=n[a][0];f=e[j].aDataSort;b=0;for(c=f.length;b<c;b++)g=f[b],i=e[g].sType||"string",n[a]._idx===k&&(n[a]._idx=h.inArray(n[a][1],e[g].asSorting)),d.push({src:j,col:g,dir:n[a][1],index:n[a]._idx,type:i,formatter:m.ext.type.order[i+"-pre"]})}return d}function mb(a){var b,c,d=[],e=m.ext.type.order,f=a.aoData,g=
0,i,j=a.aiDisplayMaster,h;Ia(a);h=V(a);b=0;for(c=h.length;b<c;b++)i=h[b],i.formatter&&g++,Ib(a,i.col);if("ssp"!=y(a)&&0!==h.length){b=0;for(c=j.length;b<c;b++)d[j[b]]=b;g===h.length?j.sort(function(a,b){var c,e,g,i,j=h.length,k=f[a]._aSortData,m=f[b]._aSortData;for(g=0;g<j;g++)if(i=h[g],c=k[i.col],e=m[i.col],c=c<e?-1:c>e?1:0,0!==c)return"asc"===i.dir?c:-c;c=d[a];e=d[b];return c<e?-1:c>e?1:0}):j.sort(function(a,b){var c,g,i,j,k=h.length,m=f[a]._aSortData,p=f[b]._aSortData;for(i=0;i<k;i++)if(j=h[i],
c=m[j.col],g=p[j.col],j=e[j.type+"-"+j.dir]||e["string-"+j.dir],c=j(c,g),0!==c)return c;c=d[a];g=d[b];return c<g?-1:c>g?1:0})}a.bSorted=!0}function Jb(a){for(var b,c,d=a.aoColumns,e=V(a),a=a.oLanguage.oAria,f=0,g=d.length;f<g;f++){c=d[f];var i=c.asSorting;b=c.sTitle.replace(/<.*?>/g,"");var j=c.nTh;j.removeAttribute("aria-sort");c.bSortable&&(0<e.length&&e[0].col==f?(j.setAttribute("aria-sort","asc"==e[0].dir?"ascending":"descending"),c=i[e[0].index+1]||i[0]):c=i[0],b+="asc"===c?a.sSortAscending:
a.sSortDescending);j.setAttribute("aria-label",b)}}function Ua(a,b,c,d){var e=a.aaSorting,f=a.aoColumns[b].asSorting,g=function(a,b){var c=a._idx;c===k&&(c=h.inArray(a[1],f));return c+1<f.length?c+1:b?null:0};"number"===typeof e[0]&&(e=a.aaSorting=[e]);c&&a.oFeatures.bSortMulti?(c=h.inArray(b,D(e,"0")),-1!==c?(b=g(e[c],!0),null===b&&1===e.length&&(b=0),null===b?e.splice(c,1):(e[c][1]=f[b],e[c]._idx=b)):(e.push([b,f[0],0]),e[e.length-1]._idx=0)):e.length&&e[0][0]==b?(b=g(e[0]),e.length=1,e[0][1]=f[b],
e[0]._idx=b):(e.length=0,e.push([b,f[0]]),e[0]._idx=0);R(a);"function"==typeof d&&d(a)}function Oa(a,b,c,d){var e=a.aoColumns[c];Va(b,{},function(b){!1!==e.bSortable&&(a.oFeatures.bProcessing?(C(a,!0),setTimeout(function(){Ua(a,c,b.shiftKey,d);"ssp"!==y(a)&&C(a,!1)},0)):Ua(a,c,b.shiftKey,d))})}function xa(a){var b=a.aLastSort,c=a.oClasses.sSortColumn,d=V(a),e=a.oFeatures,f,g;if(e.bSort&&e.bSortClasses){e=0;for(f=b.length;e<f;e++)g=b[e].src,h(D(a.aoData,"anCells",g)).removeClass(c+(2>e?e+1:3));e=0;
for(f=d.length;e<f;e++)g=d[e].src,h(D(a.aoData,"anCells",g)).addClass(c+(2>e?e+1:3))}a.aLastSort=d}function Ib(a,b){var c=a.aoColumns[b],d=m.ext.order[c.sSortDataType],e;d&&(e=d.call(a.oInstance,a,b,ba(a,b)));for(var f,g=m.ext.type.order[c.sType+"-pre"],i=0,h=a.aoData.length;i<h;i++)if(c=a.aoData[i],c._aSortData||(c._aSortData=[]),!c._aSortData[b]||d)f=d?e[i]:B(a,i,b,"sort"),c._aSortData[b]=g?g(f):f}function ya(a){if(a.oFeatures.bStateSave&&!a.bDestroying){var b={time:+new Date,start:a._iDisplayStart,
length:a._iDisplayLength,order:h.extend(!0,[],a.aaSorting),search:Ab(a.oPreviousSearch),columns:h.map(a.aoColumns,function(b,d){return{visible:b.bVisible,search:Ab(a.aoPreSearchCols[d])}})};w(a,"aoStateSaveParams","stateSaveParams",[a,b]);a.oSavedState=b;a.fnStateSaveCallback.call(a.oInstance,a,b)}}function Kb(a){var b,c,d=a.aoColumns;if(a.oFeatures.bStateSave){var e=a.fnStateLoadCallback.call(a.oInstance,a);if(e&&e.time&&(b=w(a,"aoStateLoadParams","stateLoadParams",[a,e]),-1===h.inArray(!1,b)&&(b=
a.iStateDuration,!(0<b&&e.time<+new Date-1E3*b)&&d.length===e.columns.length))){a.oLoadedState=h.extend(!0,{},e);e.start!==k&&(a._iDisplayStart=e.start,a.iInitDisplayStart=e.start);e.length!==k&&(a._iDisplayLength=e.length);e.order!==k&&(a.aaSorting=[],h.each(e.order,function(b,c){a.aaSorting.push(c[0]>=d.length?[0,c[1]]:c)}));e.search!==k&&h.extend(a.oPreviousSearch,Bb(e.search));b=0;for(c=e.columns.length;b<c;b++){var f=e.columns[b];f.visible!==k&&(d[b].bVisible=f.visible);f.search!==k&&h.extend(a.aoPreSearchCols[b],
Bb(f.search))}w(a,"aoStateLoaded","stateLoaded",[a,e])}}}function za(a){var b=m.settings,a=h.inArray(a,D(b,"nTable"));return-1!==a?b[a]:null}function J(a,b,c,d){c="DataTables warning: "+(a?"table id="+a.sTableId+" - ":"")+c;d&&(c+=". For more information about this error, please see http://datatables.net/tn/"+d);if(b)Fa.console&&console.log&&console.log(c);else if(b=m.ext,b=b.sErrMode||b.errMode,a&&w(a,null,"error",[a,d,c]),"alert"==b)alert(c);else{if("throw"==b)throw Error(c);"function"==typeof b&&
b(a,d,c)}}function F(a,b,c,d){h.isArray(c)?h.each(c,function(c,d){h.isArray(d)?F(a,b,d[0],d[1]):F(a,b,d)}):(d===k&&(d=c),b[c]!==k&&(a[d]=b[c]))}function Lb(a,b,c){var d,e;for(e in b)b.hasOwnProperty(e)&&(d=b[e],h.isPlainObject(d)?(h.isPlainObject(a[e])||(a[e]={}),h.extend(!0,a[e],d)):a[e]=c&&"data"!==e&&"aaData"!==e&&h.isArray(d)?d.slice():d);return a}function Va(a,b,c){h(a).bind("click.DT",b,function(b){a.blur();c(b)}).bind("keypress.DT",b,function(a){13===a.which&&(a.preventDefault(),c(a))}).bind("selectstart.DT",
function(){return!1})}function z(a,b,c,d){c&&a[b].push({fn:c,sName:d})}function w(a,b,c,d){var e=[];b&&(e=h.map(a[b].slice().reverse(),function(b){return b.fn.apply(a.oInstance,d)}));null!==c&&(b=h.Event(c+".dt"),h(a.nTable).trigger(b,d),e.push(b.result));return e}function Sa(a){var b=a._iDisplayStart,c=a.fnDisplayEnd(),d=a._iDisplayLength;b>=c&&(b=c-d);b-=b%d;if(-1===d||0>b)b=0;a._iDisplayStart=b}function Pa(a,b){var c=a.renderer,d=m.ext.renderer[b];return h.isPlainObject(c)&&c[b]?d[c[b]]||d._:"string"===
typeof c?d[c]||d._:d._}function y(a){return a.oFeatures.bServerSide?"ssp":a.ajax||a.sAjaxSource?"ajax":"dom"}function Aa(a,b){var c=[],c=Mb.numbers_length,d=Math.floor(c/2);b<=c?c=W(0,b):a<=d?(c=W(0,c-2),c.push("ellipsis"),c.push(b-1)):(a>=b-1-d?c=W(b-(c-2),b):(c=W(a-d+2,a+d-1),c.push("ellipsis"),c.push(b-1)),c.splice(0,0,"ellipsis"),c.splice(0,0,0));c.DT_el="span";return c}function cb(a){h.each({num:function(b){return Ba(b,a)},"num-fmt":function(b){return Ba(b,a,Wa)},"html-num":function(b){return Ba(b,
a,Ca)},"html-num-fmt":function(b){return Ba(b,a,Ca,Wa)}},function(b,c){v.type.order[b+a+"-pre"]=c;b.match(/^html\-/)&&(v.type.search[b+a]=v.type.search.html)})}function Nb(a){return function(){var b=[za(this[m.ext.iApiIndex])].concat(Array.prototype.slice.call(arguments));return m.ext.internal[a].apply(this,b)}}var m,v,t,p,s,Xa={},Ob=/[\r\n]/g,Ca=/<.*?>/g,ac=/^[\w\+\-]/,bc=/[\w\+\-]$/,Yb=RegExp("(\\/|\\.|\\*|\\+|\\?|\\||\\(|\\)|\\[|\\]|\\{|\\}|\\\\|\\$|\\^|\\-)","g"),Wa=/[',$£€¥%\u2009\u202F\u20BD\u20a9\u20BArfk]/gi,
K=function(a){return!a||!0===a||"-"===a?!0:!1},Pb=function(a){var b=parseInt(a,10);return!isNaN(b)&&isFinite(a)?b:null},Qb=function(a,b){Xa[b]||(Xa[b]=RegExp(va(b),"g"));return"string"===typeof a&&"."!==b?a.replace(/\./g,"").replace(Xa[b],"."):a},Ya=function(a,b,c){var d="string"===typeof a;if(K(a))return!0;b&&d&&(a=Qb(a,b));c&&d&&(a=a.replace(Wa,""));return!isNaN(parseFloat(a))&&isFinite(a)},Rb=function(a,b,c){return K(a)?!0:!(K(a)||"string"===typeof a)?null:Ya(a.replace(Ca,""),b,c)?!0:null},D=function(a,
b,c){var d=[],e=0,f=a.length;if(c!==k)for(;e<f;e++)a[e]&&a[e][b]&&d.push(a[e][b][c]);else for(;e<f;e++)a[e]&&d.push(a[e][b]);return d},ja=function(a,b,c,d){var e=[],f=0,g=b.length;if(d!==k)for(;f<g;f++)a[b[f]][c]&&e.push(a[b[f]][c][d]);else for(;f<g;f++)e.push(a[b[f]][c]);return e},W=function(a,b){var c=[],d;b===k?(b=0,d=a):(d=b,b=a);for(var e=b;e<d;e++)c.push(e);return c},Sb=function(a){for(var b=[],c=0,d=a.length;c<d;c++)a[c]&&b.push(a[c]);return b},pa=function(a){var b=[],c,d,e=a.length,f,g=0;
d=0;a:for(;d<e;d++){c=a[d];for(f=0;f<g;f++)if(b[f]===c)continue a;b.push(c);g++}return b},A=function(a,b,c){a[b]!==k&&(a[c]=a[b])},da=/\[.*?\]$/,U=/\(\)$/,wa=h("<div>")[0],Zb=wa.textContent!==k,$b=/<.*?>/g;m=function(a){this.$=function(a,b){return this.api(!0).$(a,b)};this._=function(a,b){return this.api(!0).rows(a,b).data()};this.api=function(a){return a?new t(za(this[v.iApiIndex])):new t(this)};this.fnAddData=function(a,b){var c=this.api(!0),d=h.isArray(a)&&(h.isArray(a[0])||h.isPlainObject(a[0]))?
c.rows.add(a):c.row.add(a);(b===k||b)&&c.draw();return d.flatten().toArray()};this.fnAdjustColumnSizing=function(a){var b=this.api(!0).columns.adjust(),c=b.settings()[0],d=c.oScroll;a===k||a?b.draw(!1):(""!==d.sX||""!==d.sY)&&Z(c)};this.fnClearTable=function(a){var b=this.api(!0).clear();(a===k||a)&&b.draw()};this.fnClose=function(a){this.api(!0).row(a).child.hide()};this.fnDeleteRow=function(a,b,c){var d=this.api(!0),a=d.rows(a),e=a.settings()[0],h=e.aoData[a[0][0]];a.remove();b&&b.call(this,e,h);
(c===k||c)&&d.draw();return h};this.fnDestroy=function(a){this.api(!0).destroy(a)};this.fnDraw=function(a){this.api(!0).draw(a)};this.fnFilter=function(a,b,c,d,e,h){e=this.api(!0);null===b||b===k?e.search(a,c,d,h):e.column(b).search(a,c,d,h);e.draw()};this.fnGetData=function(a,b){var c=this.api(!0);if(a!==k){var d=a.nodeName?a.nodeName.toLowerCase():"";return b!==k||"td"==d||"th"==d?c.cell(a,b).data():c.row(a).data()||null}return c.data().toArray()};this.fnGetNodes=function(a){var b=this.api(!0);
return a!==k?b.row(a).node():b.rows().nodes().flatten().toArray()};this.fnGetPosition=function(a){var b=this.api(!0),c=a.nodeName.toUpperCase();return"TR"==c?b.row(a).index():"TD"==c||"TH"==c?(a=b.cell(a).index(),[a.row,a.columnVisible,a.column]):null};this.fnIsOpen=function(a){return this.api(!0).row(a).child.isShown()};this.fnOpen=function(a,b,c){return this.api(!0).row(a).child(b,c).show().child()[0]};this.fnPageChange=function(a,b){var c=this.api(!0).page(a);(b===k||b)&&c.draw(!1)};this.fnSetColumnVis=
function(a,b,c){a=this.api(!0).column(a).visible(b);(c===k||c)&&a.columns.adjust().draw()};this.fnSettings=function(){return za(this[v.iApiIndex])};this.fnSort=function(a){this.api(!0).order(a).draw()};this.fnSortListener=function(a,b,c){this.api(!0).order.listener(a,b,c)};this.fnUpdate=function(a,b,c,d,e){var h=this.api(!0);c===k||null===c?h.row(b).data(a):h.cell(b,c).data(a);(e===k||e)&&h.columns.adjust();(d===k||d)&&h.draw();return 0};this.fnVersionCheck=v.fnVersionCheck;var b=this,c=a===k,d=this.length;
c&&(a={});this.oApi=this.internal=v.internal;for(var e in m.ext.internal)e&&(this[e]=Nb(e));this.each(function(){var e={},e=1<d?Lb(e,a,!0):a,g=0,i,j=this.getAttribute("id"),n=!1,l=m.defaults,r=h(this);if("table"!=this.nodeName.toLowerCase())J(null,0,"Non-table node initialisation ("+this.nodeName+")",2);else{db(l);eb(l.column);I(l,l,!0);I(l.column,l.column,!0);I(l,h.extend(e,r.data()));var q=m.settings,g=0;for(i=q.length;g<i;g++){var p=q[g];if(p.nTable==this||p.nTHead.parentNode==this||p.nTFoot&&
p.nTFoot.parentNode==this){g=e.bRetrieve!==k?e.bRetrieve:l.bRetrieve;if(c||g)return p.oInstance;if(e.bDestroy!==k?e.bDestroy:l.bDestroy){p.oInstance.fnDestroy();break}else{J(p,0,"Cannot reinitialise DataTable",3);return}}if(p.sTableId==this.id){q.splice(g,1);break}}if(null===j||""===j)this.id=j="DataTables_Table_"+m.ext._unique++;var o=h.extend(!0,{},m.models.oSettings,{sDestroyWidth:r[0].style.width,sInstance:j,sTableId:j});o.nTable=this;o.oApi=b.internal;o.oInit=e;q.push(o);o.oInstance=1===b.length?
b:r.dataTable();db(e);e.oLanguage&&S(e.oLanguage);e.aLengthMenu&&!e.iDisplayLength&&(e.iDisplayLength=h.isArray(e.aLengthMenu[0])?e.aLengthMenu[0][0]:e.aLengthMenu[0]);e=Lb(h.extend(!0,{},l),e);F(o.oFeatures,e,"bPaginate bLengthChange bFilter bSort bSortMulti bInfo bProcessing bAutoWidth bSortClasses bServerSide bDeferRender".split(" "));F(o,e,["asStripeClasses","ajax","fnServerData","fnFormatNumber","sServerMethod","aaSorting","aaSortingFixed","aLengthMenu","sPaginationType","sAjaxSource","sAjaxDataProp",
"iStateDuration","sDom","bSortCellsTop","iTabIndex","fnStateLoadCallback","fnStateSaveCallback","renderer","searchDelay","rowId",["iCookieDuration","iStateDuration"],["oSearch","oPreviousSearch"],["aoSearchCols","aoPreSearchCols"],["iDisplayLength","_iDisplayLength"],["bJQueryUI","bJUI"]]);F(o.oScroll,e,[["sScrollX","sX"],["sScrollXInner","sXInner"],["sScrollY","sY"],["bScrollCollapse","bCollapse"]]);F(o.oLanguage,e,"fnInfoCallback");z(o,"aoDrawCallback",e.fnDrawCallback,"user");z(o,"aoServerParams",
e.fnServerParams,"user");z(o,"aoStateSaveParams",e.fnStateSaveParams,"user");z(o,"aoStateLoadParams",e.fnStateLoadParams,"user");z(o,"aoStateLoaded",e.fnStateLoaded,"user");z(o,"aoRowCallback",e.fnRowCallback,"user");z(o,"aoRowCreatedCallback",e.fnCreatedRow,"user");z(o,"aoHeaderCallback",e.fnHeaderCallback,"user");z(o,"aoFooterCallback",e.fnFooterCallback,"user");z(o,"aoInitComplete",e.fnInitComplete,"user");z(o,"aoPreDrawCallback",e.fnPreDrawCallback,"user");o.rowIdFn=P(e.rowId);fb(o);j=o.oClasses;
e.bJQueryUI?(h.extend(j,m.ext.oJUIClasses,e.oClasses),e.sDom===l.sDom&&"lfrtip"===l.sDom&&(o.sDom='<"H"lfr>t<"F"ip>'),o.renderer)?h.isPlainObject(o.renderer)&&!o.renderer.header&&(o.renderer.header="jqueryui"):o.renderer="jqueryui":h.extend(j,m.ext.classes,e.oClasses);r.addClass(j.sTable);o.iInitDisplayStart===k&&(o.iInitDisplayStart=e.iDisplayStart,o._iDisplayStart=e.iDisplayStart);null!==e.iDeferLoading&&(o.bDeferLoading=!0,g=h.isArray(e.iDeferLoading),o._iRecordsDisplay=g?e.iDeferLoading[0]:e.iDeferLoading,
o._iRecordsTotal=g?e.iDeferLoading[1]:e.iDeferLoading);var t=o.oLanguage;h.extend(!0,t,e.oLanguage);""!==t.sUrl&&(h.ajax({dataType:"json",url:t.sUrl,success:function(a){S(a);I(l.oLanguage,a);h.extend(true,t,a);ia(o)},error:function(){ia(o)}}),n=!0);null===e.asStripeClasses&&(o.asStripeClasses=[j.sStripeOdd,j.sStripeEven]);var g=o.asStripeClasses,s=r.children("tbody").find("tr").eq(0);-1!==h.inArray(!0,h.map(g,function(a){return s.hasClass(a)}))&&(h("tbody tr",this).removeClass(g.join(" ")),o.asDestroyStripes=
g.slice());q=[];g=this.getElementsByTagName("thead");0!==g.length&&(fa(o.aoHeader,g[0]),q=qa(o));if(null===e.aoColumns){p=[];g=0;for(i=q.length;g<i;g++)p.push(null)}else p=e.aoColumns;g=0;for(i=p.length;g<i;g++)Ga(o,q?q[g]:null);hb(o,e.aoColumnDefs,p,function(a,b){la(o,a,b)});if(s.length){var u=function(a,b){return a.getAttribute("data-"+b)!==null?b:null};h(s[0]).children("th, td").each(function(a,b){var c=o.aoColumns[a];if(c.mData===a){var d=u(b,"sort")||u(b,"order"),e=u(b,"filter")||u(b,"search");
if(d!==null||e!==null){c.mData={_:a+".display",sort:d!==null?a+".@data-"+d:k,type:d!==null?a+".@data-"+d:k,filter:e!==null?a+".@data-"+e:k};la(o,a)}}})}var v=o.oFeatures;e.bStateSave&&(v.bStateSave=!0,Kb(o,e),z(o,"aoDrawCallback",ya,"state_save"));if(e.aaSorting===k){q=o.aaSorting;g=0;for(i=q.length;g<i;g++)q[g][1]=o.aoColumns[g].asSorting[0]}xa(o);v.bSort&&z(o,"aoDrawCallback",function(){if(o.bSorted){var a=V(o),b={};h.each(a,function(a,c){b[c.src]=c.dir});w(o,null,"order",[o,a,b]);Jb(o)}});z(o,
"aoDrawCallback",function(){(o.bSorted||y(o)==="ssp"||v.bDeferRender)&&xa(o)},"sc");g=r.children("caption").each(function(){this._captionSide=r.css("caption-side")});i=r.children("thead");0===i.length&&(i=h("<thead/>").appendTo(this));o.nTHead=i[0];i=r.children("tbody");0===i.length&&(i=h("<tbody/>").appendTo(this));o.nTBody=i[0];i=r.children("tfoot");if(0===i.length&&0<g.length&&(""!==o.oScroll.sX||""!==o.oScroll.sY))i=h("<tfoot/>").appendTo(this);0===i.length||0===i.children().length?r.addClass(j.sNoFooter):
0<i.length&&(o.nTFoot=i[0],fa(o.aoFooter,o.nTFoot));if(e.aaData)for(g=0;g<e.aaData.length;g++)L(o,e.aaData[g]);else(o.bDeferLoading||"dom"==y(o))&&ma(o,h(o.nTBody).children("tr"));o.aiDisplay=o.aiDisplayMaster.slice();o.bInitialised=!0;!1===n&&ia(o)}});b=null;return this};var Tb=[],x=Array.prototype,cc=function(a){var b,c,d=m.settings,e=h.map(d,function(a){return a.nTable});if(a){if(a.nTable&&a.oApi)return[a];if(a.nodeName&&"table"===a.nodeName.toLowerCase())return b=h.inArray(a,e),-1!==b?[d[b]]:
null;if(a&&"function"===typeof a.settings)return a.settings().toArray();"string"===typeof a?c=h(a):a instanceof h&&(c=a)}else return[];if(c)return c.map(function(){b=h.inArray(this,e);return-1!==b?d[b]:null}).toArray()};t=function(a,b){if(!(this instanceof t))return new t(a,b);var c=[],d=function(a){(a=cc(a))&&(c=c.concat(a))};if(h.isArray(a))for(var e=0,f=a.length;e<f;e++)d(a[e]);else d(a);this.context=pa(c);b&&h.merge(this,b);this.selector={rows:null,cols:null,opts:null};t.extend(this,this,Tb)};
m.Api=t;h.extend(t.prototype,{any:function(){return 0!==this.count()},concat:x.concat,context:[],count:function(){return this.flatten().length},each:function(a){for(var b=0,c=this.length;b<c;b++)a.call(this,this[b],b,this);return this},eq:function(a){var b=this.context;return b.length>a?new t(b[a],this[a]):null},filter:function(a){var b=[];if(x.filter)b=x.filter.call(this,a,this);else for(var c=0,d=this.length;c<d;c++)a.call(this,this[c],c,this)&&b.push(this[c]);return new t(this.context,b)},flatten:function(){var a=
[];return new t(this.context,a.concat.apply(a,this.toArray()))},join:x.join,indexOf:x.indexOf||function(a,b){for(var c=b||0,d=this.length;c<d;c++)if(this[c]===a)return c;return-1},iterator:function(a,b,c,d){var e=[],f,g,h,j,n,l=this.context,m,q,p=this.selector;"string"===typeof a&&(d=c,c=b,b=a,a=!1);g=0;for(h=l.length;g<h;g++){var o=new t(l[g]);if("table"===b)f=c.call(o,l[g],g),f!==k&&e.push(f);else if("columns"===b||"rows"===b)f=c.call(o,l[g],this[g],g),f!==k&&e.push(f);else if("column"===b||"column-rows"===
b||"row"===b||"cell"===b){q=this[g];"column-rows"===b&&(m=Da(l[g],p.opts));j=0;for(n=q.length;j<n;j++)f=q[j],f="cell"===b?c.call(o,l[g],f.row,f.column,g,j):c.call(o,l[g],f,g,j,m),f!==k&&e.push(f)}}return e.length||d?(a=new t(l,a?e.concat.apply([],e):e),b=a.selector,b.rows=p.rows,b.cols=p.cols,b.opts=p.opts,a):this},lastIndexOf:x.lastIndexOf||function(a,b){return this.indexOf.apply(this.toArray.reverse(),arguments)},length:0,map:function(a){var b=[];if(x.map)b=x.map.call(this,a,this);else for(var c=
0,d=this.length;c<d;c++)b.push(a.call(this,this[c],c));return new t(this.context,b)},pluck:function(a){return this.map(function(b){return b[a]})},pop:x.pop,push:x.push,reduce:x.reduce||function(a,b){return gb(this,a,b,0,this.length,1)},reduceRight:x.reduceRight||function(a,b){return gb(this,a,b,this.length-1,-1,-1)},reverse:x.reverse,selector:null,shift:x.shift,sort:x.sort,splice:x.splice,toArray:function(){return x.slice.call(this)},to$:function(){return h(this)},toJQuery:function(){return h(this)},
unique:function(){return new t(this.context,pa(this))},unshift:x.unshift});t.extend=function(a,b,c){if(c.length&&b&&(b instanceof t||b.__dt_wrapper)){var d,e,f,g=function(a,b,c){return function(){var d=b.apply(a,arguments);t.extend(d,d,c.methodExt);return d}};d=0;for(e=c.length;d<e;d++)f=c[d],b[f.name]="function"===typeof f.val?g(a,f.val,f):h.isPlainObject(f.val)?{}:f.val,b[f.name].__dt_wrapper=!0,t.extend(a,b[f.name],f.propExt)}};t.register=p=function(a,b){if(h.isArray(a))for(var c=0,d=a.length;c<
d;c++)t.register(a[c],b);else for(var e=a.split("."),f=Tb,g,i,c=0,d=e.length;c<d;c++){g=(i=-1!==e[c].indexOf("()"))?e[c].replace("()",""):e[c];var j;a:{j=0;for(var n=f.length;j<n;j++)if(f[j].name===g){j=f[j];break a}j=null}j||(j={name:g,val:{},methodExt:[],propExt:[]},f.push(j));c===d-1?j.val=b:f=i?j.methodExt:j.propExt}};t.registerPlural=s=function(a,b,c){t.register(a,c);t.register(b,function(){var a=c.apply(this,arguments);return a===this?this:a instanceof t?a.length?h.isArray(a[0])?new t(a.context,
a[0]):a[0]:k:a})};p("tables()",function(a){var b;if(a){b=t;var c=this.context;if("number"===typeof a)a=[c[a]];else var d=h.map(c,function(a){return a.nTable}),a=h(d).filter(a).map(function(){var a=h.inArray(this,d);return c[a]}).toArray();b=new b(a)}else b=this;return b});p("table()",function(a){var a=this.tables(a),b=a.context;return b.length?new t(b[0]):a});s("tables().nodes()","table().node()",function(){return this.iterator("table",function(a){return a.nTable},1)});s("tables().body()","table().body()",
function(){return this.iterator("table",function(a){return a.nTBody},1)});s("tables().header()","table().header()",function(){return this.iterator("table",function(a){return a.nTHead},1)});s("tables().footer()","table().footer()",function(){return this.iterator("table",function(a){return a.nTFoot},1)});s("tables().containers()","table().container()",function(){return this.iterator("table",function(a){return a.nTableWrapper},1)});p("draw()",function(a){return this.iterator("table",function(b){"page"===
a?M(b):("string"===typeof a&&(a="full-hold"===a?!1:!0),R(b,!1===a))})});p("page()",function(a){return a===k?this.page.info().page:this.iterator("table",function(b){Ta(b,a)})});p("page.info()",function(){if(0===this.context.length)return k;var a=this.context[0],b=a._iDisplayStart,c=a._iDisplayLength,d=a.fnRecordsDisplay(),e=-1===c;return{page:e?0:Math.floor(b/c),pages:e?1:Math.ceil(d/c),start:b,end:a.fnDisplayEnd(),length:c,recordsTotal:a.fnRecordsTotal(),recordsDisplay:d,serverSide:"ssp"===y(a)}});
p("page.len()",function(a){return a===k?0!==this.context.length?this.context[0]._iDisplayLength:k:this.iterator("table",function(b){Ra(b,a)})});var Ub=function(a,b,c){if(c){var d=new t(a);d.one("draw",function(){c(d.ajax.json())})}if("ssp"==y(a))R(a,b);else{C(a,!0);var e=a.jqXHR;e&&4!==e.readyState&&e.abort();ra(a,[],function(c){na(a);for(var c=sa(a,c),d=0,e=c.length;d<e;d++)L(a,c[d]);R(a,b);C(a,!1)})}};p("ajax.json()",function(){var a=this.context;if(0<a.length)return a[0].json});p("ajax.params()",
function(){var a=this.context;if(0<a.length)return a[0].oAjaxData});p("ajax.reload()",function(a,b){return this.iterator("table",function(c){Ub(c,!1===b,a)})});p("ajax.url()",function(a){var b=this.context;if(a===k){if(0===b.length)return k;b=b[0];return b.ajax?h.isPlainObject(b.ajax)?b.ajax.url:b.ajax:b.sAjaxSource}return this.iterator("table",function(b){h.isPlainObject(b.ajax)?b.ajax.url=a:b.ajax=a})});p("ajax.url().load()",function(a,b){return this.iterator("table",function(c){Ub(c,!1===b,a)})});
var Za=function(a,b,c,d,e){var f=[],g,i,j,n,l,m;j=typeof b;if(!b||"string"===j||"function"===j||b.length===k)b=[b];j=0;for(n=b.length;j<n;j++){i=b[j]&&b[j].split?b[j].split(","):[b[j]];l=0;for(m=i.length;l<m;l++)(g=c("string"===typeof i[l]?h.trim(i[l]):i[l]))&&g.length&&(f=f.concat(g))}a=v.selector[a];if(a.length){j=0;for(n=a.length;j<n;j++)f=a[j](d,e,f)}return pa(f)},$a=function(a){a||(a={});a.filter&&a.search===k&&(a.search=a.filter);return h.extend({search:"none",order:"current",page:"all"},a)},
ab=function(a){for(var b=0,c=a.length;b<c;b++)if(0<a[b].length)return a[0]=a[b],a[0].length=1,a.length=1,a.context=[a.context[b]],a;a.length=0;return a},Da=function(a,b){var c,d,e,f=[],g=a.aiDisplay;c=a.aiDisplayMaster;var i=b.search;d=b.order;e=b.page;if("ssp"==y(a))return"removed"===i?[]:W(0,c.length);if("current"==e){c=a._iDisplayStart;for(d=a.fnDisplayEnd();c<d;c++)f.push(g[c])}else if("current"==d||"applied"==d)f="none"==i?c.slice():"applied"==i?g.slice():h.map(c,function(a){return-1===h.inArray(a,
g)?a:null});else if("index"==d||"original"==d){c=0;for(d=a.aoData.length;c<d;c++)"none"==i?f.push(c):(e=h.inArray(c,g),(-1===e&&"removed"==i||0<=e&&"applied"==i)&&f.push(c))}return f};p("rows()",function(a,b){a===k?a="":h.isPlainObject(a)&&(b=a,a="");var b=$a(b),c=this.iterator("table",function(c){var e=b;return Za("row",a,function(a){var b=Pb(a);if(b!==null&&!e)return[b];var i=Da(c,e);if(b!==null&&h.inArray(b,i)!==-1)return[b];if(!a)return i;if(typeof a==="function")return h.map(i,function(b){var e=
c.aoData[b];return a(b,e._aData,e.nTr)?b:null});b=Sb(ja(c.aoData,i,"nTr"));if(a.nodeName&&h.inArray(a,b)!==-1)return[a._DT_RowIndex];if(typeof a==="string"&&a.charAt(0)==="#"){i=c.aIds[a.replace(/^#/,"")];if(i!==k)return[i.idx]}return h(b).filter(a).map(function(){return this._DT_RowIndex}).toArray()},c,e)},1);c.selector.rows=a;c.selector.opts=b;return c});p("rows().nodes()",function(){return this.iterator("row",function(a,b){return a.aoData[b].nTr||k},1)});p("rows().data()",function(){return this.iterator(!0,
"rows",function(a,b){return ja(a.aoData,b,"_aData")},1)});s("rows().cache()","row().cache()",function(a){return this.iterator("row",function(b,c){var d=b.aoData[c];return"search"===a?d._aFilterData:d._aSortData},1)});s("rows().invalidate()","row().invalidate()",function(a){return this.iterator("row",function(b,c){ea(b,c,a)})});s("rows().indexes()","row().index()",function(){return this.iterator("row",function(a,b){return b},1)});s("rows().ids()","row().id()",function(a){for(var b=[],c=this.context,
d=0,e=c.length;d<e;d++)for(var f=0,g=this[d].length;f<g;f++){var h=c[d].rowIdFn(c[d].aoData[this[d][f]]._aData);b.push((!0===a?"#":"")+h)}return new t(c,b)});s("rows().remove()","row().remove()",function(){var a=this;this.iterator("row",function(b,c,d){var e=b.aoData,f=e[c];e.splice(c,1);for(var g=0,h=e.length;g<h;g++)null!==e[g].nTr&&(e[g].nTr._DT_RowIndex=g);oa(b.aiDisplayMaster,c);oa(b.aiDisplay,c);oa(a[d],c,!1);Sa(b);c=b.rowIdFn(f._aData);c!==k&&delete b.aIds[c]});this.iterator("table",function(a){for(var c=
0,d=a.aoData.length;c<d;c++)a.aoData[c].idx=c});return this});p("rows.add()",function(a){var b=this.iterator("table",function(b){var c,f,g,h=[];f=0;for(g=a.length;f<g;f++)c=a[f],c.nodeName&&"TR"===c.nodeName.toUpperCase()?h.push(ma(b,c)[0]):h.push(L(b,c));return h},1),c=this.rows(-1);c.pop();h.merge(c,b);return c});p("row()",function(a,b){return ab(this.rows(a,b))});p("row().data()",function(a){var b=this.context;if(a===k)return b.length&&this.length?b[0].aoData[this[0]]._aData:k;b[0].aoData[this[0]]._aData=
a;ea(b[0],this[0],"data");return this});p("row().node()",function(){var a=this.context;return a.length&&this.length?a[0].aoData[this[0]].nTr||null:null});p("row.add()",function(a){a instanceof h&&a.length&&(a=a[0]);var b=this.iterator("table",function(b){return a.nodeName&&"TR"===a.nodeName.toUpperCase()?ma(b,a)[0]:L(b,a)});return this.row(b[0])});var bb=function(a,b){var c=a.context;if(c.length&&(c=c[0].aoData[b!==k?b:a[0]])&&c._details)c._details.remove(),c._detailsShow=k,c._details=k},Vb=function(a,
b){var c=a.context;if(c.length&&a.length){var d=c[0].aoData[a[0]];if(d._details){(d._detailsShow=b)?d._details.insertAfter(d.nTr):d._details.detach();var e=c[0],f=new t(e),g=e.aoData;f.off("draw.dt.DT_details column-visibility.dt.DT_details destroy.dt.DT_details");0<D(g,"_details").length&&(f.on("draw.dt.DT_details",function(a,b){e===b&&f.rows({page:"current"}).eq(0).each(function(a){a=g[a];a._detailsShow&&a._details.insertAfter(a.nTr)})}),f.on("column-visibility.dt.DT_details",function(a,b){if(e===
b)for(var c,d=ca(b),f=0,h=g.length;f<h;f++)c=g[f],c._details&&c._details.children("td[colspan]").attr("colspan",d)}),f.on("destroy.dt.DT_details",function(a,b){if(e===b)for(var c=0,d=g.length;c<d;c++)g[c]._details&&bb(f,c)}))}}};p("row().child()",function(a,b){var c=this.context;if(a===k)return c.length&&this.length?c[0].aoData[this[0]]._details:k;if(!0===a)this.child.show();else if(!1===a)bb(this);else if(c.length&&this.length){var d=c[0],c=c[0].aoData[this[0]],e=[],f=function(a,b){if(h.isArray(a)||
a instanceof h)for(var c=0,k=a.length;c<k;c++)f(a[c],b);else a.nodeName&&"tr"===a.nodeName.toLowerCase()?e.push(a):(c=h("<tr><td/></tr>").addClass(b),h("td",c).addClass(b).html(a)[0].colSpan=ca(d),e.push(c[0]))};f(a,b);c._details&&c._details.remove();c._details=h(e);c._detailsShow&&c._details.insertAfter(c.nTr)}return this});p(["row().child.show()","row().child().show()"],function(){Vb(this,!0);return this});p(["row().child.hide()","row().child().hide()"],function(){Vb(this,!1);return this});p(["row().child.remove()",
"row().child().remove()"],function(){bb(this);return this});p("row().child.isShown()",function(){var a=this.context;return a.length&&this.length?a[0].aoData[this[0]]._detailsShow||!1:!1});var dc=/^(.+):(name|visIdx|visible)$/,Wb=function(a,b,c,d,e){for(var c=[],d=0,f=e.length;d<f;d++)c.push(B(a,e[d],b));return c};p("columns()",function(a,b){a===k?a="":h.isPlainObject(a)&&(b=a,a="");var b=$a(b),c=this.iterator("table",function(c){var e=a,f=b,g=c.aoColumns,i=D(g,"sName"),j=D(g,"nTh");return Za("column",
e,function(a){var b=Pb(a);if(a==="")return W(g.length);if(b!==null)return[b>=0?b:g.length+b];if(typeof a==="function"){var e=Da(c,f);return h.map(g,function(b,f){return a(f,Wb(c,f,0,0,e),j[f])?f:null})}var k=typeof a==="string"?a.match(dc):"";if(k)switch(k[2]){case "visIdx":case "visible":b=parseInt(k[1],10);if(b<0){var m=h.map(g,function(a,b){return a.bVisible?b:null});return[m[m.length+b]]}return[$(c,b)];case "name":return h.map(i,function(a,b){return a===k[1]?b:null})}else return h(j).filter(a).map(function(){return h.inArray(this,
j)}).toArray()},c,f)},1);c.selector.cols=a;c.selector.opts=b;return c});s("columns().header()","column().header()",function(){return this.iterator("column",function(a,b){return a.aoColumns[b].nTh},1)});s("columns().footer()","column().footer()",function(){return this.iterator("column",function(a,b){return a.aoColumns[b].nTf},1)});s("columns().data()","column().data()",function(){return this.iterator("column-rows",Wb,1)});s("columns().dataSrc()","column().dataSrc()",function(){return this.iterator("column",
function(a,b){return a.aoColumns[b].mData},1)});s("columns().cache()","column().cache()",function(a){return this.iterator("column-rows",function(b,c,d,e,f){return ja(b.aoData,f,"search"===a?"_aFilterData":"_aSortData",c)},1)});s("columns().nodes()","column().nodes()",function(){return this.iterator("column-rows",function(a,b,c,d,e){return ja(a.aoData,e,"anCells",b)},1)});s("columns().visible()","column().visible()",function(a,b){return this.iterator("column",function(c,d){if(a===k)return c.aoColumns[d].bVisible;
var e=c.aoColumns,f=e[d],g=c.aoData,i,j,m;if(a!==k&&f.bVisible!==a){if(a){var l=h.inArray(!0,D(e,"bVisible"),d+1);i=0;for(j=g.length;i<j;i++)m=g[i].nTr,e=g[i].anCells,m&&m.insertBefore(e[d],e[l]||null)}else h(D(c.aoData,"anCells",d)).detach();f.bVisible=a;ga(c,c.aoHeader);ga(c,c.aoFooter);if(b===k||b)Y(c),(c.oScroll.sX||c.oScroll.sY)&&Z(c);w(c,null,"column-visibility",[c,d,a]);ya(c)}})});s("columns().indexes()","column().index()",function(a){return this.iterator("column",function(b,c){return"visible"===
a?ba(b,c):c},1)});p("columns.adjust()",function(){return this.iterator("table",function(a){Y(a)},1)});p("column.index()",function(a,b){if(0!==this.context.length){var c=this.context[0];if("fromVisible"===a||"toData"===a)return $(c,b);if("fromData"===a||"toVisible"===a)return ba(c,b)}});p("column()",function(a,b){return ab(this.columns(a,b))});p("cells()",function(a,b,c){h.isPlainObject(a)&&(a.row===k?(c=a,a=null):(c=b,b=null));h.isPlainObject(b)&&(c=b,b=null);if(null===b||b===k)return this.iterator("table",
function(b){var d=a,e=$a(c),f=b.aoData,g=Da(b,e),i=Sb(ja(f,g,"anCells")),j=h([].concat.apply([],i)),l,m=b.aoColumns.length,n,p,t,s,u,v;return Za("cell",d,function(a){var c=typeof a==="function";if(a===null||a===k||c){n=[];p=0;for(t=g.length;p<t;p++){l=g[p];for(s=0;s<m;s++){u={row:l,column:s};if(c){v=f[l];a(u,B(b,l,s),v.anCells?v.anCells[s]:null)&&n.push(u)}else n.push(u)}}return n}return h.isPlainObject(a)?[a]:j.filter(a).map(function(a,b){if(b.parentNode)l=b.parentNode._DT_RowIndex;else{a=0;for(t=
f.length;a<t;a++)if(h.inArray(b,f[a].anCells)!==-1){l=a;break}}return{row:l,column:h.inArray(b,f[l].anCells)}}).toArray()},b,e)});var d=this.columns(b,c),e=this.rows(a,c),f,g,i,j,m,l=this.iterator("table",function(a,b){f=[];g=0;for(i=e[b].length;g<i;g++){j=0;for(m=d[b].length;j<m;j++)f.push({row:e[b][g],column:d[b][j]})}return f},1);h.extend(l.selector,{cols:b,rows:a,opts:c});return l});s("cells().nodes()","cell().node()",function(){return this.iterator("cell",function(a,b,c){return(a=a.aoData[b].anCells)?
a[c]:k},1)});p("cells().data()",function(){return this.iterator("cell",function(a,b,c){return B(a,b,c)},1)});s("cells().cache()","cell().cache()",function(a){a="search"===a?"_aFilterData":"_aSortData";return this.iterator("cell",function(b,c,d){return b.aoData[c][a][d]},1)});s("cells().render()","cell().render()",function(a){return this.iterator("cell",function(b,c,d){return B(b,c,d,a)},1)});s("cells().indexes()","cell().index()",function(){return this.iterator("cell",function(a,b,c){return{row:b,
column:c,columnVisible:ba(a,c)}},1)});s("cells().invalidate()","cell().invalidate()",function(a){return this.iterator("cell",function(b,c,d){ea(b,c,a,d)})});p("cell()",function(a,b,c){return ab(this.cells(a,b,c))});p("cell().data()",function(a){var b=this.context,c=this[0];if(a===k)return b.length&&c.length?B(b[0],c[0].row,c[0].column):k;ib(b[0],c[0].row,c[0].column,a);ea(b[0],c[0].row,"data",c[0].column);return this});p("order()",function(a,b){var c=this.context;if(a===k)return 0!==c.length?c[0].aaSorting:
k;"number"===typeof a?a=[[a,b]]:h.isArray(a[0])||(a=Array.prototype.slice.call(arguments));return this.iterator("table",function(b){b.aaSorting=a.slice()})});p("order.listener()",function(a,b,c){return this.iterator("table",function(d){Oa(d,a,b,c)})});p(["columns().order()","column().order()"],function(a){var b=this;return this.iterator("table",function(c,d){var e=[];h.each(b[d],function(b,c){e.push([c,a])});c.aaSorting=e})});p("search()",function(a,b,c,d){var e=this.context;return a===k?0!==e.length?
e[0].oPreviousSearch.sSearch:k:this.iterator("table",function(e){e.oFeatures.bFilter&&ha(e,h.extend({},e.oPreviousSearch,{sSearch:a+"",bRegex:null===b?!1:b,bSmart:null===c?!0:c,bCaseInsensitive:null===d?!0:d}),1)})});s("columns().search()","column().search()",function(a,b,c,d){return this.iterator("column",function(e,f){var g=e.aoPreSearchCols;if(a===k)return g[f].sSearch;e.oFeatures.bFilter&&(h.extend(g[f],{sSearch:a+"",bRegex:null===b?!1:b,bSmart:null===c?!0:c,bCaseInsensitive:null===d?!0:d}),ha(e,
e.oPreviousSearch,1))})});p("state()",function(){return this.context.length?this.context[0].oSavedState:null});p("state.clear()",function(){return this.iterator("table",function(a){a.fnStateSaveCallback.call(a.oInstance,a,{})})});p("state.loaded()",function(){return this.context.length?this.context[0].oLoadedState:null});p("state.save()",function(){return this.iterator("table",function(a){ya(a)})});m.versionCheck=m.fnVersionCheck=function(a){for(var b=m.version.split("."),a=a.split("."),c,d,e=0,f=
a.length;e<f;e++)if(c=parseInt(b[e],10)||0,d=parseInt(a[e],10)||0,c!==d)return c>d;return!0};m.isDataTable=m.fnIsDataTable=function(a){var b=h(a).get(0),c=!1;h.each(m.settings,function(a,e){var f=e.nScrollHead?h("table",e.nScrollHead)[0]:null,g=e.nScrollFoot?h("table",e.nScrollFoot)[0]:null;if(e.nTable===b||f===b||g===b)c=!0});return c};m.tables=m.fnTables=function(a){var b=!1;h.isPlainObject(a)&&(b=a.api,a=a.visible);var c=h.map(m.settings,function(b){if(!a||a&&h(b.nTable).is(":visible"))return b.nTable});
return b?new t(c):c};m.util={throttle:ua,escapeRegex:va};m.camelToHungarian=I;p("$()",function(a,b){var c=this.rows(b).nodes(),c=h(c);return h([].concat(c.filter(a).toArray(),c.find(a).toArray()))});h.each(["on","one","off"],function(a,b){p(b+"()",function(){var a=Array.prototype.slice.call(arguments);a[0].match(/\.dt\b/)||(a[0]+=".dt");var d=h(this.tables().nodes());d[b].apply(d,a);return this})});p("clear()",function(){return this.iterator("table",function(a){na(a)})});p("settings()",function(){return new t(this.context,
this.context)});p("init()",function(){var a=this.context;return a.length?a[0].oInit:null});p("data()",function(){return this.iterator("table",function(a){return D(a.aoData,"_aData")}).flatten()});p("destroy()",function(a){a=a||!1;return this.iterator("table",function(b){var c=b.nTableWrapper.parentNode,d=b.oClasses,e=b.nTable,f=b.nTBody,g=b.nTHead,i=b.nTFoot,j=h(e),f=h(f),k=h(b.nTableWrapper),l=h.map(b.aoData,function(a){return a.nTr}),p;b.bDestroying=!0;w(b,"aoDestroyCallback","destroy",[b]);a||
(new t(b)).columns().visible(!0);k.unbind(".DT").find(":not(tbody *)").unbind(".DT");h(Fa).unbind(".DT-"+b.sInstance);e!=g.parentNode&&(j.children("thead").detach(),j.append(g));i&&e!=i.parentNode&&(j.children("tfoot").detach(),j.append(i));b.aaSorting=[];b.aaSortingFixed=[];xa(b);h(l).removeClass(b.asStripeClasses.join(" "));h("th, td",g).removeClass(d.sSortable+" "+d.sSortableAsc+" "+d.sSortableDesc+" "+d.sSortableNone);b.bJUI&&(h("th span."+d.sSortIcon+", td span."+d.sSortIcon,g).detach(),h("th, td",
g).each(function(){var a=h("div."+d.sSortJUIWrapper,this);h(this).append(a.contents());a.detach()}));f.children().detach();f.append(l);g=a?"remove":"detach";j[g]();k[g]();!a&&c&&(c.insertBefore(e,b.nTableReinsertBefore),j.css("width",b.sDestroyWidth).removeClass(d.sTable),(p=b.asDestroyStripes.length)&&f.children().each(function(a){h(this).addClass(b.asDestroyStripes[a%p])}));c=h.inArray(b,m.settings);-1!==c&&m.settings.splice(c,1)})});h.each(["column","row","cell"],function(a,b){p(b+"s().every()",
function(a){return this.iterator(b,function(d,e,f,g,h){a.call((new t(d))[b](e,"cell"===b?f:k),e,f,g,h)})})});p("i18n()",function(a,b,c){var d=this.context[0],a=P(a)(d.oLanguage);a===k&&(a=b);c!==k&&h.isPlainObject(a)&&(a=a[c]!==k?a[c]:a._);return a.replace("%d",c)});m.version="1.10.9";m.settings=[];m.models={};m.models.oSearch={bCaseInsensitive:!0,sSearch:"",bRegex:!1,bSmart:!0};m.models.oRow={nTr:null,anCells:null,_aData:[],_aSortData:null,_aFilterData:null,_sFilterRow:null,_sRowStripe:"",src:null,
idx:-1};m.models.oColumn={idx:null,aDataSort:null,asSorting:null,bSearchable:null,bSortable:null,bVisible:null,_sManualType:null,_bAttrSrc:!1,fnCreatedCell:null,fnGetData:null,fnSetData:null,mData:null,mRender:null,nTh:null,nTf:null,sClass:null,sContentPadding:null,sDefaultContent:null,sName:null,sSortDataType:"std",sSortingClass:null,sSortingClassJUI:null,sTitle:null,sType:null,sWidth:null,sWidthOrig:null};m.defaults={aaData:null,aaSorting:[[0,"asc"]],aaSortingFixed:[],ajax:null,aLengthMenu:[10,
25,50,100],aoColumns:null,aoColumnDefs:null,aoSearchCols:[],asStripeClasses:null,bAutoWidth:!0,bDeferRender:!1,bDestroy:!1,bFilter:!0,bInfo:!0,bJQueryUI:!1,bLengthChange:!0,bPaginate:!0,bProcessing:!1,bRetrieve:!1,bScrollCollapse:!1,bServerSide:!1,bSort:!0,bSortMulti:!0,bSortCellsTop:!1,bSortClasses:!0,bStateSave:!1,fnCreatedRow:null,fnDrawCallback:null,fnFooterCallback:null,fnFormatNumber:function(a){return a.toString().replace(/\B(?=(\d{3})+(?!\d))/g,this.oLanguage.sThousands)},fnHeaderCallback:null,
fnInfoCallback:null,fnInitComplete:null,fnPreDrawCallback:null,fnRowCallback:null,fnServerData:null,fnServerParams:null,fnStateLoadCallback:function(a){try{return JSON.parse((-1===a.iStateDuration?sessionStorage:localStorage).getItem("DataTables_"+a.sInstance+"_"+location.pathname))}catch(b){}},fnStateLoadParams:null,fnStateLoaded:null,fnStateSaveCallback:function(a,b){try{(-1===a.iStateDuration?sessionStorage:localStorage).setItem("DataTables_"+a.sInstance+"_"+location.pathname,JSON.stringify(b))}catch(c){}},
fnStateSaveParams:null,iStateDuration:7200,iDeferLoading:null,iDisplayLength:10,iDisplayStart:0,iTabIndex:0,oClasses:{},oLanguage:{oAria:{sSortAscending:": activate to sort column ascending",sSortDescending:": activate to sort column descending"},oPaginate:{sFirst:"First",sLast:"Last",sNext:"Next",sPrevious:"Previous"},sEmptyTable:"No data available in table",sInfo:"Showing _START_ to _END_ of _TOTAL_ entries",sInfoEmpty:"Showing 0 to 0 of 0 entries",sInfoFiltered:"(filtered from _MAX_ total entries)",
sInfoPostFix:"",sDecimal:"",sThousands:",",sLengthMenu:"Show _MENU_ entries",sLoadingRecords:"Loading...",sProcessing:"Processing...",sSearch:"Search:",sSearchPlaceholder:"",sUrl:"",sZeroRecords:"No matching records found"},oSearch:h.extend({},m.models.oSearch),sAjaxDataProp:"data",sAjaxSource:null,sDom:"lfrtip",searchDelay:null,sPaginationType:"simple_numbers",sScrollX:"",sScrollXInner:"",sScrollY:"",sServerMethod:"GET",renderer:null,rowId:"DT_RowId"};X(m.defaults);m.defaults.column={aDataSort:null,
iDataSort:-1,asSorting:["asc","desc"],bSearchable:!0,bSortable:!0,bVisible:!0,fnCreatedCell:null,mData:null,mRender:null,sCellType:"td",sClass:"",sContentPadding:"",sDefaultContent:null,sName:"",sSortDataType:"std",sTitle:null,sType:null,sWidth:null};X(m.defaults.column);m.models.oSettings={oFeatures:{bAutoWidth:null,bDeferRender:null,bFilter:null,bInfo:null,bLengthChange:null,bPaginate:null,bProcessing:null,bServerSide:null,bSort:null,bSortMulti:null,bSortClasses:null,bStateSave:null},oScroll:{bCollapse:null,
iBarWidth:0,sX:null,sXInner:null,sY:null},oLanguage:{fnInfoCallback:null},oBrowser:{bScrollOversize:!1,bScrollbarLeft:!1,bBounding:!1,barWidth:0},ajax:null,aanFeatures:[],aoData:[],aiDisplay:[],aiDisplayMaster:[],aIds:{},aoColumns:[],aoHeader:[],aoFooter:[],oPreviousSearch:{},aoPreSearchCols:[],aaSorting:null,aaSortingFixed:[],asStripeClasses:null,asDestroyStripes:[],sDestroyWidth:0,aoRowCallback:[],aoHeaderCallback:[],aoFooterCallback:[],aoDrawCallback:[],aoRowCreatedCallback:[],aoPreDrawCallback:[],
aoInitComplete:[],aoStateSaveParams:[],aoStateLoadParams:[],aoStateLoaded:[],sTableId:"",nTable:null,nTHead:null,nTFoot:null,nTBody:null,nTableWrapper:null,bDeferLoading:!1,bInitialised:!1,aoOpenRows:[],sDom:null,searchDelay:null,sPaginationType:"two_button",iStateDuration:0,aoStateSave:[],aoStateLoad:[],oSavedState:null,oLoadedState:null,sAjaxSource:null,sAjaxDataProp:null,bAjaxDataGet:!0,jqXHR:null,json:k,oAjaxData:k,fnServerData:null,aoServerParams:[],sServerMethod:null,fnFormatNumber:null,aLengthMenu:null,
iDraw:0,bDrawing:!1,iDrawError:-1,_iDisplayLength:10,_iDisplayStart:0,_iRecordsTotal:0,_iRecordsDisplay:0,bJUI:null,oClasses:{},bFiltered:!1,bSorted:!1,bSortCellsTop:null,oInit:null,aoDestroyCallback:[],fnRecordsTotal:function(){return"ssp"==y(this)?1*this._iRecordsTotal:this.aiDisplayMaster.length},fnRecordsDisplay:function(){return"ssp"==y(this)?1*this._iRecordsDisplay:this.aiDisplay.length},fnDisplayEnd:function(){var a=this._iDisplayLength,b=this._iDisplayStart,c=b+a,d=this.aiDisplay.length,e=
this.oFeatures,f=e.bPaginate;return e.bServerSide?!1===f||-1===a?b+d:Math.min(b+a,this._iRecordsDisplay):!f||c>d||-1===a?d:c},oInstance:null,sInstance:null,iTabIndex:0,nScrollHead:null,nScrollFoot:null,aLastSort:[],oPlugins:{},rowIdFn:null,rowId:null};m.ext=v={buttons:{},classes:{},errMode:"alert",feature:[],search:[],selector:{cell:[],column:[],row:[]},internal:{},legacy:{ajax:null},pager:{},renderer:{pageButton:{},header:{}},order:{},type:{detect:[],search:{},order:{}},_unique:0,fnVersionCheck:m.fnVersionCheck,
iApiIndex:0,oJUIClasses:{},sVersion:m.version};h.extend(v,{afnFiltering:v.search,aTypes:v.type.detect,ofnSearch:v.type.search,oSort:v.type.order,afnSortData:v.order,aoFeatures:v.feature,oApi:v.internal,oStdClasses:v.classes,oPagination:v.pager});h.extend(m.ext.classes,{sTable:"dataTable",sNoFooter:"no-footer",sPageButton:"paginate_button",sPageButtonActive:"current",sPageButtonDisabled:"disabled",sStripeOdd:"odd",sStripeEven:"even",sRowEmpty:"dataTables_empty",sWrapper:"dataTables_wrapper",sFilter:"dataTables_filter",
sInfo:"dataTables_info",sPaging:"dataTables_paginate paging_",sLength:"dataTables_length",sProcessing:"dataTables_processing",sSortAsc:"sorting_asc",sSortDesc:"sorting_desc",sSortable:"sorting",sSortableAsc:"sorting_asc_disabled",sSortableDesc:"sorting_desc_disabled",sSortableNone:"sorting_disabled",sSortColumn:"sorting_",sFilterInput:"",sLengthSelect:"",sScrollWrapper:"dataTables_scroll",sScrollHead:"dataTables_scrollHead",sScrollHeadInner:"dataTables_scrollHeadInner",sScrollBody:"dataTables_scrollBody",
sScrollFoot:"dataTables_scrollFoot",sScrollFootInner:"dataTables_scrollFootInner",sHeaderTH:"",sFooterTH:"",sSortJUIAsc:"",sSortJUIDesc:"",sSortJUI:"",sSortJUIAscAllowed:"",sSortJUIDescAllowed:"",sSortJUIWrapper:"",sSortIcon:"",sJUIHeader:"",sJUIFooter:""});var Ea="",Ea="",G=Ea+"ui-state-default",ka=Ea+"css_right ui-icon ui-icon-",Xb=Ea+"fg-toolbar ui-toolbar ui-widget-header ui-helper-clearfix";h.extend(m.ext.oJUIClasses,m.ext.classes,{sPageButton:"fg-button ui-button "+G,sPageButtonActive:"ui-state-disabled",
sPageButtonDisabled:"ui-state-disabled",sPaging:"dataTables_paginate fg-buttonset ui-buttonset fg-buttonset-multi ui-buttonset-multi paging_",sSortAsc:G+" sorting_asc",sSortDesc:G+" sorting_desc",sSortable:G+" sorting",sSortableAsc:G+" sorting_asc_disabled",sSortableDesc:G+" sorting_desc_disabled",sSortableNone:G+" sorting_disabled",sSortJUIAsc:ka+"triangle-1-n",sSortJUIDesc:ka+"triangle-1-s",sSortJUI:ka+"carat-2-n-s",sSortJUIAscAllowed:ka+"carat-1-n",sSortJUIDescAllowed:ka+"carat-1-s",sSortJUIWrapper:"DataTables_sort_wrapper",
sSortIcon:"DataTables_sort_icon",sScrollHead:"dataTables_scrollHead "+G,sScrollFoot:"dataTables_scrollFoot "+G,sHeaderTH:G,sFooterTH:G,sJUIHeader:Xb+" ui-corner-tl ui-corner-tr",sJUIFooter:Xb+" ui-corner-bl ui-corner-br"});var Mb=m.ext.pager;h.extend(Mb,{simple:function(){return["previous","next"]},full:function(){return["first","previous","next","last"]},numbers:function(a,b){return[Aa(a,b)]},simple_numbers:function(a,b){return["previous",Aa(a,b),"next"]},full_numbers:function(a,b){return["first",
"previous",Aa(a,b),"next","last"]},_numbers:Aa,numbers_length:7});h.extend(!0,m.ext.renderer,{pageButton:{_:function(a,b,c,d,e,f){var g=a.oClasses,i=a.oLanguage.oPaginate,j,k,l=0,m=function(b,d){var p,q,t,s,u=function(b){Ta(a,b.data.action,true)};p=0;for(q=d.length;p<q;p++){s=d[p];if(h.isArray(s)){t=h("<"+(s.DT_el||"div")+"/>").appendTo(b);m(t,s)}else{j=null;k="";switch(s){case "ellipsis":b.append('<span class="ellipsis">&#x2026;</span>');break;case "first":j=i.sFirst;k=s+(e>0?"":" "+g.sPageButtonDisabled);
break;case "previous":j=i.sPrevious;k=s+(e>0?"":" "+g.sPageButtonDisabled);break;case "next":j=i.sNext;k=s+(e<f-1?"":" "+g.sPageButtonDisabled);break;case "last":j=i.sLast;k=s+(e<f-1?"":" "+g.sPageButtonDisabled);break;default:j=s+1;k=e===s?g.sPageButtonActive:""}if(j!==null){t=h("<a>",{"class":g.sPageButton+" "+k,"aria-controls":a.sTableId,"data-dt-idx":l,tabindex:a.iTabIndex,id:c===0&&typeof s==="string"?a.sTableId+"_"+s:null}).html(j).appendTo(b);Va(t,{action:s},u);l++}}}},p;try{p=h(b).find(T.activeElement).data("dt-idx")}catch(t){}m(h(b).empty(),
d);p&&h(b).find("[data-dt-idx="+p+"]").focus()}}});h.extend(m.ext.type.detect,[function(a,b){var c=b.oLanguage.sDecimal;return Ya(a,c)?"num"+c:null},function(a){if(a&&!(a instanceof Date)&&(!ac.test(a)||!bc.test(a)))return null;var b=Date.parse(a);return null!==b&&!isNaN(b)||K(a)?"date":null},function(a,b){var c=b.oLanguage.sDecimal;return Ya(a,c,!0)?"num-fmt"+c:null},function(a,b){var c=b.oLanguage.sDecimal;return Rb(a,c)?"html-num"+c:null},function(a,b){var c=b.oLanguage.sDecimal;return Rb(a,c,
!0)?"html-num-fmt"+c:null},function(a){return K(a)||"string"===typeof a&&-1!==a.indexOf("<")?"html":null}]);h.extend(m.ext.type.search,{html:function(a){return K(a)?a:"string"===typeof a?a.replace(Ob," ").replace(Ca,""):""},string:function(a){return K(a)?a:"string"===typeof a?a.replace(Ob," "):a}});var Ba=function(a,b,c,d){if(0!==a&&(!a||"-"===a))return-Infinity;b&&(a=Qb(a,b));a.replace&&(c&&(a=a.replace(c,"")),d&&(a=a.replace(d,"")));return 1*a};h.extend(v.type.order,{"date-pre":function(a){return Date.parse(a)||
0},"html-pre":function(a){return K(a)?"":a.replace?a.replace(/<.*?>/g,"").toLowerCase():a+""},"string-pre":function(a){return K(a)?"":"string"===typeof a?a.toLowerCase():!a.toString?"":a.toString()},"string-asc":function(a,b){return a<b?-1:a>b?1:0},"string-desc":function(a,b){return a<b?1:a>b?-1:0}});cb("");h.extend(!0,m.ext.renderer,{header:{_:function(a,b,c,d){h(a.nTable).on("order.dt.DT",function(e,f,g,h){if(a===f){e=c.idx;b.removeClass(c.sSortingClass+" "+d.sSortAsc+" "+d.sSortDesc).addClass(h[e]==
"asc"?d.sSortAsc:h[e]=="desc"?d.sSortDesc:c.sSortingClass)}})},jqueryui:function(a,b,c,d){h("<div/>").addClass(d.sSortJUIWrapper).append(b.contents()).append(h("<span/>").addClass(d.sSortIcon+" "+c.sSortingClassJUI)).appendTo(b);h(a.nTable).on("order.dt.DT",function(e,f,g,h){if(a===f){e=c.idx;b.removeClass(d.sSortAsc+" "+d.sSortDesc).addClass(h[e]=="asc"?d.sSortAsc:h[e]=="desc"?d.sSortDesc:c.sSortingClass);b.find("span."+d.sSortIcon).removeClass(d.sSortJUIAsc+" "+d.sSortJUIDesc+" "+d.sSortJUI+" "+
d.sSortJUIAscAllowed+" "+d.sSortJUIDescAllowed).addClass(h[e]=="asc"?d.sSortJUIAsc:h[e]=="desc"?d.sSortJUIDesc:c.sSortingClassJUI)}})}}});m.render={number:function(a,b,c,d,e){return{display:function(f){if("number"!==typeof f&&"string"!==typeof f)return f;var g=0>f?"-":"",f=Math.abs(parseFloat(f)),h=parseInt(f,10),f=c?b+(f-h).toFixed(c).substring(2):"";return g+(d||"")+h.toString().replace(/\B(?=(\d{3})+(?!\d))/g,a)+f+(e||"")}}}};h.extend(m.ext.internal,{_fnExternApiFunc:Nb,_fnBuildAjax:ra,_fnAjaxUpdate:lb,
_fnAjaxParameters:ub,_fnAjaxUpdateDraw:vb,_fnAjaxDataSrc:sa,_fnAddColumn:Ga,_fnColumnOptions:la,_fnAdjustColumnSizing:Y,_fnVisibleToColumnIndex:$,_fnColumnIndexToVisible:ba,_fnVisbleColumns:ca,_fnGetColumns:aa,_fnColumnTypes:Ia,_fnApplyColumnDefs:hb,_fnHungarianMap:X,_fnCamelToHungarian:I,_fnLanguageCompat:S,_fnBrowserDetect:fb,_fnAddData:L,_fnAddTr:ma,_fnNodeToDataIndex:function(a,b){return b._DT_RowIndex!==k?b._DT_RowIndex:null},_fnNodeToColumnIndex:function(a,b,c){return h.inArray(c,a.aoData[b].anCells)},
_fnGetCellData:B,_fnSetCellData:ib,_fnSplitObjNotation:La,_fnGetObjectDataFn:P,_fnSetObjectDataFn:Q,_fnGetDataMaster:Ma,_fnClearTable:na,_fnDeleteIndex:oa,_fnInvalidate:ea,_fnGetRowElements:Ka,_fnCreateTr:Ja,_fnBuildHead:kb,_fnDrawHead:ga,_fnDraw:M,_fnReDraw:R,_fnAddOptionsHtml:nb,_fnDetectHeader:fa,_fnGetUniqueThs:qa,_fnFeatureHtmlFilter:pb,_fnFilterComplete:ha,_fnFilterCustom:yb,_fnFilterColumn:xb,_fnFilter:wb,_fnFilterCreateSearch:Qa,_fnEscapeRegex:va,_fnFilterData:zb,_fnFeatureHtmlInfo:sb,_fnUpdateInfo:Cb,
_fnInfoMacros:Db,_fnInitialise:ia,_fnInitComplete:ta,_fnLengthChange:Ra,_fnFeatureHtmlLength:ob,_fnFeatureHtmlPaginate:tb,_fnPageChange:Ta,_fnFeatureHtmlProcessing:qb,_fnProcessingDisplay:C,_fnFeatureHtmlTable:rb,_fnScrollDraw:Z,_fnApplyToChildren:H,_fnCalculateColumnWidths:Ha,_fnThrottle:ua,_fnConvertToWidth:Fb,_fnGetWidestNode:Gb,_fnGetMaxLenString:Hb,_fnStringToCss:u,_fnSortFlatten:V,_fnSort:mb,_fnSortAria:Jb,_fnSortListener:Ua,_fnSortAttachListener:Oa,_fnSortingClasses:xa,_fnSortData:Ib,_fnSaveState:ya,
_fnLoadState:Kb,_fnSettingsFromNode:za,_fnLog:J,_fnMap:F,_fnBindAction:Va,_fnCallbackReg:z,_fnCallbackFire:w,_fnLengthOverflow:Sa,_fnRenderer:Pa,_fnDataSource:y,_fnRowAttributes:Na,_fnCalculateEnd:function(){}});h.fn.dataTable=m;h.fn.dataTableSettings=m.settings;h.fn.dataTableExt=m.ext;h.fn.DataTable=function(a){return h(this).dataTable(a).api()};h.each(m,function(a,b){h.fn.DataTable[a]=b});return h.fn.dataTable};"function"===typeof define&&define.amd?define("datatables",["jquery"],S):"object"===
typeof exports?module.exports=S(require("jquery")):jQuery&&!jQuery.fn.dataTable&&S(jQuery)})(window,document);

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -1,544 +0,0 @@
.typeahead__container {
/**
* Restore the font weight unset by the previous rule.
*/
/**
* Show the overflow in IE.
* 1. Show the overflow in Edge.
*/
/**
* Remove the inheritance of text transform in Edge, Firefox, and IE.
* 1. Remove the inheritance of text transform in Firefox.
*/
/**
* 1. Prevent a WebKit bug where (2) destroys native `audio` and `video`
* controls in Android 4.
* 2. Correct the inability to style clickable types in iOS and Safari.
*/
/**
* Remove the inner border and padding in Firefox.
*/
/**
* Restore the focus styles unset by the previous rule.
*/
/**
* Change the border, margin, and padding in all browsers (opinionated).
*/
/**
* 1. Correct the text wrapping in Edge and IE.
* 2. Correct the color inheritance from `fieldset` elements in IE.
* 3. Remove the padding so developers are not caught out when they zero out
* `fieldset` elements in all browsers.
*/
/**
* Remove the default vertical scrollbar in IE.
*/
/**
* 1. Add the correct box sizing in IE 10-.
* 2. Remove the padding in IE 10-.
*/
/**
* Correct the cursor style of increment and decrement buttons in Chrome.
*/
/**
* 1. Correct the odd appearance in Chrome and Safari.
* 2. Correct the outline style in Safari.
*/
/**
* Remove the inner padding and cancel buttons in Chrome and Safari on OS X.
*/
/**
* Correct the text style of placeholders in Chrome, Edge, and Safari.
*/
/**
* 1. Correct the inability to style clickable types in iOS and Safari.
* 2. Change font properties to `inherit` in Safari.
*/
}
.typeahead__container button,
.typeahead__container input,
.typeahead__container optgroup,
.typeahead__container select,
.typeahead__container textarea {
font: inherit;
/* 1 */
margin: 0;
/* 2 */
}
.typeahead__container optgroup {
font-weight: bold;
}
.typeahead__container button,
.typeahead__container input {
/* 1 */
overflow: visible;
}
.typeahead__container button,
.typeahead__container select {
/* 1 */
text-transform: none;
}
.typeahead__container button,
.typeahead__container html [type="button"],
.typeahead__container [type="reset"],
.typeahead__container [type="submit"] {
-webkit-appearance: button;
/* 2 */
}
.typeahead__container button::-moz-focus-inner,
.typeahead__container [type="button"]::-moz-focus-inner,
.typeahead__container [type="reset"]::-moz-focus-inner,
.typeahead__container [type="submit"]::-moz-focus-inner {
border-style: none;
padding: 0;
}
.typeahead__container button:-moz-focusring,
.typeahead__container [type="button"]:-moz-focusring,
.typeahead__container [type="reset"]:-moz-focusring,
.typeahead__container [type="submit"]:-moz-focusring {
outline: 1px dotted ButtonText;
}
.typeahead__container fieldset {
border: 1px solid #c0c0c0;
margin: 0 2px;
padding: 0.35em 0.625em 0.75em;
}
.typeahead__container legend {
box-sizing: border-box;
/* 1 */
color: inherit;
/* 2 */
display: table;
/* 1 */
max-width: 100%;
/* 1 */
padding: 0;
/* 3 */
white-space: normal;
/* 1 */
}
.typeahead__container textarea {
overflow: auto;
}
.typeahead__container [type="checkbox"],
.typeahead__container [type="radio"] {
box-sizing: border-box;
/* 1 */
padding: 0;
/* 2 */
}
.typeahead__container [type="number"]::-webkit-inner-spin-button,
.typeahead__container [type="number"]::-webkit-outer-spin-button {
height: auto;
}
.typeahead__container [type="search"] {
-webkit-appearance: textfield;
/* 1 */
outline-offset: -2px;
/* 2 */
}
.typeahead__container [type="search"]::-webkit-search-cancel-button,
.typeahead__container [type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
.typeahead__container ::-webkit-input-placeholder {
color: inherit;
opacity: 0.54;
}
.typeahead__container ::-webkit-file-upload-button {
-webkit-appearance: button;
/* 1 */
font: inherit;
/* 2 */
}
.typeahead__container {
position: relative;
font: 14px Lato, "Helvetica Neue", Arial, Helvetica, sans-serif;
}
.typeahead__container * {
box-sizing: border-box;
outline: 0;
}
.typeahead__query {
position: relative;
z-index: 2;
width: 100%;
}
.typeahead__filter {
position: relative;
}
.typeahead__filter button {
min-width: 100%;
white-space: nowrap;
}
.typeahead__filter button:after {
display: inline-block;
margin-left: 4px;
width: 0;
height: 0;
vertical-align: -2px;
content: "";
border: 4px solid;
border-right-color: transparent;
border-bottom-color: transparent;
border-left-color: transparent;
}
.typeahead__field {
font-size: 0;
position: relative;
display: table;
border-collapse: collapse;
width: 100%;
}
.typeahead__field > * {
display: table-cell;
vertical-align: top;
}
.typeahead__query, .typeahead__filter, .typeahead__button {
font-size: 14px;
}
.typeahead__button {
position: relative;
font-size: 0;
width: 1%;
vertical-align: middle;
}
.typeahead__button button {
border-top-right-radius: 2px;
border-bottom-right-radius: 2px;
}
.typeahead__field {
color: #555;
}
.typeahead__field input {
display: block;
width: 100%;
height: 32px;
padding: 6px 12px;
background: #fff;
border: 1px solid #ccc;
border-radius: 2px 0 0 2px;
transition: all ease-in-out .15s;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
box-sizing: border-box;
}
.typeahead__field input:focus, .typeahead__field input:active {
border-color: #66afe9;
}
.typeahead__field input[type="search"],
.typeahead__field input[type="search"]::-webkit-search-cancel-button {
-webkit-appearance: none;
appearance: none;
}
.typeahead__field input[type="search"]::-ms-clear {
display: none;
width: 0;
height: 0;
}
.typeahead__container.hint .typeahead__field input {
background: transparent;
}
.typeahead__container.hint .typeahead__field input:last-child, .typeahead__hint {
background: #fff;
}
.typeahead__container button {
display: inline-block;
margin-bottom: 0;
text-align: center;
vertical-align: middle;
-ms-touch-action: manipulation;
touch-action: manipulation;
cursor: pointer;
background-color: #fff;
border: 1px solid #ccc;
height: 32px;
padding: 6px 12px;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
color: #555;
}
.typeahead__container button:hover, .typeahead__container button:focus {
color: #3c3c3c;
background-color: #f5f5f5;
border-color: #b3b3b3;
}
.typeahead__container button:active, .typeahead__container button.active {
background-image: none;
}
.typeahead__container button:focus, .typeahead__container button:active {
border-color: #66afe9;
}
.typeahead__container input.disabled,
.typeahead__container input[disabled],
.typeahead__container button.disabled,
.typeahead__container button[disabled] {
cursor: not-allowed;
pointer-events: none;
opacity: 0.65;
box-shadow: none;
background-color: #fff;
border-color: #ccc;
}
.typeahead__filter, .typeahead__button {
z-index: 1;
}
.typeahead__filter button, .typeahead__button button {
margin-left: -1px;
border-bottom-left-radius: 0;
border-top-left-radius: 0;
}
.typeahead__filter:hover, .typeahead__filter:active, .typeahead__filter:focus, .typeahead__button:hover, .typeahead__button:active, .typeahead__button:focus {
z-index: 1001;
}
.typeahead__filter:hover button:focus, .typeahead__filter:hover button:active, .typeahead__filter:active button:focus, .typeahead__filter:active button:active, .typeahead__filter:focus button:focus, .typeahead__filter:focus button:active, .typeahead__button:hover button:focus, .typeahead__button:hover button:active, .typeahead__button:active button:focus, .typeahead__button:active button:active, .typeahead__button:focus button:focus, .typeahead__button:focus button:active {
z-index: 1001;
}
.typeahead__filter + .typeahead__button button {
margin-left: -2px;
}
.typeahead__container.filter .typeahead__filter {
z-index: 1001;
}
.typeahead__list, .typeahead__dropdown {
position: absolute;
top: 100%;
left: 0;
z-index: 1000;
width: 100%;
min-width: 160px;
padding: 5px 0;
margin: 2px 0 0;
list-style: none;
text-align: left;
background-color: #fff;
border: 1px solid #ccc;
border-radius: 2px;
background-clip: padding-box;
}
.typeahead__result.detached .typeahead__list {
position: relative;
z-index: 1041;
top: initial;
left: initial;
}
.typeahead__dropdown {
right: 0;
left: initial;
z-index: 1001;
}
.typeahead__list > li {
position: relative;
border-top: solid 1px #ccc;
}
.typeahead__list > li:first-child {
border-top: none;
}
.typeahead__list > li > a,
.typeahead__dropdown > li > a {
display: block;
padding: 6px 12px;
clear: both;
color: #333333;
text-decoration: none;
}
.typeahead__list > li > a:hover,
.typeahead__list > li > a:focus,
.typeahead__list > li.active > a,
.typeahead__dropdown > li > a:hover,
.typeahead__dropdown > li > a:focus,
.typeahead__dropdown > li.active > a {
background-color: #f5f5f5;
color: #3c3c3c;
}
.typeahead__list.empty > li > a {
cursor: default;
}
.typeahead__list.empty > li > a:hover,
.typeahead__list.empty > li > a:focus,
.typeahead__list.empty > li.active > a {
background-color: transparent;
}
.typeahead__list > li.typeahead__group {
border-color: #bfdef6;
font-weight: bold;
}
.typeahead__list > li.typeahead__group:first-child {
border-top: solid 1px #bfdef6;
}
.typeahead__list > li.typeahead__group > a,
.typeahead__list > li.typeahead__group > a:hover,
.typeahead__list > li.typeahead__group > a:focus,
.typeahead__list > li.typeahead__group.active > a {
cursor: default;
color: #17639f;
background: #ecf5fc;
}
.typeahead__list > li.typeahead__group + li.typeahead__item {
border-color: #bfdef6;
}
.typeahead__container.result .typeahead__list,
.typeahead__container.filter .typeahead__dropdown,
.typeahead__container.hint .typeahead__hint,
.typeahead__container.backdrop + .typeahead__backdrop {
display: block !important;
}
.typeahead__container .typeahead__list,
.typeahead__container .typeahead__dropdown,
.typeahead__container .typeahead__hint,
.typeahead__container + .typeahead__backdrop {
display: none !important;
}
.typeahead__dropdown li:last-child {
margin-top: 5px;
padding-top: 5px;
border-top: solid 1px #ccc;
}
.typeahead__cancel-button {
visibility: hidden;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
border-radius: 50%;
width: 16px;
height: 16px;
position: absolute;
top: 8px;
right: .8em;
cursor: pointer;
background: url(data:image/svg+xml;charset=utf8;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pgo8IS0tIEdlbmVyYXRvcjogQWRvYmUgSWxsdXN0cmF0b3IgMTYuMC4wLCBTVkcgRXhwb3J0IFBsdWctSW4gLiBTVkcgVmVyc2lvbjogNi4wMCBCdWlsZCAwKSAgLS0+CjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0iQ2FwYV8xIiB4PSIwcHgiIHk9IjBweCIgd2lkdGg9IjE2cHgiIGhlaWdodD0iMTZweCIgdmlld0JveD0iMCAwIDQzOC41MzMgNDM4LjUzMyIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgNDM4LjUzMyA0MzguNTMzOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+CjxnPgoJPHBhdGggZD0iTTQwOS4xMzMsMTA5LjIwM2MtMTkuNjA4LTMzLjU5Mi00Ni4yMDUtNjAuMTg5LTc5Ljc5OC03OS43OTZDMjk1LjczNiw5LjgwMSwyNTkuMDU4LDAsMjE5LjI3MywwICAgYy0zOS43ODEsMC03Ni40Nyw5LjgwMS0xMTAuMDYzLDI5LjQwN2MtMzMuNTk1LDE5LjYwNC02MC4xOTIsNDYuMjAxLTc5LjgsNzkuNzk2QzkuODAxLDE0Mi44LDAsMTc5LjQ4OSwwLDIxOS4yNjcgICBjMCwzOS43OCw5LjgwNCw3Ni40NjMsMjkuNDA3LDExMC4wNjJjMTkuNjA3LDMzLjU5Miw0Ni4yMDQsNjAuMTg5LDc5Ljc5OSw3OS43OThjMzMuNTk3LDE5LjYwNSw3MC4yODMsMjkuNDA3LDExMC4wNjMsMjkuNDA3ICAgczc2LjQ3LTkuODAyLDExMC4wNjUtMjkuNDA3YzMzLjU5My0xOS42MDIsNjAuMTg5LTQ2LjIwNiw3OS43OTUtNzkuNzk4YzE5LjYwMy0zMy41OTYsMjkuNDAzLTcwLjI4NCwyOS40MDMtMTEwLjA2MiAgIEM0MzguNTMzLDE3OS40ODUsNDI4LjczMiwxNDIuNzk1LDQwOS4xMzMsMTA5LjIwM3ogTTMyMi42MjEsMjcwLjkzOWMzLjYxNywzLjYxMyw1LjQyOCw3LjkwNSw1LjQyOCwxMi44NTQgICBjMCw1LjEzMy0xLjgxMSw5LjUxNC01LjQyOCwxMy4xMjdsLTI1LjY5MywyNS43MDFjLTMuNjE0LDMuNjEzLTcuOTk0LDUuNDItMTMuMTM1LDUuNDJjLTQuOTQ4LDAtOS4yMzYtMS44MDctMTIuODQ3LTUuNDIgICBsLTUxLjY3Ni01MS42ODJsLTUxLjY3OCw1MS42ODJjLTMuNjE2LDMuNjEzLTcuODk4LDUuNDItMTIuODQ3LDUuNDJjLTUuMTQsMC05LjUxNy0xLjgwNy0xMy4xMzQtNS40MmwtMjUuNjk3LTI1LjcwMSAgIGMtMy42MTYtMy42MTMtNS40MjQtNy45OTQtNS40MjQtMTMuMTI3YzAtNC45NDgsMS44MDktOS4yNCw1LjQyNC0xMi44NTRsNTEuNjc4LTUxLjY3M2wtNTEuNjc4LTUxLjY3OCAgIGMtMy42MTYtMy42MTItNS40MjQtNy44OTgtNS40MjQtMTIuODQ3YzAtNS4xNCwxLjgwOS05LjUxNyw1LjQyNC0xMy4xMzRsMjUuNjk3LTI1LjY5M2MzLjYxNy0zLjYxNiw3Ljk5NC01LjQyNCwxMy4xMzQtNS40MjQgICBjNC45NDksMCw5LjIzMSwxLjgwOSwxMi44NDcsNS40MjRsNTEuNjc4LDUxLjY3NGw1MS42NzYtNTEuNjc0YzMuNjEtMy42MTYsNy44OTgtNS40MjQsMTIuODQ3LTUuNDI0ICAgYzUuMTQxLDAsOS41MjEsMS44MDksMTMuMTM1LDUuNDI0bDI1LjY5MywyNS42OTNjMy42MTcsMy42MTcsNS40MjgsNy45OTQsNS40MjgsMTMuMTM0YzAsNC45NDgtMS44MTEsOS4yMzUtNS40MjgsMTIuODQ3ICAgbC01MS42NzUsNTEuNjc4TDMyMi42MjEsMjcwLjkzOXoiIGZpbGw9IiM1NTU1NTUiLz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8L3N2Zz4K) no-repeat scroll center center transparent;
}
.typeahead__container.cancel:not(.loading) .typeahead__cancel-button {
visibility: visible;
opacity: .25;
}
.typeahead__container.cancel:not(.loading) .typeahead__cancel-button:hover {
opacity: .4;
}
.typeahead__search-icon {
padding: 0 1.25rem;
width: 16px;
height: 16px;
display: block;
background: url(data:image/svg+xml;charset=utf8;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pgo8IS0tIEdlbmVyYXRvcjogQWRvYmUgSWxsdXN0cmF0b3IgMTguMS4xLCBTVkcgRXhwb3J0IFBsdWctSW4gLiBTVkcgVmVyc2lvbjogNi4wMCBCdWlsZCAwKSAgLS0+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0iQ2FwYV8xIiB4PSIwcHgiIHk9IjBweCIgdmlld0JveD0iMCAwIDI1MC4zMTMgMjUwLjMxMyIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgMjUwLjMxMyAyNTAuMzEzOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSIgd2lkdGg9IjE2cHgiIGhlaWdodD0iMTZweCI+CjxnIGlkPSJTZWFyY2giPgoJPHBhdGggc3R5bGU9ImZpbGwtcnVsZTpldmVub2RkO2NsaXAtcnVsZTpldmVub2RkOyIgZD0iTTI0NC4xODYsMjE0LjYwNGwtNTQuMzc5LTU0LjM3OGMtMC4yODktMC4yODktMC42MjgtMC40OTEtMC45My0wLjc2ICAgYzEwLjctMTYuMjMxLDE2Ljk0NS0zNS42NiwxNi45NDUtNTYuNTU0QzIwNS44MjIsNDYuMDc1LDE1OS43NDcsMCwxMDIuOTExLDBTMCw0Ni4wNzUsMCwxMDIuOTExICAgYzAsNTYuODM1LDQ2LjA3NCwxMDIuOTExLDEwMi45MSwxMDIuOTExYzIwLjg5NSwwLDQwLjMyMy02LjI0NSw1Ni41NTQtMTYuOTQ1YzAuMjY5LDAuMzAxLDAuNDcsMC42NCwwLjc1OSwwLjkyOWw1NC4zOCw1NC4zOCAgIGM4LjE2OSw4LjE2OCwyMS40MTMsOC4xNjgsMjkuNTgzLDBDMjUyLjM1NCwyMzYuMDE3LDI1Mi4zNTQsMjIyLjc3MywyNDQuMTg2LDIxNC42MDR6IE0xMDIuOTExLDE3MC4xNDYgICBjLTM3LjEzNCwwLTY3LjIzNi0zMC4xMDItNjcuMjM2LTY3LjIzNWMwLTM3LjEzNCwzMC4xMDMtNjcuMjM2LDY3LjIzNi02Ny4yMzZjMzcuMTMyLDAsNjcuMjM1LDMwLjEwMyw2Ny4yMzUsNjcuMjM2ICAgQzE3MC4xNDYsMTQwLjA0NCwxNDAuMDQzLDE3MC4xNDYsMTAyLjkxMSwxNzAuMTQ2eiIgZmlsbD0iIzU1NTU1NSIvPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+Cjwvc3ZnPgo=) no-repeat scroll center center transparent;
}
.typeahead__container.loading .typeahead__query:before, .typeahead__container.loading .typeahead__query:after {
transition: all 0s linear, opacity 0.2s ease;
position: absolute;
z-index: 3;
content: '';
top: 50%;
right: .55em;
margin-top: -10.5px;
width: 21px;
height: 21px;
box-sizing: border-box;
border-radius: 500rem;
border-style: solid;
border-width: .1em;
}
.typeahead__container.loading .typeahead__query:before {
border-color: rgba(0, 0, 0, 0.35);
}
.typeahead__container.loading .typeahead__query:after {
-webkit-animation: button-spin 0.6s linear;
animation: button-spin 0.6s linear;
-webkit-animation-iteration-count: infinite;
animation-iteration-count: infinite;
border-color: #fff transparent transparent;
box-shadow: 0 0 0 1px transparent;
}
@-webkit-keyframes button-spin {
from {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
to {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@keyframes button-spin {
from {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
to {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,497 +0,0 @@
/*!
* jQuery UI Position v1.10.0
* http://jqueryui.com
*
* Copyright 2013 jQuery Foundation and other contributors
* Released under the MIT license.
* http://jquery.org/license
*
* http://api.jqueryui.com/position/
*/
(function( $, undefined ) {
$.ui = $.ui || {};
var cachedScrollbarWidth,
max = Math.max,
abs = Math.abs,
round = Math.round,
rhorizontal = /left|center|right/,
rvertical = /top|center|bottom/,
roffset = /[\+\-]\d+%?/,
rposition = /^\w+/,
rpercent = /%$/,
_position = $.fn.position;
function getOffsets( offsets, width, height ) {
return [
parseInt( offsets[ 0 ], 10 ) * ( rpercent.test( offsets[ 0 ] ) ? width / 100 : 1 ),
parseInt( offsets[ 1 ], 10 ) * ( rpercent.test( offsets[ 1 ] ) ? height / 100 : 1 )
];
}
function parseCss( element, property ) {
return parseInt( $.css( element, property ), 10 ) || 0;
}
function getDimensions( elem ) {
var raw = elem[0];
if ( raw.nodeType === 9 ) {
return {
width: elem.width(),
height: elem.height(),
offset: { top: 0, left: 0 }
};
}
if ( $.isWindow( raw ) ) {
return {
width: elem.width(),
height: elem.height(),
offset: { top: elem.scrollTop(), left: elem.scrollLeft() }
};
}
if ( raw.preventDefault ) {
return {
width: 0,
height: 0,
offset: { top: raw.pageY, left: raw.pageX }
};
}
return {
width: elem.outerWidth(),
height: elem.outerHeight(),
offset: elem.offset()
};
}
$.position = {
scrollbarWidth: function() {
if ( cachedScrollbarWidth !== undefined ) {
return cachedScrollbarWidth;
}
var w1, w2,
div = $( "<div style='display:block;width:50px;height:50px;overflow:hidden;'><div style='height:100px;width:auto;'></div></div>" ),
innerDiv = div.children()[0];
$( "body" ).append( div );
w1 = innerDiv.offsetWidth;
div.css( "overflow", "scroll" );
w2 = innerDiv.offsetWidth;
if ( w1 === w2 ) {
w2 = div[0].clientWidth;
}
div.remove();
return (cachedScrollbarWidth = w1 - w2);
},
getScrollInfo: function( within ) {
var overflowX = within.isWindow ? "" : within.element.css( "overflow-x" ),
overflowY = within.isWindow ? "" : within.element.css( "overflow-y" ),
hasOverflowX = overflowX === "scroll" ||
( overflowX === "auto" && within.width < within.element[0].scrollWidth ),
hasOverflowY = overflowY === "scroll" ||
( overflowY === "auto" && within.height < within.element[0].scrollHeight );
return {
width: hasOverflowX ? $.position.scrollbarWidth() : 0,
height: hasOverflowY ? $.position.scrollbarWidth() : 0
};
},
getWithinInfo: function( element ) {
var withinElement = $( element || window ),
isWindow = $.isWindow( withinElement[0] );
return {
element: withinElement,
isWindow: isWindow,
offset: withinElement.offset() || { left: 0, top: 0 },
scrollLeft: withinElement.scrollLeft(),
scrollTop: withinElement.scrollTop(),
width: isWindow ? withinElement.width() : withinElement.outerWidth(),
height: isWindow ? withinElement.height() : withinElement.outerHeight()
};
}
};
$.fn.position = function( options ) {
if ( !options || !options.of ) {
return _position.apply( this, arguments );
}
// make a copy, we don't want to modify arguments
options = $.extend( {}, options );
var atOffset, targetWidth, targetHeight, targetOffset, basePosition, dimensions,
target = $( options.of ),
within = $.position.getWithinInfo( options.within ),
scrollInfo = $.position.getScrollInfo( within ),
collision = ( options.collision || "flip" ).split( " " ),
offsets = {};
dimensions = getDimensions( target );
if ( target[0].preventDefault ) {
// force left top to allow flipping
options.at = "left top";
}
targetWidth = dimensions.width;
targetHeight = dimensions.height;
targetOffset = dimensions.offset;
// clone to reuse original targetOffset later
basePosition = $.extend( {}, targetOffset );
// force my and at to have valid horizontal and vertical positions
// if a value is missing or invalid, it will be converted to center
$.each( [ "my", "at" ], function() {
var pos = ( options[ this ] || "" ).split( " " ),
horizontalOffset,
verticalOffset;
if ( pos.length === 1) {
pos = rhorizontal.test( pos[ 0 ] ) ?
pos.concat( [ "center" ] ) :
rvertical.test( pos[ 0 ] ) ?
[ "center" ].concat( pos ) :
[ "center", "center" ];
}
pos[ 0 ] = rhorizontal.test( pos[ 0 ] ) ? pos[ 0 ] : "center";
pos[ 1 ] = rvertical.test( pos[ 1 ] ) ? pos[ 1 ] : "center";
// calculate offsets
horizontalOffset = roffset.exec( pos[ 0 ] );
verticalOffset = roffset.exec( pos[ 1 ] );
offsets[ this ] = [
horizontalOffset ? horizontalOffset[ 0 ] : 0,
verticalOffset ? verticalOffset[ 0 ] : 0
];
// reduce to just the positions without the offsets
options[ this ] = [
rposition.exec( pos[ 0 ] )[ 0 ],
rposition.exec( pos[ 1 ] )[ 0 ]
];
});
// normalize collision option
if ( collision.length === 1 ) {
collision[ 1 ] = collision[ 0 ];
}
if ( options.at[ 0 ] === "right" ) {
basePosition.left += targetWidth;
} else if ( options.at[ 0 ] === "center" ) {
basePosition.left += targetWidth / 2;
}
if ( options.at[ 1 ] === "bottom" ) {
basePosition.top += targetHeight;
} else if ( options.at[ 1 ] === "center" ) {
basePosition.top += targetHeight / 2;
}
atOffset = getOffsets( offsets.at, targetWidth, targetHeight );
basePosition.left += atOffset[ 0 ];
basePosition.top += atOffset[ 1 ];
return this.each(function() {
var collisionPosition, using,
elem = $( this ),
elemWidth = elem.outerWidth(),
elemHeight = elem.outerHeight(),
marginLeft = parseCss( this, "marginLeft" ),
marginTop = parseCss( this, "marginTop" ),
collisionWidth = elemWidth + marginLeft + parseCss( this, "marginRight" ) + scrollInfo.width,
collisionHeight = elemHeight + marginTop + parseCss( this, "marginBottom" ) + scrollInfo.height,
position = $.extend( {}, basePosition ),
myOffset = getOffsets( offsets.my, elem.outerWidth(), elem.outerHeight() );
if ( options.my[ 0 ] === "right" ) {
position.left -= elemWidth;
} else if ( options.my[ 0 ] === "center" ) {
position.left -= elemWidth / 2;
}
if ( options.my[ 1 ] === "bottom" ) {
position.top -= elemHeight;
} else if ( options.my[ 1 ] === "center" ) {
position.top -= elemHeight / 2;
}
position.left += myOffset[ 0 ];
position.top += myOffset[ 1 ];
// if the browser doesn't support fractions, then round for consistent results
if ( !$.support.offsetFractions ) {
position.left = round( position.left );
position.top = round( position.top );
}
collisionPosition = {
marginLeft: marginLeft,
marginTop: marginTop
};
$.each( [ "left", "top" ], function( i, dir ) {
if ( $.ui.position[ collision[ i ] ] ) {
$.ui.position[ collision[ i ] ][ dir ]( position, {
targetWidth: targetWidth,
targetHeight: targetHeight,
elemWidth: elemWidth,
elemHeight: elemHeight,
collisionPosition: collisionPosition,
collisionWidth: collisionWidth,
collisionHeight: collisionHeight,
offset: [ atOffset[ 0 ] + myOffset[ 0 ], atOffset [ 1 ] + myOffset[ 1 ] ],
my: options.my,
at: options.at,
within: within,
elem : elem
});
}
});
if ( options.using ) {
// adds feedback as second argument to using callback, if present
using = function( props ) {
var left = targetOffset.left - position.left,
right = left + targetWidth - elemWidth,
top = targetOffset.top - position.top,
bottom = top + targetHeight - elemHeight,
feedback = {
target: {
element: target,
left: targetOffset.left,
top: targetOffset.top,
width: targetWidth,
height: targetHeight
},
element: {
element: elem,
left: position.left,
top: position.top,
width: elemWidth,
height: elemHeight
},
horizontal: right < 0 ? "left" : left > 0 ? "right" : "center",
vertical: bottom < 0 ? "top" : top > 0 ? "bottom" : "middle"
};
if ( targetWidth < elemWidth && abs( left + right ) < targetWidth ) {
feedback.horizontal = "center";
}
if ( targetHeight < elemHeight && abs( top + bottom ) < targetHeight ) {
feedback.vertical = "middle";
}
if ( max( abs( left ), abs( right ) ) > max( abs( top ), abs( bottom ) ) ) {
feedback.important = "horizontal";
} else {
feedback.important = "vertical";
}
options.using.call( this, props, feedback );
};
}
elem.offset( $.extend( position, { using: using } ) );
});
};
$.ui.position = {
fit: {
left: function( position, data ) {
var within = data.within,
withinOffset = within.isWindow ? within.scrollLeft : within.offset.left,
outerWidth = within.width,
collisionPosLeft = position.left - data.collisionPosition.marginLeft,
overLeft = withinOffset - collisionPosLeft,
overRight = collisionPosLeft + data.collisionWidth - outerWidth - withinOffset,
newOverRight;
// element is wider than within
if ( data.collisionWidth > outerWidth ) {
// element is initially over the left side of within
if ( overLeft > 0 && overRight <= 0 ) {
newOverRight = position.left + overLeft + data.collisionWidth - outerWidth - withinOffset;
position.left += overLeft - newOverRight;
// element is initially over right side of within
} else if ( overRight > 0 && overLeft <= 0 ) {
position.left = withinOffset;
// element is initially over both left and right sides of within
} else {
if ( overLeft > overRight ) {
position.left = withinOffset + outerWidth - data.collisionWidth;
} else {
position.left = withinOffset;
}
}
// too far left -> align with left edge
} else if ( overLeft > 0 ) {
position.left += overLeft;
// too far right -> align with right edge
} else if ( overRight > 0 ) {
position.left -= overRight;
// adjust based on position and margin
} else {
position.left = max( position.left - collisionPosLeft, position.left );
}
},
top: function( position, data ) {
var within = data.within,
withinOffset = within.isWindow ? within.scrollTop : within.offset.top,
outerHeight = data.within.height,
collisionPosTop = position.top - data.collisionPosition.marginTop,
overTop = withinOffset - collisionPosTop,
overBottom = collisionPosTop + data.collisionHeight - outerHeight - withinOffset,
newOverBottom;
// element is taller than within
if ( data.collisionHeight > outerHeight ) {
// element is initially over the top of within
if ( overTop > 0 && overBottom <= 0 ) {
newOverBottom = position.top + overTop + data.collisionHeight - outerHeight - withinOffset;
position.top += overTop - newOverBottom;
// element is initially over bottom of within
} else if ( overBottom > 0 && overTop <= 0 ) {
position.top = withinOffset;
// element is initially over both top and bottom of within
} else {
if ( overTop > overBottom ) {
position.top = withinOffset + outerHeight - data.collisionHeight;
} else {
position.top = withinOffset;
}
}
// too far up -> align with top
} else if ( overTop > 0 ) {
position.top += overTop;
// too far down -> align with bottom edge
} else if ( overBottom > 0 ) {
position.top -= overBottom;
// adjust based on position and margin
} else {
position.top = max( position.top - collisionPosTop, position.top );
}
}
},
flip: {
left: function( position, data ) {
var within = data.within,
withinOffset = within.offset.left + within.scrollLeft,
outerWidth = within.width,
offsetLeft = within.isWindow ? within.scrollLeft : within.offset.left,
collisionPosLeft = position.left - data.collisionPosition.marginLeft,
overLeft = collisionPosLeft - offsetLeft,
overRight = collisionPosLeft + data.collisionWidth - outerWidth - offsetLeft,
myOffset = data.my[ 0 ] === "left" ?
-data.elemWidth :
data.my[ 0 ] === "right" ?
data.elemWidth :
0,
atOffset = data.at[ 0 ] === "left" ?
data.targetWidth :
data.at[ 0 ] === "right" ?
-data.targetWidth :
0,
offset = -2 * data.offset[ 0 ],
newOverRight,
newOverLeft;
if ( overLeft < 0 ) {
newOverRight = position.left + myOffset + atOffset + offset + data.collisionWidth - outerWidth - withinOffset;
if ( newOverRight < 0 || newOverRight < abs( overLeft ) ) {
position.left += myOffset + atOffset + offset;
}
}
else if ( overRight > 0 ) {
newOverLeft = position.left - data.collisionPosition.marginLeft + myOffset + atOffset + offset - offsetLeft;
if ( newOverLeft > 0 || abs( newOverLeft ) < overRight ) {
position.left += myOffset + atOffset + offset;
}
}
},
top: function( position, data ) {
var within = data.within,
withinOffset = within.offset.top + within.scrollTop,
outerHeight = within.height,
offsetTop = within.isWindow ? within.scrollTop : within.offset.top,
collisionPosTop = position.top - data.collisionPosition.marginTop,
overTop = collisionPosTop - offsetTop,
overBottom = collisionPosTop + data.collisionHeight - outerHeight - offsetTop,
top = data.my[ 1 ] === "top",
myOffset = top ?
-data.elemHeight :
data.my[ 1 ] === "bottom" ?
data.elemHeight :
0,
atOffset = data.at[ 1 ] === "top" ?
data.targetHeight :
data.at[ 1 ] === "bottom" ?
-data.targetHeight :
0,
offset = -2 * data.offset[ 1 ],
newOverTop,
newOverBottom;
if ( overTop < 0 ) {
newOverBottom = position.top + myOffset + atOffset + offset + data.collisionHeight - outerHeight - withinOffset;
if ( ( position.top + myOffset + atOffset + offset) > overTop && ( newOverBottom < 0 || newOverBottom < abs( overTop ) ) ) {
position.top += myOffset + atOffset + offset;
}
}
else if ( overBottom > 0 ) {
newOverTop = position.top - data.collisionPosition.marginTop + myOffset + atOffset + offset - offsetTop;
if ( ( position.top + myOffset + atOffset + offset) > overBottom && ( newOverTop > 0 || abs( newOverTop ) < overBottom ) ) {
position.top += myOffset + atOffset + offset;
}
}
}
},
flipfit: {
left: function() {
$.ui.position.flip.left.apply( this, arguments );
$.ui.position.fit.left.apply( this, arguments );
},
top: function() {
$.ui.position.flip.top.apply( this, arguments );
$.ui.position.fit.top.apply( this, arguments );
}
}
};
// fraction support test
(function () {
var testElement, testElementParent, testElementStyle, offsetLeft, i,
body = document.getElementsByTagName( "body" )[ 0 ],
div = document.createElement( "div" );
//Create a "fake body" for testing based on method used in jQuery.support
testElement = document.createElement( body ? "div" : "body" );
testElementStyle = {
visibility: "hidden",
width: 0,
height: 0,
border: 0,
margin: 0,
background: "none"
};
if ( body ) {
$.extend( testElementStyle, {
position: "absolute",
left: "-1000px",
top: "-1000px"
});
}
for ( i in testElementStyle ) {
testElement.style[ i ] = testElementStyle[ i ];
}
testElement.appendChild( div );
testElementParent = body || document.documentElement;
testElementParent.insertBefore( testElement, testElementParent.firstChild );
div.style.cssText = "position: absolute; left: 10.7432222px;";
offsetLeft = $( div ).offset().left;
$.support.offsetFractions = offsetLeft > 10 && offsetLeft < 11;
testElement.innerHTML = "";
testElementParent.removeChild( testElement );
})();
}( jQuery ) );

View File

@@ -3,7 +3,9 @@ const isCI = require("is-ci");
module.exports = {
launch: {
headless: isCI,
slowMo: isCI ? null : 20,
defaultViewport: null
slowMo: 55,
defaultViewport: null,
ignoreHTTPSErrors: true,
args: ["--disable-web-security"]
}
};

View File

@@ -39,10 +39,10 @@ module.exports = {
// An object that configures minimum threshold enforcement for coverage results
coverageThreshold: {
global: {
branches: 18,
functions: 22,
lines: 28,
statements: 27
branches: 20,
functions: 24,
lines: 30,
statements: 29.0
}
},

View File

@@ -35,6 +35,7 @@
@NotificationLow: #FFF4CE;
@NotificationHigh: #F9E9B0;
@Purple1: #8A2DA5;
@Dirty: #9b4f96;
@BaseLow: #F2F2F2;
@BaseMediumLow: #E6E6E6;
@@ -54,7 +55,7 @@
@SelectionColor: #3074B0;
@FocusColor: #00bcf2;
@FocusColor: #605e5c;
/******************************************************************************
METRICS
@@ -104,6 +105,7 @@
@newCollectionPaneInputWidth: 300px;
@tooltipTextWidth: 280px;
@sharedCollectionThroughputTooltipTextWidth: 150px;
@mongoWildcardIndexTooltipWidth: 150px;
@addContainerPaneThroughputInfoWidth: 370px;
@optionsInfoWidth: 210px;
@noFixedCollectionsTooltipWidth: 196px;

View File

@@ -1522,6 +1522,10 @@ p {
.tooltipVisible();
}
.infoTooltip a {
color: @AccentHigh;
}
.nowrap {
white-space: nowrap;
}
@@ -1561,6 +1565,10 @@ p {
min-width: @tooltipTextWidth;
}
.mongoWildcardIndexTooltipWidth {
min-width: @mongoWildcardIndexTooltipWidth;
}
.sharedCollectionThroughputTooltipWidth {
min-width: @sharedCollectionThroughputTooltipTextWidth;
}
@@ -1646,7 +1654,7 @@ p {
}
.contextual-pane .collid {
border: 1px solid #bbbbbb;
border: 1px solid #605e5c;
font-size: 10px;
padding: 5px 10px;
color: #000;
@@ -1739,7 +1747,7 @@ input::-webkit-calendar-picker-indicator {
padding-right: 34px;
color: @BaseDark;
overflow-y: auto;
overflow-x: hidden;
overflow-x: auto;
margin: (2 * @MediumSpace) 0px;
}
@@ -2074,7 +2082,8 @@ a:link {
.resourceTreeAndTabs {
display: flex;
flex: 1 1 auto;
overflow: hidden;
overflow-x: auto;
overflow-y: hidden;
height: 100%;
}
@@ -2423,22 +2432,6 @@ a:link {
display: none;
}
::-webkit-input-placeholder {
color: #969696;
}
::-moz-placeholder {
color: #969696;
}
:-ms-input-placeholder {
color: #969696;
}
:-moz-placeholder {
color: #969696;
}
::-ms-expand {
color: #969696;
}
@@ -2988,6 +2981,10 @@ settings-pane {
.enableAnalyticalStorageRadio:nth-child(n+2) {
margin-left: @LargeSpace;
}
.enableAnalyticalStorageRadioLabel {
padding: 0px
}
}
.addCollectionLabel {
@@ -3018,3 +3015,15 @@ settings-pane {
.italic {
font-style: italic;
}
.warningErrorContent a {
color: @AccentMediumHigh
}
.infoBoxContent a {
color: @AccentMediumHigh
}
.collapsibleSection :hover{
cursor: pointer;
}

17459
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -4,28 +4,28 @@
"description": "Cosmos Explorer",
"main": "index.js",
"dependencies": {
"@azure/cosmos": "3.7.4",
"@azure/cosmos": "3.9.0",
"@azure/cosmos-language-service": "0.0.4",
"@jupyterlab/services": "4.2.0",
"@jupyterlab/terminal": "1.2.1",
"@microsoft/applicationinsights-web": "2.5.4",
"@nteract/commutable": "7.1.4",
"@nteract/connected-components": "6.7.8",
"@nteract/core": "13.0.0",
"@jupyterlab/services": "6.0.0-rc.2",
"@jupyterlab/terminal": "3.0.0-rc.2",
"@microsoft/applicationinsights-web": "2.5.9",
"@nteract/commutable": "7.3.2",
"@nteract/connected-components": "6.8.2",
"@nteract/core": "15.1.0",
"@nteract/data-explorer": "8.0.3",
"@nteract/directory-listing": "2.0.6",
"@nteract/dropdown-menu": "1.0.1",
"@nteract/editor": "9.6.6",
"@nteract/editor": "10.1.2",
"@nteract/fixtures": "2.3.0",
"@nteract/iron-icons": "1.0.0",
"@nteract/jupyter-widgets": "2.0.0",
"@nteract/logos": "1.0.0",
"@nteract/markdown": "4.4.0",
"@nteract/monaco-editor": "3.0.3",
"@nteract/monaco-editor": "3.2.0",
"@nteract/octicons": "2.0.0",
"@nteract/outputs": "3.0.9",
"@nteract/presentational-components": "3.0.7",
"@nteract/stateful-components": "1.4.0",
"@nteract/stateful-components": "1.7.0",
"@nteract/styles": "2.0.2",
"@nteract/transform-geojson": "5.1.8",
"@nteract/transform-model-debug": "5.0.1",
@@ -42,11 +42,12 @@
"applicationinsights": "1.8.0",
"babel-polyfill": "6.26.0",
"bootstrap": "3.4.1",
"canvas": "2.6.0",
"canvas": "2.6.1",
"clean-webpack-plugin": "0.1.19",
"copy-webpack-plugin": "6.0.2",
"crossroads": "0.12.2",
"css-element-queries": "1.1.1",
"d3": "6.1.1",
"datatables.net-colreorder-dt": "1.5.1",
"datatables.net-dt": "1.10.19",
"date-fns": "1.29.0",
@@ -55,7 +56,9 @@
"es6-object-assign": "1.1.0",
"es6-symbol": "3.1.3",
"eslint-plugin-jest": "23.13.2",
"eslint-plugin-react": "7.20.0",
"hasher": "1.2.0",
"html2canvas": "1.0.0-rc.5",
"immutable": "4.0.0-rc.12",
"is-ci": "2.0.0",
"jquery": "3.5.1",
@@ -65,12 +68,13 @@
"mkdirp": "1.0.4",
"monaco-editor": "0.15.6",
"object.entries": "1.1.0",
"office-ui-fabric-react": "7.121.10",
"office-ui-fabric-react": "7.134.1",
"p-retry": "4.2.0",
"plotly.js-cartesian-dist-min": "1.52.3",
"promise-polyfill": "8.1.0",
"promise.prototype.finally": "3.1.0",
"q": "1.5.1",
"react": "16.9.0",
"react": "16.13.1",
"react-animate-height": "2.0.8",
"react-dnd": "9.4.0",
"react-dnd-html5-backend": "9.4.0",
@@ -79,12 +83,13 @@
"react-notification-system": "0.2.17",
"react-redux": "7.1.3",
"redux": "4.0.4",
"rx-jupyter": "5.5.2",
"rxjs": "6.5.3",
"rx-jupyter": "5.5.12",
"rxjs": "6.6.3",
"styled-components": "4.3.2",
"text-encoding": "0.7.0",
"underscore": "1.9.1",
"url-polyfill": "1.1.7",
"utility-types": "3.10.0",
"webcrypto-liner": "1.1.4",
"webfontloader": "1.6.28",
"whatwg-fetch": "3.0.0"
@@ -97,17 +102,20 @@
"@types/applicationinsights-js": "1.0.7",
"@types/codemirror": "0.0.56",
"@types/crossroads": "0.0.30",
"@types/d3": "4.13.2",
"@types/enzyme": "3.10.3",
"@types/enzyme-adapter-react-16": "1.0.5",
"@types/d3": "5.9.2",
"@types/enzyme": "3.10.7",
"@types/enzyme-adapter-react-16": "1.0.6",
"@types/expect-puppeteer": "4.4.3",
"@types/hasher": "0.0.31",
"@types/jest": "23.3.10",
"@types/jest-environment-puppeteer": "4.3.2",
"@types/memoize-one": "4.1.1",
"@types/node": "12.11.1",
"@types/promise.prototype.finally": "2.0.3",
"@types/prop-types": "15.5.8",
"@types/puppeteer": "3.0.1",
"@types/q": "1.5.1",
"@types/react": "16.8.25",
"@types/react": "16.9.49",
"@types/react-dom": "16.0.7",
"@types/react-notification-system": "0.2.39",
"@types/react-redux": "7.1.7",
@@ -116,8 +124,8 @@
"@types/text-encoding": "0.0.33",
"@types/underscore": "1.7.36",
"@types/webfontloader": "1.6.29",
"@typescript-eslint/eslint-plugin": "3.2.0",
"@typescript-eslint/parser": "3.2.0",
"@typescript-eslint/eslint-plugin": "4.0.1",
"@typescript-eslint/parser": "4.0.1",
"adal-angular": "1.0.15",
"axe-puppeteer": "1.1.0",
"babel-jest": "24.9.0",
@@ -126,14 +134,13 @@
"case-sensitive-paths-webpack-plugin": "2.3.0",
"create-file-webpack": "1.0.2",
"css-loader": "1.0.0",
"d3": "4.13.0",
"enzyme": "3.10.0",
"enzyme-adapter-react-16": "1.15.1",
"enzyme-to-json": "3.4.3",
"eslint": "7.3.1",
"enzyme": "3.11.0",
"enzyme-adapter-react-16": "1.15.5",
"enzyme-to-json": "3.6.1",
"eslint": "7.8.1",
"eslint-cli": "1.1.1",
"eslint-plugin-no-null": "1.0.2",
"eslint-plugin-react": "7.20.0",
"eslint-plugin-prefer-arrow": "1.2.2",
"expose-loader": "0.7.5",
"file-loader": "2.0.0",
"fs-extra": "7.0.0",
@@ -150,7 +157,7 @@
"less-vars-loader": "1.1.0",
"mini-css-extract-plugin": "0.4.3",
"monaco-editor-webpack-plugin": "1.7.0",
"node-fetch": "2.6.0",
"node-fetch": "2.6.1",
"prettier": "1.19.1",
"puppeteer": "4.0.0",
"raw-loader": "0.5.1",
@@ -161,8 +168,9 @@
"ts-loader": "6.2.2",
"tslint": "5.11.0",
"tslint-microsoft-contrib": "6.0.0",
"typescript": "3.9.6",
"typescript": "4.0.2",
"url-loader": "1.1.1",
"wait-on": "4.0.2",
"webpack": "4.43.0",
"webpack-bundle-analyzer": "3.6.1",
"webpack-cli": "3.3.10",
@@ -181,12 +189,13 @@
"test": "rimraf coverage && jest",
"test:e2e": "jest -c ./jest.config.e2e.js --detectOpenHandles",
"watch": "npm run start",
"wait-for-server": "wait-on -t 240000 -i 5000 -v https-get://0.0.0.0:1234/",
"build:ase": "gulp build:ase",
"compile": "tsc",
"compile:contracts": "tsc -p ./tsconfig.contracts.json",
"compile:strict": "tsc -p ./tsconfig.strict.json",
"format": "prettier --write \"{src,cypress}/**/*.{ts,tsx,html}\" \"*.{js,html}\"",
"format:check": "prettier --check \"{src,cypress}/**/*.{ts,tsx,html}\" \"*.{js,html}\"",
"format": "prettier --write \"{src,cypress,test}/**/*.{ts,tsx,html}\" \"*.{js,html}\"",
"format:check": "prettier --check \"{src,cypress,test}/**/*.{ts,tsx,html}\" \"*.{js,html}\"",
"lint": "tslint --project tsconfig.json && eslint \"**/*.{ts,tsx}\"",
"build:contracts": "npm run compile:contracts",
"strictEligibleFiles": "node ./strict-migration-tools/index.js",

View File

@@ -3,7 +3,6 @@
"offerThroughput": 400,
"databaseLevelThroughput": false,
"collectionId": "Persons",
"rupmEnabled": false,
"partitionKey": { "kind": "Hash", "paths": ["/name"] },
"data": [
"g.addV('person').property(id, '1').property('name', 'Eva').property('age', 44)",

View File

@@ -3,8 +3,8 @@
"offerThroughput": 400,
"databaseLevelThroughput": false,
"collectionId": "Persons",
"rupmEnabled": false,
"partitionKey": { "kind": "Hash", "paths": ["/firstname"] },
"createNewDatabase": true,
"partitionKey": { "kind": "Hash", "paths": ["/firstname"], "version": 1 },
"data": [
{
"firstname": "Eva",

View File

@@ -1,13 +0,0 @@
import * as ViewModels from "../Contracts/ViewModels";
export class DefaultApi implements ViewModels.CosmosDbApi {
public isSystemDatabasePredicate = (database: ViewModels.Database): boolean => {
return false;
};
}
export class CassandraApi implements ViewModels.CosmosDbApi {
public isSystemDatabasePredicate = (database: ViewModels.Database): boolean => {
return database.id() === "system";
};
}

View File

@@ -1,5 +1,3 @@
import { AutopilotTier } from "../Contracts/DataModels";
import { config } from "../Config";
import { HashMap } from "./HashMap";
export class AuthorizationEndpoints {
@@ -7,14 +5,17 @@ export class AuthorizationEndpoints {
public static common: string = "https://login.windows.net/";
}
export class BackendEndpoints {
public static localhost: string = "https://localhost:12900";
public static dev: string = "https://ext.documents-dev.windows-int.net";
public static productionPortal: string = config.BACKEND_ENDPOINT || "https://main.documentdb.ext.azure.com";
export class CodeOfConductEndpoints {
public static privacyStatement: string = "https://aka.ms/ms-privacy-policy";
public static codeOfConduct: string = "https://aka.ms/cosmos-code-of-conduct";
public static termsOfUse: string = "https://aka.ms/ms-terms-of-use";
}
export class EndpointsRegex {
public static readonly cassandra = "AccountEndpoint=(.*).cassandra.cosmosdb.azure.com";
public static readonly cassandra = [
"AccountEndpoint=(.*).cassandra.cosmosdb.azure.com",
"HostName=(.*).cassandra.cosmos.azure.com"
];
public static readonly mongo = "mongodb://.*:(.*)@(.*).documents.azure.com";
public static readonly mongoCompute = "mongodb://.*:(.*)@(.*).mongo.cosmos.azure.com";
public static readonly sql = "AccountEndpoint=https://(.*).documents.azure.com";
@@ -101,17 +102,19 @@ export class CapabilityNames {
public static readonly EnableNotebooks: string = "EnableNotebooks";
public static readonly EnableStorageAnalytics: string = "EnableStorageAnalytics";
public static readonly EnableMongo: string = "EnableMongo";
public static readonly EnableServerless: string = "EnableServerless";
}
export class Features {
public static readonly cosmosdb = "cosmosdb";
public static readonly enableChangeFeedPolicy = "enablechangefeedpolicy";
public static readonly enableRupm = "enablerupm";
public static readonly executeSproc = "dataexplorerexecutesproc";
public static readonly hostedDataExplorer = "hosteddataexplorerenabled";
public static readonly enableTtl = "enablettl";
public static readonly enableNotebooks = "enablenotebooks";
public static readonly enableGalleryPublish = "enablegallerypublish";
public static readonly enableCodeOfConduct = "enablecodeofconduct";
public static readonly enableLinkInjection = "enablelinkinjection";
public static readonly enableSpark = "enablespark";
public static readonly livyEndpoint = "livyendpoint";
public static readonly notebookServerUrl = "notebookserverurl";
@@ -119,9 +122,15 @@ export class Features {
public static readonly notebookBasePath = "notebookbasepath";
public static readonly canExceedMaximumValue = "canexceedmaximumvalue";
public static readonly enableFixedCollectionWithSharedThroughput = "enablefixedcollectionwithsharedthroughput";
public static readonly enableAutoPilotV2 = "enableautopilotv2";
public static readonly ttl90Days = "ttl90days";
public static readonly enableRightPanelV2 = "enablerightpanelv2";
public static readonly enableSDKoperations = "enablesdkoperations";
}
// flight names returned from the portal are always lowercase
export class Flights {
public static readonly SettingsV2 = "settingsv2";
public static readonly MongoIndexEditor = "mongoindexeditor";
}
export class AfecFeatures {
@@ -157,89 +166,8 @@ export enum MongoBackendEndpointType {
remote
}
export class MongoBackend {
public static localhostEndpoint: string = "/api/mongo/explorer";
public static centralUsEndpoint: string = "https://main.documentdb.ext.azure.com/api/mongo/explorer";
public static northEuropeEndpoint: string = "https://main.documentdb.ext.azure.com/api/mongo/explorer";
public static southEastAsiaEndpoint: string = "https://main.documentdb.ext.azure.com/api/mongo/explorer";
public static endpointsByRegion: any = {
default: MongoBackend.centralUsEndpoint,
northeurope: MongoBackend.northEuropeEndpoint,
ukwest: MongoBackend.northEuropeEndpoint,
uksouth: MongoBackend.northEuropeEndpoint,
westeurope: MongoBackend.northEuropeEndpoint,
australiaeast: MongoBackend.southEastAsiaEndpoint,
australiasoutheast: MongoBackend.southEastAsiaEndpoint,
centralindia: MongoBackend.southEastAsiaEndpoint,
eastasia: MongoBackend.southEastAsiaEndpoint,
japaneast: MongoBackend.southEastAsiaEndpoint,
japanwest: MongoBackend.southEastAsiaEndpoint,
koreacentral: MongoBackend.southEastAsiaEndpoint,
koreasouth: MongoBackend.southEastAsiaEndpoint,
southeastasia: MongoBackend.southEastAsiaEndpoint,
southindia: MongoBackend.southEastAsiaEndpoint,
westindia: MongoBackend.southEastAsiaEndpoint
};
public static endpointsByEnvironment: any = {
default: MongoBackendEndpointType.local,
localhost: MongoBackendEndpointType.local,
prod1: MongoBackendEndpointType.remote,
prod2: MongoBackendEndpointType.remote
};
}
// TODO: 435619 Add default endpoints per cloud and use regional only when available
export class CassandraBackend {
public static readonly localhostEndpoint: string = "https://localhost:12901/";
public static readonly devEndpoint: string = "https://platformproxycassandradev.azurewebsites.net/";
public static readonly centralUsEndpoint: string = "https://main.documentdb.ext.azure.com/";
public static readonly northEuropeEndpoint: string = "https://main.documentdb.ext.azure.com/";
public static readonly southEastAsiaEndpoint: string = "https://main.documentdb.ext.azure.com/";
public static readonly bf_default: string = "https://main.documentdb.ext.microsoftazure.de/";
public static readonly mc_default: string = "https://main.documentdb.ext.azure.cn/";
public static readonly ff_default: string = "https://main.documentdb.ext.azure.us/";
public static readonly endpointsByRegion: any = {
default: CassandraBackend.centralUsEndpoint,
northeurope: CassandraBackend.northEuropeEndpoint,
ukwest: CassandraBackend.northEuropeEndpoint,
uksouth: CassandraBackend.northEuropeEndpoint,
westeurope: CassandraBackend.northEuropeEndpoint,
australiaeast: CassandraBackend.southEastAsiaEndpoint,
australiasoutheast: CassandraBackend.southEastAsiaEndpoint,
centralindia: CassandraBackend.southEastAsiaEndpoint,
eastasia: CassandraBackend.southEastAsiaEndpoint,
japaneast: CassandraBackend.southEastAsiaEndpoint,
japanwest: CassandraBackend.southEastAsiaEndpoint,
koreacentral: CassandraBackend.southEastAsiaEndpoint,
koreasouth: CassandraBackend.southEastAsiaEndpoint,
southeastasia: CassandraBackend.southEastAsiaEndpoint,
southindia: CassandraBackend.southEastAsiaEndpoint,
westindia: CassandraBackend.southEastAsiaEndpoint,
// Black Forest
germanycentral: CassandraBackend.bf_default,
germanynortheast: CassandraBackend.bf_default,
// Fairfax
usdodeast: CassandraBackend.ff_default,
usdodcentral: CassandraBackend.ff_default,
usgovarizona: CassandraBackend.ff_default,
usgoviowa: CassandraBackend.ff_default,
usgovtexas: CassandraBackend.ff_default,
usgovvirginia: CassandraBackend.ff_default,
// Mooncake
chinaeast: CassandraBackend.mc_default,
chinaeast2: CassandraBackend.mc_default,
chinanorth: CassandraBackend.mc_default,
chinanorth2: CassandraBackend.mc_default
};
public static readonly createOrDeleteApi: string = "api/cassandra/createordelete";
public static readonly guestCreateOrDeleteApi: string = "api/guest/cassandra/createordelete";
public static readonly queryApi: string = "api/cassandra";
@@ -249,12 +177,6 @@ export class CassandraBackend {
public static readonly schemaApi: string = "api/cassandra/schema";
public static readonly guestSchemaApi: string = "api/guest/cassandra/schema";
}
export class RUPMStates {
public static on: string = "on";
public static off: string = "off";
}
export class Queries {
public static CustomPageOption: string = "custom";
public static UnlimitedPageOption: string = "unlimited";
@@ -331,7 +253,6 @@ export class HttpHeaders {
public static usePolygonsSmallerThanAHemisphere = "x-ms-documentdb-usepolygonssmallerthanahemisphere";
public static autoPilotThroughput = "autoscaleSettings";
public static autoPilotThroughputSDK = "x-ms-cosmos-offer-autopilot-settings";
public static autoPilotTier = "x-ms-cosmos-offer-autopilot-tier";
public static partitionKey: string = "x-ms-documentdb-partitionkey";
public static migrateOfferToManualThroughput: string = "x-ms-cosmos-migrate-offer-to-manual-throughput";
public static migrateOfferToAutopilot: string = "x-ms-cosmos-migrate-offer-to-autopilot";
@@ -476,54 +397,6 @@ export enum ConflictOperationType {
Delete = "delete"
}
export class AutoPilot {
public static tier1Text: string = "4,000 RU/s";
public static tier2Text: string = "20,000 RU/s";
public static tier3Text: string = "100,000 RU/s";
public static tier4Text: string = "500,000 RU/s";
public static tierText = {
[AutopilotTier.Tier1]: "Tier 1",
[AutopilotTier.Tier2]: "Tier 2",
[AutopilotTier.Tier3]: "Tier 3",
[AutopilotTier.Tier4]: "Tier 4"
};
public static tierMaxRus = {
[AutopilotTier.Tier1]: 2000,
[AutopilotTier.Tier2]: 20000,
[AutopilotTier.Tier3]: 100000,
[AutopilotTier.Tier4]: 500000
};
public static tierMinRus = {
[AutopilotTier.Tier1]: 0,
[AutopilotTier.Tier2]: 0,
[AutopilotTier.Tier3]: 0,
[AutopilotTier.Tier4]: 0
};
public static tierStorageInGB = {
[AutopilotTier.Tier1]: 50,
[AutopilotTier.Tier2]: 200,
[AutopilotTier.Tier3]: 1000,
[AutopilotTier.Tier4]: 5000
};
}
export class DataExplorerVersions {
public static readonly v_1_0_0: string = "1.0.0";
public static readonly v_1_0_1: string = "1.0.1";
}
export class DataExplorerFeatures {
public static offerCache: string = "OfferCache";
}
export const DataExplorerFeaturesVersions: any = {
OfferCache: DataExplorerVersions.v_1_0_1
};
export const EmulatorMasterKey =
//[SuppressMessage("Microsoft.Security", "CS002:SecretInNextLine", Justification="Well known public masterKey for emulator")]
"C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==";
@@ -549,3 +422,11 @@ export class AnalyticalStorageTtl {
public static readonly Infinite: number = -1;
public static readonly Disabled: number = 0;
}
export class TerminalQueryParams {
public static readonly Terminal = "terminal";
public static readonly Server = "server";
public static readonly Token = "token";
public static readonly SubscriptionId = "subscriptionId";
public static readonly TerminalEndpoint = "terminalEndpoint";
}

View File

@@ -1,6 +1,7 @@
import { CosmosClient, tokenProvider, endpoint, requestPlugin, getTokenFromAuthService } from "./CosmosClient";
import { ResourceType } from "@azure/cosmos/dist-esm/common/constants";
import { config, Platform } from "../Config";
import { configContext, Platform, updateConfigContext, resetConfigContext } from "../ConfigContext";
import { updateUserContext } from "../UserContext";
import { endpoint, getTokenFromAuthService, requestPlugin, tokenProvider } from "./CosmosClient";
describe("tokenProvider", () => {
const options = {
@@ -13,7 +14,9 @@ describe("tokenProvider", () => {
};
beforeEach(() => {
window.dataExplorer = { extensionEndpoint: () => "https://main.documentdb.ext.azure.com" } as any;
updateConfigContext({
BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com"
});
window.fetch = jest.fn().mockImplementation(() => {
return {
json: () => "{}",
@@ -32,7 +35,9 @@ describe("tokenProvider", () => {
});
it("does not call the auth service if a master key is set", async () => {
CosmosClient.masterKey("foo");
updateUserContext({
masterKey: "foo"
});
await tokenProvider(options);
expect((window.fetch as any).mock.calls.length).toBe(0);
});
@@ -41,7 +46,7 @@ describe("tokenProvider", () => {
describe("getTokenFromAuthService", () => {
beforeEach(() => {
delete window.dataExplorer;
delete config.BACKEND_ENDPOINT;
resetConfigContext();
window.fetch = jest.fn().mockImplementation(() => {
return {
json: () => "{}",
@@ -55,7 +60,9 @@ describe("getTokenFromAuthService", () => {
});
it("builds the correct URL in production", () => {
window.dataExplorer = { extensionEndpoint: () => "https://main.documentdb.ext.azure.com" } as any;
updateConfigContext({
BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com"
});
getTokenFromAuthService("GET", "dbs", "foo");
expect(window.fetch).toHaveBeenCalledWith(
"https://main.documentdb.ext.azure.com/api/guest/runtimeproxy/authorizationTokens",
@@ -64,7 +71,9 @@ describe("getTokenFromAuthService", () => {
});
it("builds the correct URL in dev", () => {
config.BACKEND_ENDPOINT = "https://localhost:1234";
updateConfigContext({
BACKEND_ENDPOINT: "https://localhost:1234"
});
getTokenFromAuthService("GET", "dbs", "foo");
expect(window.fetch).toHaveBeenCalledWith(
"https://localhost:1234/api/guest/runtimeproxy/authorizationTokens",
@@ -75,7 +84,8 @@ describe("getTokenFromAuthService", () => {
describe("endpoint", () => {
it("falls back to _databaseAccount", () => {
CosmosClient.databaseAccount({
updateUserContext({
databaseAccount: {
id: "foo",
name: "foo",
location: "foo",
@@ -88,29 +98,31 @@ describe("endpoint", () => {
tableEndpoint: "foo",
cassandraEndpoint: "foo"
}
}
});
expect(endpoint()).toEqual("bar");
});
it("uses _endpoint if set", () => {
CosmosClient.endpoint("baz");
updateUserContext({
endpoint: "baz"
});
expect(endpoint()).toEqual("baz");
});
});
describe("requestPlugin", () => {
beforeEach(() => {
delete window.dataExplorerPlatform;
delete config.PROXY_PATH;
delete config.BACKEND_ENDPOINT;
delete config.PROXY_PATH;
resetConfigContext();
});
describe("Hosted", () => {
it("builds a proxy URL in development", () => {
const next = jest.fn();
config.platform = Platform.Hosted;
config.BACKEND_ENDPOINT = "https://localhost:1234";
config.PROXY_PATH = "/proxy";
updateConfigContext({
platform: Platform.Hosted,
BACKEND_ENDPOINT: "https://localhost:1234",
PROXY_PATH: "/proxy"
});
const headers = {};
const endpoint = "https://docs.azure.com";
const path = "/dbs/foo";
@@ -122,8 +134,7 @@ describe("requestPlugin", () => {
describe("Emulator", () => {
it("builds a url for emulator proxy via webpack", () => {
const next = jest.fn();
config.platform = Platform.Emulator;
config.PROXY_PATH = "/proxy";
updateConfigContext({ platform: Platform.Emulator, PROXY_PATH: "/proxy" });
const headers = {};
const endpoint = "";
const path = "/dbs/foo";

View File

@@ -1,39 +1,28 @@
import * as Cosmos from "@azure/cosmos";
import { RequestInfo, setAuthorizationTokenHeaderUsingMasterKey } from "@azure/cosmos";
import { DatabaseAccount } from "../Contracts/DataModels";
import { HttpHeaders, EmulatorMasterKey } from "./Constants";
import { NotificationConsoleUtils } from "../Utils/NotificationConsoleUtils";
import { ConsoleDataType } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
import { config, Platform } from "../Config";
let _client: Cosmos.CosmosClient;
let _masterKey: string;
let _endpoint: string;
let _authorizationToken: string;
let _accessToken: string;
let _databaseAccount: DatabaseAccount;
let _subscriptionId: string;
let _resourceGroup: string;
let _resourceToken: string;
import { configContext, Platform } from "../ConfigContext";
import { logConsoleError } from "../Utils/NotificationConsoleUtils";
import { EmulatorMasterKey, HttpHeaders } from "./Constants";
import { userContext } from "../UserContext";
const _global = typeof self === "undefined" ? window : self;
export const tokenProvider = async (requestInfo: RequestInfo) => {
const { verb, resourceId, resourceType, headers } = requestInfo;
if (config.platform === Platform.Emulator) {
if (configContext.platform === Platform.Emulator) {
// TODO This SDK method mutates the headers object. Find a better one or fix the SDK.
await setAuthorizationTokenHeaderUsingMasterKey(verb, resourceId, resourceType, headers, EmulatorMasterKey);
return decodeURIComponent(headers.authorization);
}
if (_masterKey) {
if (userContext.masterKey) {
// TODO This SDK method mutates the headers object. Find a better one or fix the SDK.
await setAuthorizationTokenHeaderUsingMasterKey(verb, resourceId, resourceType, headers, EmulatorMasterKey);
return decodeURIComponent(headers.authorization);
}
if (_resourceToken) {
return _resourceToken;
if (userContext.resourceToken) {
return userContext.resourceToken;
}
const result = await getTokenFromAuthService(verb, resourceType, resourceId);
@@ -42,28 +31,33 @@ export const tokenProvider = async (requestInfo: RequestInfo) => {
};
export const requestPlugin: Cosmos.Plugin<any> = async (requestContext, next) => {
requestContext.endpoint = config.PROXY_PATH;
requestContext.endpoint = configContext.PROXY_PATH;
requestContext.headers["x-ms-proxy-target"] = endpoint();
return next(requestContext);
};
export const endpoint = () => {
if (config.platform === Platform.Emulator) {
if (configContext.platform === Platform.Emulator) {
// In worker scope, _global(self).parent does not exist
const location = _global.parent ? _global.parent.location : _global.location;
return config.EMULATOR_ENDPOINT || location.origin;
return configContext.EMULATOR_ENDPOINT || location.origin;
}
return _endpoint || (_databaseAccount && _databaseAccount.properties && _databaseAccount.properties.documentEndpoint);
return (
userContext.endpoint ||
(userContext.databaseAccount &&
userContext.databaseAccount.properties &&
userContext.databaseAccount.properties.documentEndpoint)
);
};
export async function getTokenFromAuthService(verb: string, resourceType: string, resourceId?: string): Promise<any> {
try {
const host = config.BACKEND_ENDPOINT || _global.dataExplorer.extensionEndpoint();
const host = configContext.BACKEND_ENDPOINT;
const response = await _global.fetch(host + "/api/guest/runtimeproxy/authorizationTokens", {
method: "POST",
headers: {
"content-type": "application/json",
"x-ms-encrypted-auth-token": _accessToken
"x-ms-encrypted-auth-token": userContext.accessToken
},
body: JSON.stringify({
verb,
@@ -75,22 +69,15 @@ export async function getTokenFromAuthService(verb: string, resourceType: string
const result = JSON.parse(await response.json());
return result;
} catch (error) {
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error,
`Failed to get authorization headers for ${resourceType}: ${JSON.stringify(error)}`
);
logConsoleError(`Failed to get authorization headers for ${resourceType}: ${error.message}`);
return Promise.reject(error);
}
}
export const CosmosClient = {
client(): Cosmos.CosmosClient {
if (_client) {
return _client;
}
export function client(): Cosmos.CosmosClient {
const options: Cosmos.CosmosClientOptions = {
endpoint: endpoint() || " ", // CosmosClient gets upset if we pass a falsy value here
key: _masterKey,
key: userContext.masterKey,
tokenProvider,
connectionPolicy: {
enableEndpointDiscovery: false
@@ -98,83 +85,8 @@ export const CosmosClient = {
userAgentSuffix: "Azure Portal"
};
// In development we proxy requests to the backend via webpack. This is removed in production bundles.
if (process.env.NODE_ENV === "development") {
if (configContext.PROXY_PATH !== undefined) {
(options as any).plugins = [{ on: "request", plugin: requestPlugin }];
}
_client = new Cosmos.CosmosClient(options);
return _client;
},
authorizationToken(value?: string): string {
if (typeof value === "undefined") {
return _authorizationToken;
}
_authorizationToken = value;
_client = null;
return value;
},
accessToken(value?: string): string {
if (typeof value === "undefined") {
return _accessToken;
}
_accessToken = value;
_client = null;
return value;
},
masterKey(value?: string): string {
if (typeof value === "undefined") {
return _masterKey;
}
_client = null;
_masterKey = value;
return value;
},
endpoint(value?: string): string {
if (typeof value === "undefined") {
return _endpoint;
}
_client = null;
_endpoint = value;
return value;
},
databaseAccount(value?: DatabaseAccount): DatabaseAccount {
if (typeof value === "undefined") {
return _databaseAccount || ({} as any);
}
_client = null;
_databaseAccount = value;
return value;
},
subscriptionId(value?: string): string {
if (typeof value === "undefined") {
return _subscriptionId;
}
_client = null;
_subscriptionId = value;
return value;
},
resourceGroup(value?: string): string {
if (typeof value === "undefined") {
return _resourceGroup;
}
_client = null;
_resourceGroup = value;
return value;
},
resourceToken(value?: string): string {
if (typeof value === "undefined") {
return _resourceToken;
}
_client = null;
_resourceToken = value;
return value;
}
};
return new Cosmos.CosmosClient(options);
}

View File

@@ -1,30 +1,26 @@
import * as _ from "underscore";
import * as Constants from "./Constants";
import * as DataModels from "../Contracts/DataModels";
import * as HeadersUtility from "./HeadersUtility";
import * as ViewModels from "../Contracts/ViewModels";
import Q from "q";
import {
ConflictDefinition,
ContainerDefinition,
ContainerResponse,
DatabaseResponse,
FeedOptions,
ItemDefinition,
PartitionKeyDefinition,
OfferDefinition,
QueryIterator,
Resource,
TriggerDefinition
Resource
} from "@azure/cosmos";
import { ContainerRequest } from "@azure/cosmos/dist-esm/client/Container/ContainerRequest";
import { CosmosClient } from "./CosmosClient";
import { DatabaseRequest } from "@azure/cosmos/dist-esm/client/Database/DatabaseRequest";
import { LocalStorageUtility, StorageKey } from "../Shared/StorageUtility";
import { MessageHandler } from "./MessageHandler";
import { MessageTypes } from "../Contracts/ExplorerContracts";
import { OfferUtils } from "../Utils/OfferUtils";
import { RequestOptions } from "@azure/cosmos/dist-esm";
import Q from "q";
import { configContext, Platform } from "../ConfigContext";
import * as DataModels from "../Contracts/DataModels";
import { MessageTypes } from "../Contracts/ExplorerContracts";
import * as ViewModels from "../Contracts/ViewModels";
import ConflictId from "../Explorer/Tree/ConflictId";
import DocumentId from "../Explorer/Tree/DocumentId";
import StoredProcedure from "../Explorer/Tree/StoredProcedure";
import { LocalStorageUtility, StorageKey } from "../Shared/StorageUtility";
import { OfferUtils } from "../Utils/OfferUtils";
import * as Constants from "./Constants";
import { client } from "./CosmosClient";
import * as HeadersUtility from "./HeadersUtility";
import { sendCachedDataMessage } from "./MessageHandler";
export function getCommonQueryOptions(options: FeedOptions): any {
const storedItemPerPageSetting: number = LocalStorageUtility.getEntryNumber(StorageKey.ActualItemPerPage);
@@ -43,114 +39,66 @@ export function getCommonQueryOptions(options: FeedOptions): any {
return options;
}
// TODO: Add timeout for all promises
export abstract class DataAccessUtilityBase {
public queryDocuments(
export function queryDocuments(
databaseId: string,
containerId: string,
query: string,
options: any
): Q.Promise<QueryIterator<ItemDefinition & Resource>> {
): Q.Promise<QueryIterator<ItemDefinition & Resource>> {
options = getCommonQueryOptions(options);
const documentsIterator = CosmosClient.client()
const documentsIterator = client()
.database(databaseId)
.container(containerId)
.items.query(query, options);
return Q(documentsIterator);
}
export function getPartitionKeyHeaderForConflict(conflictId: ConflictId): Object {
const partitionKeyDefinition: DataModels.PartitionKey = conflictId.partitionKey;
const partitionKeyValue: any = conflictId.partitionKeyValue;
return getPartitionKeyHeader(partitionKeyDefinition, partitionKeyValue);
}
export function getPartitionKeyHeader(partitionKeyDefinition: DataModels.PartitionKey, partitionKeyValue: any): Object {
if (!partitionKeyDefinition) {
return undefined;
}
public readStoredProcedures(
collection: ViewModels.Collection,
options?: any
): Q.Promise<DataModels.StoredProcedure[]> {
return Q(
CosmosClient.client()
.database(collection.databaseId)
.container(collection.id())
.scripts.storedProcedures.readAll(options)
.fetchAll()
.then(response => response.resources as DataModels.StoredProcedure[])
);
if (partitionKeyValue === undefined) {
return [{}];
}
public readStoredProcedure(
collection: ViewModels.Collection,
requestedResource: DataModels.Resource,
options?: any
): Q.Promise<DataModels.StoredProcedure> {
return Q(
CosmosClient.client()
.database(collection.databaseId)
.container(collection.id())
.scripts.storedProcedure(requestedResource.id)
.read(options)
.then(response => response.resource as DataModels.StoredProcedure)
);
}
public readUserDefinedFunctions(
collection: ViewModels.Collection,
options: any
): Q.Promise<DataModels.UserDefinedFunction[]> {
return Q(
CosmosClient.client()
.database(collection.databaseId)
.container(collection.id())
.scripts.userDefinedFunctions.readAll(options)
.fetchAll()
.then(response => response.resources as DataModels.UserDefinedFunction[])
);
}
public readUserDefinedFunction(
collection: ViewModels.Collection,
requestedResource: DataModels.Resource,
options?: any
): Q.Promise<DataModels.UserDefinedFunction> {
return Q(
CosmosClient.client()
.database(collection.databaseId)
.container(collection.id())
.scripts.userDefinedFunction(requestedResource.id)
.read(options)
.then(response => response.resource as DataModels.UserDefinedFunction)
);
}
return [partitionKeyValue];
}
export function updateDocument(
collection: ViewModels.CollectionBase,
documentId: DocumentId,
newDocument: any
): Q.Promise<any> {
const partitionKey = documentId.partitionKeyValue;
public readTriggers(collection: ViewModels.Collection, options: any): Q.Promise<DataModels.Trigger[]> {
return Q(
CosmosClient.client()
client()
.database(collection.databaseId)
.container(collection.id())
.scripts.triggers.readAll(options)
.fetchAll()
.then(response => response.resources as DataModels.Trigger[])
.item(documentId.id(), partitionKey)
.replace(newDocument)
.then(response => response.resource)
);
}
}
public readTrigger(
collection: ViewModels.Collection,
requestedResource: DataModels.Resource,
options?: any
): Q.Promise<DataModels.Trigger> {
return Q(
CosmosClient.client()
.database(collection.databaseId)
.container(collection.id())
.scripts.trigger(requestedResource.id)
.read(options)
.then(response => response.resource as DataModels.Trigger)
);
}
public executeStoredProcedure(
export function executeStoredProcedure(
collection: ViewModels.Collection,
storedProcedure: StoredProcedure,
partitionKeyValue: any,
params: any[]
): Q.Promise<any> {
): Q.Promise<any> {
// TODO remove this deferred. Kept it because of timeout code at bottom of function
const deferred = Q.defer<any>();
CosmosClient.client()
client()
.database(collection.databaseId)
.container(collection.id())
.scripts.storedProcedure(storedProcedure.id())
@@ -167,505 +115,68 @@ export abstract class DataAccessUtilityBase {
Constants.ClientDefaults.requestTimeoutMs,
`Request timed out while executing stored procedure ${storedProcedure.id()}`
);
}
}
public readDocument(collection: ViewModels.CollectionBase, documentId: ViewModels.DocumentId): Q.Promise<any> {
export function createDocument(collection: ViewModels.CollectionBase, newDocument: any): Q.Promise<any> {
return Q(
client()
.database(collection.databaseId)
.container(collection.id())
.items.create(newDocument)
.then(response => response.resource)
);
}
export function readDocument(collection: ViewModels.CollectionBase, documentId: DocumentId): Q.Promise<any> {
const partitionKey = documentId.partitionKeyValue;
return Q(
CosmosClient.client()
client()
.database(collection.databaseId)
.container(collection.id())
.item(documentId.id(), partitionKey)
.read()
.then(response => response.resource)
);
}
}
public getPartitionKeyHeaderForConflict(conflictId: ViewModels.ConflictId): Object {
const partitionKeyDefinition: DataModels.PartitionKey = conflictId.partitionKey;
const partitionKeyValue: any = conflictId.partitionKeyValue;
return this.getPartitionKeyHeader(partitionKeyDefinition, partitionKeyValue);
}
public getPartitionKeyHeader(partitionKeyDefinition: DataModels.PartitionKey, partitionKeyValue: any): Object {
if (!partitionKeyDefinition) {
return undefined;
}
if (partitionKeyValue === undefined) {
return [{}];
}
return [partitionKeyValue];
}
public updateCollection(
databaseId: string,
collectionId: string,
newCollection: DataModels.Collection,
options: any = {}
): Q.Promise<DataModels.Collection> {
return Q(
CosmosClient.client()
.database(databaseId)
.container(collectionId)
.replace(newCollection as ContainerDefinition, options)
.then(async (response: ContainerResponse) => {
return this.refreshCachedResources().then(() => response.resource as DataModels.Collection);
})
);
}
public updateDocument(
collection: ViewModels.CollectionBase,
documentId: ViewModels.DocumentId,
newDocument: any
): Q.Promise<any> {
export function deleteDocument(collection: ViewModels.CollectionBase, documentId: DocumentId): Q.Promise<any> {
const partitionKey = documentId.partitionKeyValue;
return Q(
CosmosClient.client()
.database(collection.databaseId)
.container(collection.id())
.item(documentId.id(), partitionKey)
.replace(newDocument)
.then(response => response.resource)
);
}
public updateOffer(
offer: DataModels.Offer,
newOffer: DataModels.Offer,
options?: RequestOptions
): Q.Promise<DataModels.Offer> {
return Q(
CosmosClient.client()
.offer(offer.id)
.replace(newOffer, options)
.then(response => {
return Promise.all([this.refreshCachedOffers(), this.refreshCachedResources()]).then(() => response.resource);
})
);
}
public updateStoredProcedure(
collection: ViewModels.Collection,
storedProcedure: DataModels.StoredProcedure,
options: any
): Q.Promise<DataModels.StoredProcedure> {
return Q(
CosmosClient.client()
.database(collection.databaseId)
.container(collection.id())
.scripts.storedProcedure(storedProcedure.id)
.replace(storedProcedure, options)
.then(response => response.resource as DataModels.StoredProcedure)
);
}
public updateUserDefinedFunction(
collection: ViewModels.Collection,
userDefinedFunction: DataModels.UserDefinedFunction,
options?: any
): Q.Promise<DataModels.UserDefinedFunction> {
return Q(
CosmosClient.client()
.database(collection.databaseId)
.container(collection.id())
.scripts.userDefinedFunction(userDefinedFunction.id)
.replace(userDefinedFunction, options)
.then(response => response.resource as DataModels.StoredProcedure)
);
}
public updateTrigger(
collection: ViewModels.Collection,
trigger: DataModels.Trigger,
options?: any
): Q.Promise<DataModels.Trigger> {
return Q(
CosmosClient.client()
.database(collection.databaseId)
.container(collection.id())
.scripts.trigger(trigger.id)
.replace(trigger as TriggerDefinition, options)
.then(response => response.resource as DataModels.Trigger)
);
}
public createDocument(collection: ViewModels.CollectionBase, newDocument: any): Q.Promise<any> {
return Q(
CosmosClient.client()
.database(collection.databaseId)
.container(collection.id())
.items.create(newDocument)
.then(response => response.resource as DataModels.StoredProcedure)
);
}
public createStoredProcedure(
collection: ViewModels.Collection,
newStoredProcedure: DataModels.StoredProcedure,
options?: any
): Q.Promise<DataModels.StoredProcedure> {
return Q(
CosmosClient.client()
.database(collection.databaseId)
.container(collection.id())
.scripts.storedProcedures.create(newStoredProcedure, options)
.then(response => response.resource as DataModels.StoredProcedure)
);
}
public createUserDefinedFunction(
collection: ViewModels.Collection,
newUserDefinedFunction: DataModels.UserDefinedFunction,
options: any
): Q.Promise<DataModels.UserDefinedFunction> {
return Q(
CosmosClient.client()
.database(collection.databaseId)
.container(collection.id())
.scripts.userDefinedFunctions.create(newUserDefinedFunction, options)
.then(response => response.resource as DataModels.UserDefinedFunction)
);
}
public createTrigger(
collection: ViewModels.Collection,
newTrigger: DataModels.Trigger,
options?: any
): Q.Promise<DataModels.Trigger> {
return Q(
CosmosClient.client()
.database(collection.databaseId)
.container(collection.id())
.scripts.triggers.create(newTrigger as TriggerDefinition, options)
.then(response => response.resource as DataModels.Trigger)
);
}
public deleteDocument(collection: ViewModels.CollectionBase, documentId: ViewModels.DocumentId): Q.Promise<any> {
const partitionKey = documentId.partitionKeyValue;
return Q(
CosmosClient.client()
client()
.database(collection.databaseId)
.container(collection.id())
.item(documentId.id(), partitionKey)
.delete()
);
}
}
public deleteConflict(
export function deleteConflict(
collection: ViewModels.CollectionBase,
conflictId: ViewModels.ConflictId,
conflictId: ConflictId,
options: any = {}
): Q.Promise<any> {
options.partitionKey = options.partitionKey || this.getPartitionKeyHeaderForConflict(conflictId);
): Q.Promise<any> {
options.partitionKey = options.partitionKey || getPartitionKeyHeaderForConflict(conflictId);
return Q(
CosmosClient.client()
client()
.database(collection.databaseId)
.container(collection.id())
.conflict(conflictId.id())
.delete(options)
);
}
}
public deleteCollection(collection: ViewModels.Collection, options: any): Q.Promise<any> {
return Q(
CosmosClient.client()
.database(collection.databaseId)
.container(collection.id())
.delete()
.then(() => this.refreshCachedResources())
);
}
public deleteDatabase(database: ViewModels.Database, options: any): Q.Promise<any> {
return Q(
CosmosClient.client()
.database(database.id())
.delete()
.then(() => this.refreshCachedResources())
);
}
public deleteStoredProcedure(
collection: ViewModels.Collection,
storedProcedure: DataModels.StoredProcedure,
options: any
): Q.Promise<any> {
return Q(
CosmosClient.client()
.database(collection.databaseId)
.container(collection.id())
.scripts.storedProcedure(storedProcedure.id)
.delete()
);
}
public deleteUserDefinedFunction(
collection: ViewModels.Collection,
userDefinedFunction: DataModels.UserDefinedFunction,
options: any
): Q.Promise<any> {
return Q(
CosmosClient.client()
.database(collection.databaseId)
.container(collection.id())
.scripts.userDefinedFunction(userDefinedFunction.id)
.delete()
);
}
public deleteTrigger(collection: ViewModels.Collection, trigger: DataModels.Trigger, options: any): Q.Promise<any> {
return Q(
CosmosClient.client()
.database(collection.databaseId)
.container(collection.id())
.scripts.trigger(trigger.id)
.delete()
);
}
public readCollections(database: ViewModels.Database, options: any): Q.Promise<DataModels.Collection[]> {
return Q(
CosmosClient.client()
.database(database.id())
.containers.readAll()
.fetchAll()
.then(response => response.resources as DataModels.Collection[])
);
}
public readCollection(databaseId: string, collectionId: string): Q.Promise<DataModels.Collection> {
return Q(
CosmosClient.client()
.database(databaseId)
.container(collectionId)
.read()
.then(response => response.resource as DataModels.Collection)
);
}
public readCollectionQuotaInfo(
collection: ViewModels.Collection,
options: any
): Q.Promise<DataModels.CollectionQuotaInfo> {
options = options || {};
options.populateQuotaInfo = true;
options.initialHeaders = options.initialHeaders || {};
options.initialHeaders[Constants.HttpHeaders.populatePartitionStatistics] = true;
return Q(
CosmosClient.client()
.database(collection.databaseId)
.container(collection.id())
.read(options)
// TODO any needed because SDK does not properly type response.resource.statistics
.then((response: any) => {
let quota: DataModels.CollectionQuotaInfo = HeadersUtility.getQuota(response.headers);
quota["usageSizeInKB"] = response.resource.statistics.reduce(
(
previousValue: number,
currentValue: DataModels.Statistic,
currentIndex: number,
array: DataModels.Statistic[]
) => {
return previousValue + currentValue.sizeInKB;
},
0
);
quota["numPartitions"] = response.resource.statistics.length;
quota["uniqueKeyPolicy"] = collection.uniqueKeyPolicy; // TODO: Remove after refactoring (#119617)
return quota;
})
);
}
public readOffers(options: any): Q.Promise<DataModels.Offer[]> {
return Q(
CosmosClient.client()
.offers.readAll()
.fetchAll()
.then(response => response.resources)
);
}
public readOffer(requestedResource: DataModels.Offer, options: any): Q.Promise<DataModels.OfferWithHeaders> {
options = options || {};
options.initialHeaders = options.initialHeaders || {};
if (!OfferUtils.isOfferV1(requestedResource)) {
options.initialHeaders[Constants.HttpHeaders.populateCollectionThroughputInfo] = true;
}
return Q(
CosmosClient.client()
.offer(requestedResource.id)
.read(options)
.then(response => ({ ...response.resource, headers: response.headers }))
);
}
public readDatabases(options: any): Q.Promise<DataModels.Database[]> {
return Q(
CosmosClient.client()
.databases.readAll()
.fetchAll()
.then(response => response.resources as DataModels.Database[])
);
}
public getOrCreateDatabaseAndCollection(
request: DataModels.CreateDatabaseAndCollectionRequest,
options: any
): Q.Promise<DataModels.Collection> {
const databaseOptions: any = options && _.omit(options, "sharedOfferThroughput");
const {
databaseId,
databaseLevelThroughput,
collectionId,
partitionKey,
indexingPolicy,
uniqueKeyPolicy,
offerThroughput,
analyticalStorageTtl,
hasAutoPilotV2FeatureFlag
} = request;
const createBody: DatabaseRequest = {
id: databaseId
};
// TODO: replace when SDK support autopilot
const initialHeaders = request.autoPilot
? !hasAutoPilotV2FeatureFlag
? {
[Constants.HttpHeaders.autoPilotThroughputSDK]: JSON.stringify({
maxThroughput: request.autoPilot.maxThroughput
})
}
: {
[Constants.HttpHeaders.autoPilotTier]: request.autoPilot.autopilotTier
}
: undefined;
if (databaseLevelThroughput) {
if (request.autoPilot) {
databaseOptions.initialHeaders = initialHeaders;
}
createBody.throughput = offerThroughput;
}
return Q(
CosmosClient.client()
.databases.createIfNotExists(createBody, databaseOptions)
.then(response => {
return response.database.containers.create(
{
id: collectionId,
partitionKey: (partitionKey || undefined) as PartitionKeyDefinition,
indexingPolicy: indexingPolicy ? indexingPolicy : undefined,
uniqueKeyPolicy: uniqueKeyPolicy ? uniqueKeyPolicy : undefined,
analyticalStorageTtl: analyticalStorageTtl,
throughput: databaseLevelThroughput || request.autoPilot ? undefined : offerThroughput
} as ContainerRequest, // TODO: remove cast when https://github.com/Azure/azure-cosmos-js/issues/423 is fixed
{
initialHeaders: databaseLevelThroughput ? undefined : initialHeaders
}
);
})
.then(containerResponse => containerResponse.resource as DataModels.Collection)
.finally(() => this.refreshCachedResources(options))
);
}
public createDatabase(request: DataModels.CreateDatabaseRequest, options: any): Q.Promise<DataModels.Database> {
var deferred = Q.defer<DataModels.Database>();
this._createDatabase(request, options).then(
(createdDatabase: DataModels.Database) => {
this.refreshCachedOffers().then(() => {
deferred.resolve(createdDatabase);
});
},
_createDatabaseError => {
deferred.reject(_createDatabaseError);
}
);
return deferred.promise;
}
public refreshCachedOffers(): Q.Promise<void> {
if (MessageHandler.canSendMessage()) {
return MessageHandler.sendCachedDataMessage(MessageTypes.RefreshOffers, []);
} else {
return Q();
}
}
public refreshCachedResources(options?: any): Q.Promise<void> {
if (MessageHandler.canSendMessage()) {
return MessageHandler.sendCachedDataMessage(MessageTypes.RefreshResources, []);
} else {
return Q();
}
}
public queryConflicts(
export function queryConflicts(
databaseId: string,
containerId: string,
query: string,
options: any
): Q.Promise<QueryIterator<ConflictDefinition & Resource>> {
const documentsIterator = CosmosClient.client()
): Q.Promise<QueryIterator<ConflictDefinition & Resource>> {
const documentsIterator = client()
.database(databaseId)
.container(containerId)
.conflicts.query(query, options);
return Q(documentsIterator);
}
public updateOfferThroughputBeyondLimit(
request: DataModels.UpdateOfferThroughputRequest,
options: any
): Q.Promise<void> {
throw new Error("Updating throughput beyond specified limit is not supported on this platform");
}
private _createDatabase(
request: DataModels.CreateDatabaseRequest,
options: any = {}
): Q.Promise<DataModels.Database> {
const { databaseId, databaseLevelThroughput, offerThroughput, autoPilot, hasAutoPilotV2FeatureFlag } = request;
const createBody: DatabaseRequest = { id: databaseId };
const databaseOptions: any = options && _.omit(options, "sharedOfferThroughput");
// TODO: replace when SDK support autopilot
const initialHeaders = autoPilot
? !hasAutoPilotV2FeatureFlag
? {
[Constants.HttpHeaders.autoPilotThroughputSDK]: JSON.stringify({ maxThroughput: autoPilot.maxThroughput })
}
: {
[Constants.HttpHeaders.autoPilotTier]: autoPilot.autopilotTier
}
: undefined;
if (!!databaseLevelThroughput) {
if (autoPilot) {
databaseOptions.initialHeaders = initialHeaders;
}
createBody.throughput = offerThroughput;
}
return Q(
CosmosClient.client()
.databases.create(createBody, databaseOptions)
.then((response: DatabaseResponse) => {
return this.refreshCachedResources(databaseOptions).then(() => response.resource);
})
);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,49 +1,8 @@
import * as Constants from "../Common/Constants";
import * as ViewModels from "../Contracts/ViewModels";
import { AuthType } from "../AuthType";
import { StringUtils } from "../Utils/StringUtils";
import Explorer from "../Explorer/Explorer";
export default class EnvironmentUtility {
public static getMongoBackendEndpoint(serverId: string, location: string, extensionEndpoint: string = ""): string {
const defaultEnvironment: string = "default";
const defaultLocation: string = "default";
let environment: string = serverId;
const endpointType: Constants.MongoBackendEndpointType =
Constants.MongoBackend.endpointsByEnvironment[environment] ||
Constants.MongoBackend.endpointsByEnvironment[defaultEnvironment];
if (endpointType === Constants.MongoBackendEndpointType.local) {
return `${extensionEndpoint}${Constants.MongoBackend.localhostEndpoint}`;
}
const normalizedLocation = EnvironmentUtility.normalizeRegionName(location);
return (
Constants.MongoBackend.endpointsByRegion[normalizedLocation] ||
Constants.MongoBackend.endpointsByRegion[defaultLocation]
);
}
public static isAadUser(): boolean {
return window.authType === AuthType.AAD;
}
public static getCassandraBackendEndpoint(explorer: Explorer): string {
const defaultLocation: string = "default";
const location: string = EnvironmentUtility.normalizeRegionName(explorer.databaseAccount().location);
return (
Constants.CassandraBackend.endpointsByRegion[location] ||
Constants.CassandraBackend.endpointsByRegion[defaultLocation]
);
}
public static normalizeArmEndpointUri(uri: string): string {
if (uri && uri.slice(-1) !== "/") {
return `${uri}/`;
}
return uri;
}
private static normalizeRegionName(region: string): string {
return region && StringUtils.stripSpacesFromString(region.toLocaleLowerCase());
}
}

View File

@@ -0,0 +1,11 @@
import { CosmosError, sendNotificationForError } from "./dataAccess/sendNotificationForError";
import { logConsoleError } from "../Utils/NotificationConsoleUtils";
import { logError } from "./Logger";
import { replaceKnownError } from "./ErrorParserUtility";
export const handleError = (error: CosmosError, consoleErrorPrefix: string, area: string): void => {
const sanitizedErrorMsg = replaceKnownError(error.message);
logConsoleError(`${consoleErrorPrefix}:\n ${sanitizedErrorMsg}`);
logError(sanitizedErrorMsg, area, error.code);
sendNotificationForError(error);
};

View File

@@ -1,4 +1,4 @@
import * as DataModels from "../Contracts/DataModels";
import * as DataModels from "../Contracts/DataModels";
import * as ViewModels from "../Contracts/ViewModels";
export function replaceKnownError(err: string): string {
@@ -7,6 +7,8 @@ export function replaceKnownError(err: string): string {
err.indexOf("SharedOffer is Disabled for your account") >= 0
) {
return "Database throughput is not supported for internal subscriptions.";
} else if (err.indexOf("Partition key paths must contain only valid") >= 0) {
return "Partition key paths must contain only valid characters and not contain a trailing slash or wildcard character.";
}
return err;

View File

@@ -1,46 +1,26 @@
jest.mock("./MessageHandler");
import { LogEntryLevel } from "../Contracts/Diagnostics";
import * as Logger from "./Logger";
import { MessageHandler } from "./MessageHandler";
import { MessageTypes } from "../Contracts/ExplorerContracts";
import { sendMessage } from "./MessageHandler";
describe("Logger", () => {
let sendMessageSpy: jasmine.Spy;
beforeEach(() => {
sendMessageSpy = spyOn(MessageHandler, "sendMessage");
});
afterEach(() => {
sendMessageSpy = null;
jest.resetAllMocks();
});
it("should log info messages", () => {
Logger.logInfo("Test info", "DocDB");
const spyArgs = sendMessageSpy.calls.mostRecent().args[0];
expect(spyArgs.type).toBe(MessageTypes.LogInfo);
expect(spyArgs.data).toContain(LogEntryLevel.Verbose);
expect(spyArgs.data).toContain("DocDB");
expect(spyArgs.data).toContain("Test info");
expect(sendMessage).toBeCalled();
});
it("should log error messages", () => {
Logger.logError("Test error", "DocDB");
const spyArgs = sendMessageSpy.calls.mostRecent().args[0];
expect(spyArgs.type).toBe(MessageTypes.LogInfo);
expect(spyArgs.data).toContain(LogEntryLevel.Error);
expect(spyArgs.data).toContain("DocDB");
expect(spyArgs.data).toContain("Test error");
expect(sendMessage).toBeCalled();
});
it("should log warnings", () => {
Logger.logWarning("Test warning", "DocDB");
const spyArgs = sendMessageSpy.calls.mostRecent().args[0];
expect(spyArgs.type).toBe(MessageTypes.LogInfo);
expect(spyArgs.data).toContain(LogEntryLevel.Warning);
expect(spyArgs.data).toContain("DocDB");
expect(spyArgs.data).toContain("Test warning");
expect(sendMessage).toBeCalled();
});
});

View File

@@ -1,4 +1,4 @@
import { MessageHandler } from "./MessageHandler";
import { sendMessage } from "./MessageHandler";
import { Diagnostics, MessageTypes } from "../Contracts/ExplorerContracts";
import { appInsights } from "../Shared/appInsights";
import { SeverityLevel } from "@microsoft/applicationinsights-web";
@@ -33,7 +33,7 @@ export function logError(message: string | Error, area: string, code?: number):
}
function _logEntry(entry: Diagnostics.LogEntry): void {
MessageHandler.sendMessage({
sendMessage({
type: MessageTypes.LogInfo,
data: JSON.stringify(entry)
});

View File

@@ -1,65 +1,28 @@
import Q from "q";
import { CachedDataPromise, MessageHandler } from "./MessageHandler";
import { MessageTypes } from "../Contracts/ExplorerContracts";
class MockMessageHandler extends MessageHandler {
public static addToMap(key: string, value: CachedDataPromise<any>): void {
MessageHandler.RequestMap[key] = value;
}
public static mapContainsKey(key: string): boolean {
return MessageHandler.RequestMap[key] != null;
}
public static clearAllEntries(): void {
MessageHandler.RequestMap = {};
}
public static runGarbageCollector(): void {
MessageHandler.runGarbageCollector();
}
}
import * as MessageHandler from "./MessageHandler";
describe("Message Handler", () => {
beforeEach(() => {
MockMessageHandler.clearAllEntries();
});
xit("should send cached data message", (done: any) => {
const testValidationCallback = (e: MessageEvent) => {
expect(e.data.data).toEqual(
jasmine.objectContaining({ type: MessageTypes.AllDatabases, params: ["some param"] })
);
e.currentTarget.removeEventListener(e.type, testValidationCallback);
done();
};
window.parent.addEventListener("message", testValidationCallback);
MockMessageHandler.sendCachedDataMessage(MessageTypes.AllDatabases, ["some param"]);
});
it("should handle cached message", () => {
let mockPromise: CachedDataPromise<any> = {
it("should handle cached message", async () => {
let mockPromise = {
id: "123",
startTime: new Date(),
deferred: Q.defer<any>()
};
let mockMessage = { message: { id: "123", data: "{}" } };
MockMessageHandler.addToMap(mockPromise.id, mockPromise);
MockMessageHandler.handleCachedDataMessage(mockMessage);
MessageHandler.RequestMap[mockPromise.id] = mockPromise;
MessageHandler.handleCachedDataMessage(mockMessage);
expect(mockPromise.deferred.promise.isFulfilled()).toBe(true);
});
it("should delete fulfilled promises on running the garbage collector", () => {
let mockPromise: CachedDataPromise<any> = {
it("should delete fulfilled promises on running the garbage collector", async () => {
let message = {
id: "123",
startTime: new Date(),
deferred: Q.defer<any>()
};
MockMessageHandler.addToMap(mockPromise.id, mockPromise);
mockPromise.deferred.reject("some error");
MockMessageHandler.runGarbageCollector();
expect(MockMessageHandler.mapContainsKey(mockPromise.id)).toBe(false);
MessageHandler.handleCachedDataMessage(message);
MessageHandler.runGarbageCollector();
expect(MessageHandler.RequestMap["123"]).toBeUndefined();
});
});

View File

@@ -2,6 +2,7 @@ import { MessageTypes } from "../Contracts/ExplorerContracts";
import Q from "q";
import * as _ from "underscore";
import * as Constants from "./Constants";
import { getDataExplorerWindow } from "../Utils/WindowUtils";
export interface CachedDataPromise<T> {
deferred: Q.Deferred<T>;
@@ -9,77 +10,67 @@ export interface CachedDataPromise<T> {
id: string;
}
/**
* For some reason, typescript emits a Map() in the compiled js output(despite the target being set to ES5) forcing us to define our own polyfill,
* so we define our own custom implementation of the ES6 Map to work around it.
*/
type Map = { [key: string]: CachedDataPromise<any> };
export const RequestMap: Record<string, CachedDataPromise<any>> = {};
export class MessageHandler {
protected static RequestMap: Map = {};
public static handleCachedDataMessage(message: any): void {
export function handleCachedDataMessage(message: any): void {
const messageContent = message && message.message;
if (
message == null ||
messageContent == null ||
messageContent.id == null ||
!MessageHandler.RequestMap[messageContent.id]
) {
if (message == null || messageContent == null || messageContent.id == null || !RequestMap[messageContent.id]) {
return;
}
const cachedDataPromise = MessageHandler.RequestMap[messageContent.id];
const cachedDataPromise = RequestMap[messageContent.id];
if (messageContent.error != null) {
cachedDataPromise.deferred.reject(messageContent.error);
} else {
cachedDataPromise.deferred.resolve(JSON.parse(messageContent.data));
}
MessageHandler.runGarbageCollector();
}
runGarbageCollector();
}
public static sendCachedDataMessage<TResponseDataModel>(
export function sendCachedDataMessage<TResponseDataModel>(
messageType: MessageTypes,
params: Object[],
timeoutInMs?: number
): Q.Promise<TResponseDataModel> {
): Q.Promise<TResponseDataModel> {
let cachedDataPromise: CachedDataPromise<TResponseDataModel> = {
deferred: Q.defer<TResponseDataModel>(),
startTime: new Date(),
id: _.uniqueId()
};
MessageHandler.RequestMap[cachedDataPromise.id] = cachedDataPromise;
MessageHandler.sendMessage({ type: messageType, params: params, id: cachedDataPromise.id });
RequestMap[cachedDataPromise.id] = cachedDataPromise;
sendMessage({ type: messageType, params: params, id: cachedDataPromise.id });
//TODO: Use telemetry to measure optimal time to resolve/reject promises
return cachedDataPromise.deferred.promise.timeout(
timeoutInMs || Constants.ClientDefaults.requestTimeoutMs,
"Timed out while waiting for response from portal"
);
}
}
public static sendMessage(data: any): void {
if (MessageHandler.canSendMessage()) {
window.parent.postMessage(
export function sendMessage(data: any): void {
if (canSendMessage()) {
// We try to find data explorer window first, then fallback to current window
const portalChildWindow = getDataExplorerWindow(window) || window;
portalChildWindow.parent.postMessage(
{
signature: "pcIframe",
data: data
},
window.document.referrer
portalChildWindow.document.referrer
);
}
}
}
public static canSendMessage(): boolean {
export function canSendMessage(): boolean {
return window.parent !== window;
}
}
protected static runGarbageCollector() {
Object.keys(MessageHandler.RequestMap).forEach((key: string) => {
const promise: Q.Promise<any> = MessageHandler.RequestMap[key].deferred.promise;
// TODO: This is exported just for testing. It should not be.
export function runGarbageCollector() {
Object.keys(RequestMap).forEach((key: string) => {
const promise: Q.Promise<any> = RequestMap[key].deferred.promise;
if (promise.isFulfilled() || promise.isRejected()) {
delete MessageHandler.RequestMap[key];
delete RequestMap[key];
}
});
}
}

View File

@@ -1,16 +1,10 @@
import {
_createMongoCollectionWithARM,
deleteDocument,
getEndpoint,
queryDocuments,
readDocument,
updateDocument
} from "./MongoProxyClient";
import { AuthType } from "../AuthType";
import { Collection, DatabaseAccount, DocumentId } from "../Contracts/ViewModels";
import { config } from "../Config";
import { CosmosClient } from "./CosmosClient";
import { ResourceProviderClient } from "../ResourceProvider/ResourceProviderClient";
import { resetConfigContext, updateConfigContext } from "../ConfigContext";
import { DatabaseAccount } from "../Contracts/DataModels";
import { Collection } from "../Contracts/ViewModels";
import DocumentId from "../Explorer/Tree/DocumentId";
import { updateUserContext } from "../UserContext";
import { deleteDocument, getEndpoint, queryDocuments, readDocument, updateDocument } from "./MongoProxyClient";
jest.mock("../ResourceProvider/ResourceProviderClient.ts");
const databaseId = "testDB";
@@ -60,17 +54,18 @@ const databaseAccount = {
tableEndpoint: "foo",
cassandraEndpoint: "foo"
}
};
} as DatabaseAccount;
describe("MongoProxyClient", () => {
describe("queryDocuments", () => {
beforeEach(() => {
delete config.BACKEND_ENDPOINT;
CosmosClient.databaseAccount(databaseAccount as any);
window.dataExplorer = {
extensionEndpoint: () => "https://main.documentdb.ext.azure.com",
serverId: () => ""
} as any;
resetConfigContext();
updateUserContext({
databaseAccount
});
updateConfigContext({
BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com"
});
window.fetch = jest.fn().mockImplementation(fetchMock);
});
afterEach(() => {
@@ -86,7 +81,7 @@ describe("MongoProxyClient", () => {
});
it("builds the correct proxy URL in development", () => {
config.MONGO_BACKEND_ENDPOINT = "https://localhost:1234";
updateConfigContext({ MONGO_BACKEND_ENDPOINT: "https://localhost:1234" });
queryDocuments(databaseId, collection, true, "{}");
expect(window.fetch).toHaveBeenCalledWith(
"https://localhost:1234/api/mongo/explorer/resourcelist?db=testDB&coll=testCollection&resourceUrl=bardbs%2FtestDB%2Fcolls%2FtestCollection%2Fdocs%2F&rid=testCollectionrid&rtype=docs&sid=&rg=&dba=foo&pk=pk",
@@ -96,12 +91,13 @@ describe("MongoProxyClient", () => {
});
describe("readDocument", () => {
beforeEach(() => {
delete config.MONGO_BACKEND_ENDPOINT;
CosmosClient.databaseAccount(databaseAccount as any);
window.dataExplorer = {
extensionEndpoint: () => "https://main.documentdb.ext.azure.com",
serverId: () => ""
} as any;
resetConfigContext();
updateUserContext({
databaseAccount
});
updateConfigContext({
BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com"
});
window.fetch = jest.fn().mockImplementation(fetchMock);
});
afterEach(() => {
@@ -117,7 +113,7 @@ describe("MongoProxyClient", () => {
});
it("builds the correct proxy URL in development", () => {
config.MONGO_BACKEND_ENDPOINT = "https://localhost:1234";
updateConfigContext({ MONGO_BACKEND_ENDPOINT: "https://localhost:1234" });
readDocument(databaseId, collection, documentId);
expect(window.fetch).toHaveBeenCalledWith(
"https://localhost:1234/api/mongo/explorer?db=testDB&coll=testCollection&resourceUrl=bardb%2FtestDB%2Fdb%2FtestCollection%2FtestId&rid=testId&rtype=docs&sid=&rg=&dba=foo&pk=pk",
@@ -127,12 +123,13 @@ describe("MongoProxyClient", () => {
});
describe("createDocument", () => {
beforeEach(() => {
delete config.MONGO_BACKEND_ENDPOINT;
CosmosClient.databaseAccount(databaseAccount as any);
window.dataExplorer = {
extensionEndpoint: () => "https://main.documentdb.ext.azure.com",
serverId: () => ""
} as any;
resetConfigContext();
updateUserContext({
databaseAccount
});
updateConfigContext({
BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com"
});
window.fetch = jest.fn().mockImplementation(fetchMock);
});
afterEach(() => {
@@ -148,7 +145,7 @@ describe("MongoProxyClient", () => {
});
it("builds the correct proxy URL in development", () => {
config.MONGO_BACKEND_ENDPOINT = "https://localhost:1234";
updateConfigContext({ MONGO_BACKEND_ENDPOINT: "https://localhost:1234" });
readDocument(databaseId, collection, documentId);
expect(window.fetch).toHaveBeenCalledWith(
"https://localhost:1234/api/mongo/explorer?db=testDB&coll=testCollection&resourceUrl=bardb%2FtestDB%2Fdb%2FtestCollection%2FtestId&rid=testId&rtype=docs&sid=&rg=&dba=foo&pk=pk",
@@ -158,12 +155,13 @@ describe("MongoProxyClient", () => {
});
describe("updateDocument", () => {
beforeEach(() => {
delete config.MONGO_BACKEND_ENDPOINT;
CosmosClient.databaseAccount(databaseAccount as any);
window.dataExplorer = {
extensionEndpoint: () => "https://main.documentdb.ext.azure.com",
serverId: () => ""
} as any;
resetConfigContext();
updateUserContext({
databaseAccount
});
updateConfigContext({
BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com"
});
window.fetch = jest.fn().mockImplementation(fetchMock);
});
afterEach(() => {
@@ -171,7 +169,7 @@ describe("MongoProxyClient", () => {
});
it("builds the correct URL", () => {
updateDocument(databaseId, collection, documentId, {});
updateDocument(databaseId, collection, documentId, "{}");
expect(window.fetch).toHaveBeenCalledWith(
"https://main.documentdb.ext.azure.com/api/mongo/explorer?db=testDB&coll=testCollection&resourceUrl=bardb%2FtestDB%2Fdb%2FtestCollection%2Fdocs%2FtestId&rid=testId&rtype=docs&sid=&rg=&dba=foo&pk=pk",
expect.any(Object)
@@ -179,8 +177,8 @@ describe("MongoProxyClient", () => {
});
it("builds the correct proxy URL in development", () => {
config.MONGO_BACKEND_ENDPOINT = "https://localhost:1234";
updateDocument(databaseId, collection, documentId, {});
updateConfigContext({ MONGO_BACKEND_ENDPOINT: "https://localhost:1234" });
updateDocument(databaseId, collection, documentId, "{}");
expect(window.fetch).toHaveBeenCalledWith(
"https://localhost:1234/api/mongo/explorer?db=testDB&coll=testCollection&resourceUrl=bardb%2FtestDB%2Fdb%2FtestCollection%2Fdocs%2FtestId&rid=testId&rtype=docs&sid=&rg=&dba=foo&pk=pk",
expect.any(Object)
@@ -189,12 +187,13 @@ describe("MongoProxyClient", () => {
});
describe("deleteDocument", () => {
beforeEach(() => {
delete config.MONGO_BACKEND_ENDPOINT;
CosmosClient.databaseAccount(databaseAccount as any);
window.dataExplorer = {
extensionEndpoint: () => "https://main.documentdb.ext.azure.com",
serverId: () => ""
} as any;
resetConfigContext();
updateUserContext({
databaseAccount
});
updateConfigContext({
BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com"
});
window.fetch = jest.fn().mockImplementation(fetchMock);
});
afterEach(() => {
@@ -210,7 +209,7 @@ describe("MongoProxyClient", () => {
});
it("builds the correct proxy URL in development", () => {
config.MONGO_BACKEND_ENDPOINT = "https://localhost:1234";
updateConfigContext({ MONGO_BACKEND_ENDPOINT: "https://localhost:1234" });
deleteDocument(databaseId, collection, documentId);
expect(window.fetch).toHaveBeenCalledWith(
"https://localhost:1234/api/mongo/explorer?db=testDB&coll=testCollection&resourceUrl=bardb%2FtestDB%2Fdb%2FtestCollection%2Fdocs%2FtestId&rid=testId&rtype=docs&sid=&rg=&dba=foo&pk=pk",
@@ -220,84 +219,31 @@ describe("MongoProxyClient", () => {
});
describe("getEndpoint", () => {
beforeEach(() => {
delete config.MONGO_BACKEND_ENDPOINT;
resetConfigContext();
delete window.authType;
CosmosClient.databaseAccount(databaseAccount as any);
window.dataExplorer = {
extensionEndpoint: () => "https://main.documentdb.ext.azure.com",
serverId: () => ""
} as any;
updateUserContext({
databaseAccount
});
updateConfigContext({
BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com"
});
});
it("returns a production endpoint", () => {
const endpoint = getEndpoint(databaseAccount as DatabaseAccount);
const endpoint = getEndpoint();
expect(endpoint).toEqual("https://main.documentdb.ext.azure.com/api/mongo/explorer");
});
it("returns a development endpoint", () => {
config.MONGO_BACKEND_ENDPOINT = "https://localhost:1234";
const endpoint = getEndpoint(databaseAccount as DatabaseAccount);
updateConfigContext({ MONGO_BACKEND_ENDPOINT: "https://localhost:1234" });
const endpoint = getEndpoint();
expect(endpoint).toEqual("https://localhost:1234/api/mongo/explorer");
});
it("returns a guest endpoint", () => {
window.authType = AuthType.EncryptedToken;
const endpoint = getEndpoint(databaseAccount as DatabaseAccount);
const endpoint = getEndpoint();
expect(endpoint).toEqual("https://main.documentdb.ext.azure.com/api/guest/mongo/explorer");
});
});
describe("createMongoCollectionWithARM", () => {
it("should create a collection with autopilot when autopilot is selected + shared throughput is false", () => {
const resourceProviderClientPutAsyncSpy = jest.spyOn(ResourceProviderClient.prototype, "putAsync");
const properties = {
pk: "state",
coll: "abc-collection",
cd: true,
db: "a1-db",
st: false,
sid: "a2",
rg: "c1",
dba: "main",
is: false
};
_createMongoCollectionWithARM("management.azure.com", properties, { "x-ms-cosmos-offer-autopilot-tier": "1" });
expect(resourceProviderClientPutAsyncSpy).toHaveBeenCalledWith(
"subscriptions/a2/resourceGroups/c1/providers/Microsoft.DocumentDB/databaseAccounts/foo/mongodbDatabases/a1-db/collections/abc-collection",
"2020-04-01",
{
properties: {
options: { "x-ms-cosmos-offer-autopilot-tier": "1" },
resource: { id: "abc-collection" }
}
}
);
});
it("should create a collection with provisioned throughput when provisioned throughput is selected + shared throughput is false", () => {
const resourceProviderClientPutAsyncSpy = jest.spyOn(ResourceProviderClient.prototype, "putAsync");
const properties = {
pk: "state",
coll: "abc-collection",
cd: true,
db: "a1-db",
st: false,
sid: "a2",
rg: "c1",
dba: "main",
is: false,
offerThroughput: 400
};
_createMongoCollectionWithARM("management.azure.com", properties, undefined);
expect(resourceProviderClientPutAsyncSpy).toHaveBeenCalledWith(
"subscriptions/a2/resourceGroups/c1/providers/Microsoft.DocumentDB/databaseAccounts/foo/mongodbDatabases/a1-db/collections/abc-collection",
"2020-04-01",
{
properties: {
options: { throughput: "400" },
resource: { id: "abc-collection" }
}
}
);
});
});
});

View File

@@ -1,22 +1,17 @@
import * as Constants from "../Common/Constants";
import * as DataExplorerConstants from "../Common/Constants";
import * as DataModels from "../Contracts/DataModels";
import * as ViewModels from "../Contracts/ViewModels";
import EnvironmentUtility from "./EnvironmentUtility";
import queryString from "querystring";
import { AddDbUtilities } from "../Shared/AddDatabaseUtility";
import { ApiType, HttpHeaders, HttpStatusCodes } from "./Constants";
import { AuthType } from "../AuthType";
import { Collection } from "../Contracts/ViewModels";
import { config } from "../Config";
import { ConsoleDataType } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
import { Constants as CosmosSDKConstants } from "@azure/cosmos";
import { CosmosClient } from "./CosmosClient";
import { MessageHandler } from "./MessageHandler";
import queryString from "querystring";
import { AuthType } from "../AuthType";
import { configContext } from "../ConfigContext";
import * as DataModels from "../Contracts/DataModels";
import { MessageTypes } from "../Contracts/ExplorerContracts";
import { NotificationConsoleUtils } from "../Utils/NotificationConsoleUtils";
import { ResourceProviderClient } from "../ResourceProvider/ResourceProviderClient";
import { Collection } from "../Contracts/ViewModels";
import { ConsoleDataType } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
import DocumentId from "../Explorer/Tree/DocumentId";
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
import { ApiType, HttpHeaders, HttpStatusCodes } from "./Constants";
import { userContext } from "../UserContext";
import { MinimalQueryIterator } from "./IteratorUtilities";
import { sendMessage } from "./MessageHandler";
const defaultHeaders = {
[HttpHeaders.apiType]: ApiType.MongoDB.toString(),
@@ -26,9 +21,9 @@ const defaultHeaders = {
function authHeaders() {
if (window.authType === AuthType.EncryptedToken) {
return { [HttpHeaders.guestAccessToken]: CosmosClient.accessToken() };
return { [HttpHeaders.guestAccessToken]: userContext.accessToken };
} else {
return { [HttpHeaders.authorization]: CosmosClient.authorizationToken() };
return { [HttpHeaders.authorization]: userContext.authorizationToken };
}
}
@@ -67,7 +62,7 @@ export function queryDocuments(
query: string,
continuationToken?: string
): Promise<QueryResponse> {
const databaseAccount = CosmosClient.databaseAccount();
const databaseAccount = userContext.databaseAccount;
const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint;
const params = {
db: databaseId,
@@ -75,14 +70,14 @@ export function queryDocuments(
resourceUrl: `${resourceEndpoint}dbs/${databaseId}/colls/${collection.id()}/docs/`,
rid: collection.rid,
rtype: "docs",
sid: CosmosClient.subscriptionId(),
rg: CosmosClient.resourceGroup(),
sid: userContext.subscriptionId,
rg: userContext.resourceGroup,
dba: databaseAccount.name,
pk:
collection && collection.partitionKey && !collection.partitionKey.systemKey ? collection.partitionKeyProperty : ""
};
const endpoint = getEndpoint(databaseAccount) || "";
const endpoint = getEndpoint() || "";
const headers = {
...defaultHeaders,
@@ -123,9 +118,9 @@ export function queryDocuments(
export function readDocument(
databaseId: string,
collection: Collection,
documentId: ViewModels.DocumentId
documentId: DocumentId
): Promise<DataModels.DocumentId> {
const databaseAccount = CosmosClient.databaseAccount();
const databaseAccount = userContext.databaseAccount;
const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint;
const idComponents = documentId.self.split("/");
const path = idComponents.slice(0, 4).join("/");
@@ -136,14 +131,14 @@ export function readDocument(
resourceUrl: `${resourceEndpoint}${path}/${rid}`,
rid,
rtype: "docs",
sid: CosmosClient.subscriptionId(),
rg: CosmosClient.resourceGroup(),
sid: userContext.subscriptionId,
rg: userContext.resourceGroup,
dba: databaseAccount.name,
pk:
documentId && documentId.partitionKey && !documentId.partitionKey.systemKey ? documentId.partitionKeyProperty : ""
};
const endpoint = getEndpoint(databaseAccount);
const endpoint = getEndpoint();
return window
.fetch(`${endpoint}?${queryString.stringify(params)}`, {
method: "GET",
@@ -169,7 +164,7 @@ export function createDocument(
partitionKeyProperty: string,
documentContent: unknown
): Promise<DataModels.DocumentId> {
const databaseAccount = CosmosClient.databaseAccount();
const databaseAccount = userContext.databaseAccount;
const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint;
const params = {
db: databaseId,
@@ -177,13 +172,13 @@ export function createDocument(
resourceUrl: `${resourceEndpoint}dbs/${databaseId}/colls/${collection.id()}/docs/`,
rid: collection.rid,
rtype: "docs",
sid: CosmosClient.subscriptionId(),
rg: CosmosClient.resourceGroup(),
sid: userContext.subscriptionId,
rg: userContext.resourceGroup,
dba: databaseAccount.name,
pk: collection && collection.partitionKey && !collection.partitionKey.systemKey ? partitionKeyProperty : ""
};
const endpoint = getEndpoint(databaseAccount);
const endpoint = getEndpoint();
return window
.fetch(`${endpoint}/resourcelist?${queryString.stringify(params)}`, {
@@ -205,10 +200,10 @@ export function createDocument(
export function updateDocument(
databaseId: string,
collection: Collection,
documentId: ViewModels.DocumentId,
documentContent: unknown
documentId: DocumentId,
documentContent: string
): Promise<DataModels.DocumentId> {
const databaseAccount = CosmosClient.databaseAccount();
const databaseAccount = userContext.databaseAccount;
const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint;
const idComponents = documentId.self.split("/");
const path = idComponents.slice(0, 5).join("/");
@@ -219,18 +214,18 @@ export function updateDocument(
resourceUrl: `${resourceEndpoint}${path}/${rid}`,
rid,
rtype: "docs",
sid: CosmosClient.subscriptionId(),
rg: CosmosClient.resourceGroup(),
sid: userContext.subscriptionId,
rg: userContext.resourceGroup,
dba: databaseAccount.name,
pk:
documentId && documentId.partitionKey && !documentId.partitionKey.systemKey ? documentId.partitionKeyProperty : ""
};
const endpoint = getEndpoint(databaseAccount);
const endpoint = getEndpoint();
return window
.fetch(`${endpoint}?${queryString.stringify(params)}`, {
method: "PUT",
body: JSON.stringify(documentContent),
body: documentContent,
headers: {
...defaultHeaders,
...authHeaders(),
@@ -246,12 +241,8 @@ export function updateDocument(
});
}
export function deleteDocument(
databaseId: string,
collection: Collection,
documentId: ViewModels.DocumentId
): Promise<void> {
const databaseAccount = CosmosClient.databaseAccount();
export function deleteDocument(databaseId: string, collection: Collection, documentId: DocumentId): Promise<void> {
const databaseAccount = userContext.databaseAccount;
const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint;
const idComponents = documentId.self.split("/");
const path = idComponents.slice(0, 5).join("/");
@@ -262,13 +253,13 @@ export function deleteDocument(
resourceUrl: `${resourceEndpoint}${path}/${rid}`,
rid,
rtype: "docs",
sid: CosmosClient.subscriptionId(),
rg: CosmosClient.resourceGroup(),
sid: userContext.subscriptionId,
rg: userContext.resourceGroup,
dba: databaseAccount.name,
pk:
documentId && documentId.partitionKey && !documentId.partitionKey.systemKey ? documentId.partitionKeyProperty : ""
};
const endpoint = getEndpoint(databaseAccount);
const endpoint = getEndpoint();
return window
.fetch(`${endpoint}?${queryString.stringify(params)}`, {
@@ -289,43 +280,35 @@ export function deleteDocument(
}
export function createMongoCollectionWithProxy(
databaseId: string,
collectionId: string,
offerThroughput: number,
shardKey: string,
createDatabase: boolean,
sharedThroughput: boolean,
isSharded: boolean,
autopilotOptions?: DataModels.RpOptions
params: DataModels.CreateCollectionParams
): Promise<DataModels.Collection> {
const databaseAccount = CosmosClient.databaseAccount();
const params: DataModels.MongoParameters = {
const databaseAccount = userContext.databaseAccount;
const shardKey: string = params.partitionKey?.paths[0];
const mongoParams: DataModels.MongoParameters = {
resourceUrl: databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint,
db: databaseId,
coll: collectionId,
db: params.databaseId,
coll: params.collectionId,
pk: shardKey,
offerThroughput,
cd: createDatabase,
st: sharedThroughput,
is: isSharded,
offerThroughput: params.offerThroughput,
cd: params.createNewDatabase,
st: params.databaseLevelThroughput,
is: !!shardKey,
rid: "",
rtype: "colls",
sid: CosmosClient.subscriptionId(),
rg: CosmosClient.resourceGroup(),
sid: userContext.subscriptionId,
rg: userContext.resourceGroup,
dba: databaseAccount.name,
isAutoPilot: false
isAutoPilot: !!params.autoPilotMaxThroughput,
autoPilotThroughput: params.autoPilotMaxThroughput?.toString()
};
if (autopilotOptions) {
params.isAutoPilot = true;
params.autoPilotTier = autopilotOptions[Constants.HttpHeaders.autoPilotTier] as string;
}
const endpoint = getEndpoint(databaseAccount);
const endpoint = getEndpoint();
return window
.fetch(
`${endpoint}/createCollection?${queryString.stringify((params as unknown) as queryString.ParsedUrlQueryInput)}`,
`${endpoint}/createCollection?${queryString.stringify(
(mongoParams as unknown) as queryString.ParsedUrlQueryInput
)}`,
{
method: "POST",
headers: {
@@ -339,58 +322,12 @@ export function createMongoCollectionWithProxy(
if (response.ok) {
return response.json();
}
return errorHandling(response, "creating collection", params);
return errorHandling(response, "creating collection", mongoParams);
});
}
export function createMongoCollectionWithARM(
armEndpoint: string,
databaseId: string,
analyticalStorageTtl: number,
collectionId: string,
offerThroughput: number,
shardKey: string,
createDatabase: boolean,
sharedThroughput: boolean,
isSharded: boolean,
additionalOptions?: DataModels.RpOptions
): Promise<DataModels.CreateCollectionWithRpResponse> {
const databaseAccount = CosmosClient.databaseAccount();
const params: DataModels.MongoParameters = {
resourceUrl: databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint,
db: databaseId,
coll: collectionId,
pk: shardKey,
offerThroughput,
cd: createDatabase,
st: sharedThroughput,
is: isSharded,
rid: "",
rtype: "colls",
sid: CosmosClient.subscriptionId(),
rg: CosmosClient.resourceGroup(),
dba: databaseAccount.name,
analyticalStorageTtl
};
if (createDatabase) {
return AddDbUtilities.createMongoDatabaseWithARM(
armEndpoint,
params,
sharedThroughput ? additionalOptions : {}
).then(() => {
return _createMongoCollectionWithARM(armEndpoint, params, sharedThroughput ? {} : additionalOptions);
});
}
return _createMongoCollectionWithARM(armEndpoint, params, additionalOptions);
}
export function getEndpoint(databaseAccount: ViewModels.DatabaseAccount): string {
const serverId = window.dataExplorer.serverId();
const extensionEndpoint = window.dataExplorer.extensionEndpoint();
let url = config.MONGO_BACKEND_ENDPOINT
? config.MONGO_BACKEND_ENDPOINT + "/api/mongo/explorer"
: EnvironmentUtility.getMongoBackendEndpoint(serverId, databaseAccount.location, extensionEndpoint);
export function getEndpoint(): string {
let url = (configContext.MONGO_BACKEND_ENDPOINT || configContext.BACKEND_ENDPOINT) + "/api/mongo/explorer";
if (window.authType === AuthType.EncryptedToken) {
url = url.replace("api/mongo", "api/guest/mongo");
@@ -408,57 +345,12 @@ async function errorHandling(response: Response, action: string, params: unknown
`Error ${action}: ${errorMessage}, Payload: ${JSON.stringify(params)}`
);
if (response.status === HttpStatusCodes.Forbidden) {
MessageHandler.sendMessage({ type: MessageTypes.ForbiddenError, reason: errorMessage });
sendMessage({ type: MessageTypes.ForbiddenError, reason: errorMessage });
return;
}
throw new Error(errorMessage);
}
export function getARMCreateCollectionEndpoint(params: DataModels.MongoParameters): string {
return `subscriptions/${params.sid}/resourceGroups/${params.rg}/providers/Microsoft.DocumentDB/databaseAccounts/${
CosmosClient.databaseAccount().name
}/mongodbDatabases/${params.db}/collections/${params.coll}`;
}
export async function _createMongoCollectionWithARM(
armEndpoint: string,
params: DataModels.MongoParameters,
rpOptions: DataModels.RpOptions
): Promise<DataModels.CreateCollectionWithRpResponse> {
const rpPayloadToCreateCollection: DataModels.MongoCreationRequest = {
properties: {
resource: {
id: params.coll
},
options: {}
}
};
if (params.is) {
rpPayloadToCreateCollection.properties.resource["shardKey"] = { [params.pk]: "Hash" };
}
if (!params.st) {
if (rpOptions) {
rpPayloadToCreateCollection.properties.options = rpOptions;
} else {
rpPayloadToCreateCollection.properties.options["throughput"] =
params.offerThroughput && params.offerThroughput.toString();
}
}
if (params.analyticalStorageTtl) {
rpPayloadToCreateCollection.properties.resource.analyticalStorageTtl = params.analyticalStorageTtl;
}
try {
return new ResourceProviderClient<DataModels.CreateCollectionWithRpResponse>(armEndpoint).putAsync(
getARMCreateCollectionEndpoint(params),
DataExplorerConstants.ArmApiVersions.publicVersion,
rpPayloadToCreateCollection
);
} catch (response) {
errorHandling(response, "creating collection", undefined);
return undefined;
}
return `subscriptions/${params.sid}/resourceGroups/${params.rg}/providers/Microsoft.DocumentDB/databaseAccounts/${userContext.databaseAccount.name}/mongodbDatabases/${params.db}/collections/${params.coll}`;
}

View File

@@ -1,47 +0,0 @@
import "jquery";
import * as Q from "q";
import * as DataModels from "../Contracts/DataModels";
import * as ViewModels from "../Contracts/ViewModels";
import { getAuthorizationHeader } from "../Utils/AuthorizationUtils";
import { CosmosClient } from "./CosmosClient";
export class NotificationsClientBase implements ViewModels.NotificationsClient {
private _extensionEndpoint: string;
private _notificationsApiSuffix: string;
protected constructor(notificationsApiSuffix: string) {
this._notificationsApiSuffix = notificationsApiSuffix;
}
public fetchNotifications(): Q.Promise<DataModels.Notification[]> {
const deferred: Q.Deferred<DataModels.Notification[]> = Q.defer<DataModels.Notification[]>();
const databaseAccount: ViewModels.DatabaseAccount = CosmosClient.databaseAccount();
const subscriptionId: string = CosmosClient.subscriptionId();
const resourceGroup: string = CosmosClient.resourceGroup();
const url: string = `${this._extensionEndpoint}${this._notificationsApiSuffix}?accountName=${databaseAccount.name}&subscriptionId=${subscriptionId}&resourceGroup=${resourceGroup}`;
const authorizationHeader: ViewModels.AuthorizationTokenHeaderMetadata = getAuthorizationHeader();
const headers: any = {};
headers[authorizationHeader.header] = authorizationHeader.token;
$.ajax({
url: url,
type: "GET",
headers: headers,
cache: false
}).then(
(notifications: DataModels.Notification[], textStatus: string, xhr: JQueryXHR<any>) => {
deferred.resolve(notifications);
},
(xhr: JQueryXHR<any>, textStatus: string, error: any) => {
deferred.reject(xhr.responseText);
}
);
return deferred.promise;
}
public setExtensionEndpoint(extensionEndpoint: string): void {
this._extensionEndpoint = extensionEndpoint;
}
}

View File

@@ -0,0 +1,41 @@
import * as DataModels from "../Contracts/DataModels";
import * as ViewModels from "../Contracts/ViewModels";
import { getAuthorizationHeader } from "../Utils/AuthorizationUtils";
import { userContext } from "../UserContext";
import { configContext, Platform } from "../ConfigContext";
const notificationsPath = () => {
switch (configContext.platform) {
case Platform.Hosted:
return "/api/guest/notifications";
case Platform.Portal:
return "/api/notifications";
default:
throw new Error(`Unknown platform: ${configContext.platform}`);
}
};
export const fetchPortalNotifications = async (): Promise<DataModels.Notification[]> => {
if (configContext.platform === Platform.Emulator) {
return [];
}
const databaseAccount = userContext.databaseAccount;
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const url = `${configContext.BACKEND_ENDPOINT}${notificationsPath()}?accountName=${
databaseAccount.name
}&subscriptionId=${subscriptionId}&resourceGroup=${resourceGroup}`;
const authorizationHeader: ViewModels.AuthorizationTokenHeaderMetadata = getAuthorizationHeader();
const headers = { [authorizationHeader.header]: authorizationHeader.token };
const response = await window.fetch(url, {
headers
});
if (!response.ok) {
throw new Error(await response.text());
}
return (await response.json()) as DataModels.Notification[];
};

View File

@@ -1,18 +1,21 @@
import { ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
import * as _ from "underscore";
import * as DataModels from "../Contracts/DataModels";
import * as ViewModels from "../Contracts/ViewModels";
import DocumentId from "../Explorer/Tree/DocumentId";
import * as ErrorParserUtility from "./ErrorParserUtility";
import { BackendDefaults, HttpStatusCodes, SavedQueries } from "./Constants";
import { ConsoleDataType } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
import { CosmosClient } from "./CosmosClient";
import { ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
import * as Logger from "./Logger";
import { NotificationConsoleUtils } from "../Utils/NotificationConsoleUtils";
import { QueryUtils } from "../Utils/QueryUtils";
import Explorer from "../Explorer/Explorer";
import { ConsoleDataType } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
import DocumentsTab from "../Explorer/Tabs/DocumentsTab";
import DocumentId from "../Explorer/Tree/DocumentId";
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
import { QueryUtils } from "../Utils/QueryUtils";
import { BackendDefaults, HttpStatusCodes, SavedQueries } from "./Constants";
import { userContext } from "../UserContext";
import { createDocument, deleteDocument, queryDocuments, queryDocumentsPage } from "./DocumentClientUtilityBase";
import { createCollection } from "./dataAccess/createCollection";
import * as ErrorParserUtility from "./ErrorParserUtility";
import * as Logger from "./Logger";
export class QueriesClient implements ViewModels.QueriesClient {
export class QueriesClient {
private static readonly PartitionKey: DataModels.PartitionKey = {
paths: [`/${SavedQueries.PartitionKeyProperty}`],
kind: BackendDefaults.partitionKeyKind,
@@ -33,13 +36,13 @@ export class QueriesClient implements ViewModels.QueriesClient {
ConsoleDataType.InProgress,
"Setting up account for saving queries"
);
return this.container.documentClientUtility
.getOrCreateDatabaseAndCollection({
return createCollection({
collectionId: SavedQueries.CollectionName,
createNewDatabase: true,
databaseId: SavedQueries.DatabaseName,
partitionKey: QueriesClient.PartitionKey,
offerThroughput: SavedQueries.OfferThroughput,
databaseLevelThroughput: undefined
databaseLevelThroughput: false
})
.then(
(collection: DataModels.Collection) => {
@@ -50,7 +53,7 @@ export class QueriesClient implements ViewModels.QueriesClient {
return Promise.resolve(collection);
},
(error: any) => {
const stringifiedError: string = JSON.stringify(error);
const stringifiedError: string = error.message;
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error,
`Failed to set up account for saving queries: ${stringifiedError}`
@@ -89,8 +92,7 @@ export class QueriesClient implements ViewModels.QueriesClient {
`Saving query ${query.queryName}`
);
query.id = query.queryName;
return this.container.documentClientUtility
.createDocument(queriesCollection, query)
return createDocument(queriesCollection, query)
.then(
(savedQuery: DataModels.Query) => {
NotificationConsoleUtils.logConsoleMessage(
@@ -131,17 +133,11 @@ export class QueriesClient implements ViewModels.QueriesClient {
const options: any = { enableCrossPartitionQuery: true };
const id = NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.InProgress, "Fetching saved queries");
return this.container.documentClientUtility
.queryDocuments(SavedQueries.DatabaseName, SavedQueries.CollectionName, this.fetchQueriesQuery(), options)
return queryDocuments(SavedQueries.DatabaseName, SavedQueries.CollectionName, this.fetchQueriesQuery(), options)
.then(
(queryIterator: QueryIterator<ItemDefinition & Resource>) => {
const fetchQueries = (firstItemIndex: number): Q.Promise<ViewModels.QueryResults> =>
this.container.documentClientUtility.queryDocumentsPage(
queriesCollection.id(),
queryIterator,
firstItemIndex,
options
);
queryDocumentsPage(queriesCollection.id(), queryIterator, firstItemIndex, options);
return QueryUtils.queryAllPages(fetchQueries).then(
(results: ViewModels.QueryResults) => {
let queries: DataModels.Query[] = _.map(results.documents, (document: DataModels.Query) => {
@@ -167,7 +163,7 @@ export class QueriesClient implements ViewModels.QueriesClient {
return Promise.resolve(queries);
},
(error: any) => {
const stringifiedError: string = JSON.stringify(error);
const stringifiedError: string = error.message;
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error,
`Failed to fetch saved queries: ${stringifiedError}`
@@ -179,7 +175,7 @@ export class QueriesClient implements ViewModels.QueriesClient {
},
(error: any) => {
// should never get into this state but we handle this regardless
const stringifiedError: string = JSON.stringify(error);
const stringifiedError: string = error.message;
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error,
`Failed to fetch saved queries: ${stringifiedError}`
@@ -217,17 +213,16 @@ export class QueriesClient implements ViewModels.QueriesClient {
`Deleting query ${query.queryName}`
);
query.id = query.queryName;
const documentId: ViewModels.DocumentId = new DocumentId(
const documentId = new DocumentId(
{
partitionKey: QueriesClient.PartitionKey,
partitionKeyProperty: "id"
} as ViewModels.DocumentsTab,
} as DocumentsTab,
query,
query.queryName
); // TODO: Remove DocumentId's dependency on DocumentsTab
const options: any = { partitionKey: query.resourceId };
return this.container.documentClientUtility
.deleteDocument(queriesCollection, documentId)
return deleteDocument(queriesCollection, documentId)
.then(
() => {
NotificationConsoleUtils.logConsoleMessage(
@@ -237,7 +232,7 @@ export class QueriesClient implements ViewModels.QueriesClient {
return Promise.resolve();
},
(error: any) => {
const stringifiedError: string = JSON.stringify(error);
const stringifiedError: string = error.message;
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error,
`Failed to delete query ${query.queryName}: ${stringifiedError}`
@@ -250,10 +245,10 @@ export class QueriesClient implements ViewModels.QueriesClient {
}
public getResourceId(): string {
const databaseAccount: ViewModels.DatabaseAccount = CosmosClient.databaseAccount();
const databaseAccountName: string = (databaseAccount && databaseAccount.name) || "";
const subscriptionId: string = CosmosClient.subscriptionId() || "";
const resourceGroup: string = CosmosClient.resourceGroup() || "";
const databaseAccount = userContext.databaseAccount;
const databaseAccountName = (databaseAccount && databaseAccount.name) || "";
const subscriptionId = userContext.subscriptionId || "";
const resourceGroup = userContext.resourceGroup || "";
return `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroup}/providers/Microsoft.DocumentDb/databaseAccounts/${databaseAccountName}`;
}

View File

@@ -0,0 +1,81 @@
jest.mock("../../Utils/arm/request");
jest.mock("../CosmosClient");
jest.mock("../DataAccessUtilityBase");
import { AuthType } from "../../AuthType";
import { CreateCollectionParams, DatabaseAccount } from "../../Contracts/DataModels";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { armRequest } from "../../Utils/arm/request";
import { client } from "../CosmosClient";
import { createCollection, constructRpOptions } from "./createCollection";
import { updateUserContext } from "../../UserContext";
describe("createCollection", () => {
const createCollectionParams: CreateCollectionParams = {
createNewDatabase: false,
collectionId: "testContainer",
databaseId: "testDatabase",
databaseLevelThroughput: true,
offerThroughput: 400
};
beforeAll(() => {
updateUserContext({
databaseAccount: {
name: "test"
} as DatabaseAccount,
defaultExperience: DefaultAccountExperienceType.DocumentDB
});
});
it("should call ARM if logged in with AAD", async () => {
window.authType = AuthType.AAD;
await createCollection(createCollectionParams);
expect(armRequest).toHaveBeenCalled();
});
it("should call SDK if not logged in with non-AAD method", async () => {
window.authType = AuthType.MasterKey;
(client as jest.Mock).mockReturnValue({
databases: {
createIfNotExists: () => {
return {
database: {
containers: {
create: () => ({})
}
}
};
}
}
});
await createCollection(createCollectionParams);
expect(client).toHaveBeenCalled();
});
it("constructRpOptions should return the correct options", () => {
expect(constructRpOptions(createCollectionParams)).toEqual({});
const manualThroughputParams: CreateCollectionParams = {
createNewDatabase: false,
collectionId: "testContainer",
databaseId: "testDatabase",
databaseLevelThroughput: false,
offerThroughput: 400
};
expect(constructRpOptions(manualThroughputParams)).toEqual({ throughput: 400 });
const autoPilotThroughputParams: CreateCollectionParams = {
createNewDatabase: false,
collectionId: "testContainer",
databaseId: "testDatabase",
databaseLevelThroughput: false,
offerThroughput: 400,
autoPilotMaxThroughput: 4000
};
expect(constructRpOptions(autoPilotThroughputParams)).toEqual({
autoscaleSettings: {
maxThroughput: 4000
}
});
});
});

View File

@@ -0,0 +1,378 @@
import * as DataModels from "../../Contracts/DataModels";
import { AuthType } from "../../AuthType";
import { ContainerResponse, DatabaseResponse } from "@azure/cosmos";
import { ContainerRequest } from "@azure/cosmos/dist-esm/client/Container/ContainerRequest";
import { DatabaseRequest } from "@azure/cosmos/dist-esm/client/Database/DatabaseRequest";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { RequestOptions } from "@azure/cosmos/dist-esm";
import * as ARMTypes from "../../Utils/arm/generatedClients/2020-04-01/types";
import { client } from "../CosmosClient";
import { createMongoCollectionWithProxy } from "../MongoProxyClient";
import { createUpdateSqlContainer, getSqlContainer } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import {
createUpdateCassandraTable,
getCassandraTable
} from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
import {
createUpdateMongoDBCollection,
getMongoDBCollection
} from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import {
createUpdateGremlinGraph,
getGremlinGraph
} from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import { createUpdateTable, getTable } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
import { logConsoleProgress, logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext";
import { createDatabase } from "./createDatabase";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import { handleError } from "../ErrorHandlingUtils";
export const createCollection = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
const clearMessage = logConsoleProgress(
`Creating a new container ${params.collectionId} for database ${params.databaseId}`
);
try {
let collection: DataModels.Collection;
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
if (params.createNewDatabase) {
const createDatabaseParams: DataModels.CreateDatabaseParams = {
autoPilotMaxThroughput: params.autoPilotMaxThroughput,
databaseId: params.databaseId,
databaseLevelThroughput: params.databaseLevelThroughput,
offerThroughput: params.offerThroughput
};
await createDatabase(createDatabaseParams);
}
collection = await createCollectionWithARM(params);
} else if (userContext.defaultExperience === DefaultAccountExperienceType.MongoDB) {
collection = await createMongoCollectionWithProxy(params);
} else {
collection = await createCollectionWithSDK(params);
}
logConsoleInfo(`Successfully created container ${params.collectionId}`);
return collection;
} catch (error) {
handleError(error, `Error while creating container ${params.collectionId}`, "CreateCollection");
throw error;
} finally {
clearMessage();
}
};
const createCollectionWithARM = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
const defaultExperience = userContext.defaultExperience;
switch (defaultExperience) {
case DefaultAccountExperienceType.DocumentDB:
return createSqlContainer(params);
case DefaultAccountExperienceType.MongoDB:
return createMongoCollection(params);
case DefaultAccountExperienceType.Cassandra:
return createCassandraTable(params);
case DefaultAccountExperienceType.Graph:
return createGraph(params);
case DefaultAccountExperienceType.Table:
return createTable(params);
default:
throw new Error(`Unsupported default experience type: ${defaultExperience}`);
}
};
const createSqlContainer = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
try {
const getResponse = await getSqlContainer(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
params.databaseId,
params.collectionId
);
if (getResponse?.properties?.resource) {
throw new Error(`Create container failed: container with id ${params.collectionId} already exists`);
}
} catch (error) {
if (error.code !== "NotFound") {
throw error;
}
}
const options: ARMTypes.CreateUpdateOptions = constructRpOptions(params);
const resource: ARMTypes.SqlContainerResource = {
id: params.collectionId
};
if (params.analyticalStorageTtl) {
resource.analyticalStorageTtl = params.analyticalStorageTtl;
}
if (params.indexingPolicy) {
resource.indexingPolicy = params.indexingPolicy;
}
if (params.partitionKey) {
resource.partitionKey = params.partitionKey;
}
if (params.uniqueKeyPolicy) {
resource.uniqueKeyPolicy = params.uniqueKeyPolicy;
}
const rpPayload: ARMTypes.SqlDatabaseCreateUpdateParameters = {
properties: {
resource,
options
}
};
const createResponse = await createUpdateSqlContainer(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
params.databaseId,
params.collectionId,
rpPayload
);
return createResponse && (createResponse.properties.resource as DataModels.Collection);
};
const createMongoCollection = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
const mongoWildcardIndexOnAllFields: ARMTypes.MongoIndex[] = [{ key: { keys: ["$**"] } }, { key: { keys: ["_id"] } }];
try {
const getResponse = await getMongoDBCollection(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
params.databaseId,
params.collectionId
);
if (getResponse?.properties?.resource) {
throw new Error(`Create collection failed: collection with id ${params.collectionId} already exists`);
}
} catch (error) {
if (error.code !== "NotFound") {
throw error;
}
}
const options: ARMTypes.CreateUpdateOptions = constructRpOptions(params);
const resource: ARMTypes.MongoDBCollectionResource = {
id: params.collectionId
};
if (params.analyticalStorageTtl) {
resource.analyticalStorageTtl = params.analyticalStorageTtl;
}
if (params.partitionKey) {
const partitionKeyPath: string = params.partitionKey.paths[0];
resource.shardKey = { [partitionKeyPath]: "Hash" };
}
if (params.createMongoWildcardIndex) {
resource.indexes = mongoWildcardIndexOnAllFields;
}
const rpPayload: ARMTypes.MongoDBCollectionCreateUpdateParameters = {
properties: {
resource,
options
}
};
const createResponse = await createUpdateMongoDBCollection(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
params.databaseId,
params.collectionId,
rpPayload
);
if (params.createMongoWildcardIndex) {
TelemetryProcessor.trace(Action.CreateMongoCollectionWithWildcardIndex, ActionModifiers.Mark, {
message: "Mongo Collection created with wildcard index on all fields."
});
}
return createResponse && (createResponse.properties.resource as DataModels.Collection);
};
const createCassandraTable = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
try {
const getResponse = await getCassandraTable(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
params.databaseId,
params.collectionId
);
if (getResponse?.properties?.resource) {
throw new Error(`Create table failed: table with id ${params.collectionId} already exists`);
}
} catch (error) {
if (error.code !== "NotFound") {
throw error;
}
}
const options: ARMTypes.CreateUpdateOptions = constructRpOptions(params);
const resource: ARMTypes.CassandraTableResource = {
id: params.collectionId
};
if (params.analyticalStorageTtl) {
resource.analyticalStorageTtl = params.analyticalStorageTtl;
}
const rpPayload: ARMTypes.CassandraTableCreateUpdateParameters = {
properties: {
resource,
options
}
};
const createResponse = await createUpdateCassandraTable(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
params.databaseId,
params.collectionId,
rpPayload
);
return createResponse && (createResponse.properties.resource as DataModels.Collection);
};
const createGraph = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
try {
const getResponse = await getGremlinGraph(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
params.databaseId,
params.collectionId
);
if (getResponse?.properties?.resource) {
throw new Error(`Create graph failed: graph with id ${params.collectionId} already exists`);
}
} catch (error) {
if (error.code !== "NotFound") {
throw error;
}
}
const options: ARMTypes.CreateUpdateOptions = constructRpOptions(params);
const resource: ARMTypes.GremlinGraphResource = {
id: params.collectionId
};
if (params.indexingPolicy) {
resource.indexingPolicy = params.indexingPolicy;
}
if (params.partitionKey) {
resource.partitionKey = params.partitionKey;
}
if (params.uniqueKeyPolicy) {
resource.uniqueKeyPolicy = params.uniqueKeyPolicy;
}
const rpPayload: ARMTypes.GremlinGraphCreateUpdateParameters = {
properties: {
resource,
options
}
};
const createResponse = await createUpdateGremlinGraph(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
params.databaseId,
params.collectionId,
rpPayload
);
return createResponse && (createResponse.properties.resource as DataModels.Collection);
};
const createTable = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
try {
const getResponse = await getTable(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
params.collectionId
);
if (getResponse?.properties?.resource) {
throw new Error(`Create table failed: table with id ${params.collectionId} already exists`);
}
} catch (error) {
if (error.code !== "NotFound") {
throw error;
}
}
const options: ARMTypes.CreateUpdateOptions = constructRpOptions(params);
const resource: ARMTypes.TableResource = {
id: params.collectionId
};
const rpPayload: ARMTypes.TableCreateUpdateParameters = {
properties: {
resource,
options
}
};
const createResponse = await createUpdateTable(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
params.collectionId,
rpPayload
);
return createResponse && (createResponse.properties.resource as DataModels.Collection);
};
export const constructRpOptions = (params: DataModels.CreateDatabaseParams): ARMTypes.CreateUpdateOptions => {
if (params.databaseLevelThroughput) {
return {};
}
if (params.autoPilotMaxThroughput) {
return {
autoscaleSettings: {
maxThroughput: params.autoPilotMaxThroughput
}
};
}
return {
throughput: params.offerThroughput
};
};
const createCollectionWithSDK = async (params: DataModels.CreateCollectionParams): Promise<DataModels.Collection> => {
const createCollectionBody: ContainerRequest = {
id: params.collectionId,
partitionKey: params.partitionKey || undefined,
indexingPolicy: params.indexingPolicy || undefined,
uniqueKeyPolicy: params.uniqueKeyPolicy || undefined,
analyticalStorageTtl: params.analyticalStorageTtl
} as ContainerRequest; // TODO: remove cast when https://github.com/Azure/azure-cosmos-js/issues/423 is fixed
const collectionOptions: RequestOptions = {};
const createDatabaseBody: DatabaseRequest = { id: params.databaseId };
if (params.databaseLevelThroughput) {
if (params.autoPilotMaxThroughput) {
createDatabaseBody.maxThroughput = params.autoPilotMaxThroughput;
} else {
createDatabaseBody.throughput = params.offerThroughput;
}
} else {
if (params.autoPilotMaxThroughput) {
createCollectionBody.maxThroughput = params.autoPilotMaxThroughput;
} else {
createCollectionBody.throughput = params.offerThroughput;
}
}
const databaseResponse: DatabaseResponse = await client().databases.createIfNotExists(createDatabaseBody);
const collectionResponse: ContainerResponse = await databaseResponse?.database.containers.create(
createCollectionBody,
collectionOptions
);
return collectionResponse?.resource as DataModels.Collection;
};

View File

@@ -0,0 +1,242 @@
import * as DataModels from "../../Contracts/DataModels";
import { AuthType } from "../../AuthType";
import { DatabaseResponse } from "@azure/cosmos";
import { DatabaseRequest } from "@azure/cosmos/dist-esm/client/Database/DatabaseRequest";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import {
CassandraKeyspaceCreateUpdateParameters,
GremlinDatabaseCreateUpdateParameters,
MongoDBDatabaseCreateUpdateParameters,
SqlDatabaseCreateUpdateParameters,
CreateUpdateOptions
} from "../../Utils/arm/generatedClients/2020-04-01/types";
import { client } from "../CosmosClient";
import { createUpdateSqlDatabase, getSqlDatabase } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import {
createUpdateCassandraKeyspace,
getCassandraKeyspace
} from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
import {
createUpdateMongoDBDatabase,
getMongoDBDatabase
} from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import {
createUpdateGremlinDatabase,
getGremlinDatabase
} from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import { handleError } from "../ErrorHandlingUtils";
import { logConsoleProgress, logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext";
export async function createDatabase(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
const clearMessage = logConsoleProgress(`Creating a new database ${params.databaseId}`);
try {
if (userContext.defaultExperience === DefaultAccountExperienceType.Table) {
throw new Error("Creating database resources is not allowed for tables accounts");
}
const database: DataModels.Database = await (window.authType === AuthType.AAD && !userContext.useSDKOperations
? createDatabaseWithARM(params)
: createDatabaseWithSDK(params));
logConsoleInfo(`Successfully created database ${params.databaseId}`);
return database;
} catch (error) {
handleError(error, `Error while creating database ${params.databaseId}`, "CreateDatabase");
throw error;
} finally {
clearMessage();
}
}
async function createDatabaseWithARM(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
const defaultExperience = userContext.defaultExperience;
switch (defaultExperience) {
case DefaultAccountExperienceType.DocumentDB:
return createSqlDatabase(params);
case DefaultAccountExperienceType.MongoDB:
return createMongoDatabase(params);
case DefaultAccountExperienceType.Cassandra:
return createCassandraKeyspace(params);
case DefaultAccountExperienceType.Graph:
return createGremlineDatabase(params);
default:
throw new Error(`Unsupported default experience type: ${defaultExperience}`);
}
}
async function createSqlDatabase(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
try {
const getResponse = await getSqlDatabase(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
params.databaseId
);
if (getResponse?.properties?.resource) {
throw new Error(`Create database failed: database with id ${params.databaseId} already exists`);
}
} catch (error) {
if (error.code !== "NotFound") {
throw error;
}
}
const options: CreateUpdateOptions = constructRpOptions(params);
const rpPayload: SqlDatabaseCreateUpdateParameters = {
properties: {
resource: {
id: params.databaseId
},
options
}
};
const createResponse = await createUpdateSqlDatabase(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
params.databaseId,
rpPayload
);
return createResponse && (createResponse.properties.resource as DataModels.Database);
}
async function createMongoDatabase(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
try {
const getResponse = await getMongoDBDatabase(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
params.databaseId
);
if (getResponse?.properties?.resource) {
throw new Error(`Create database failed: database with id ${params.databaseId} already exists`);
}
} catch (error) {
if (error.code !== "NotFound") {
throw error;
}
}
const options: CreateUpdateOptions = constructRpOptions(params);
const rpPayload: MongoDBDatabaseCreateUpdateParameters = {
properties: {
resource: {
id: params.databaseId
},
options
}
};
const createResponse = await createUpdateMongoDBDatabase(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
params.databaseId,
rpPayload
);
return createResponse && (createResponse.properties.resource as DataModels.Database);
}
async function createCassandraKeyspace(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
try {
const getResponse = await getCassandraKeyspace(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
params.databaseId
);
if (getResponse?.properties?.resource) {
throw new Error(`Create database failed: database with id ${params.databaseId} already exists`);
}
} catch (error) {
if (error.code !== "NotFound") {
throw error;
}
}
const options: CreateUpdateOptions = constructRpOptions(params);
const rpPayload: CassandraKeyspaceCreateUpdateParameters = {
properties: {
resource: {
id: params.databaseId
},
options
}
};
const createResponse = await createUpdateCassandraKeyspace(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
params.databaseId,
rpPayload
);
return createResponse && (createResponse.properties.resource as DataModels.Database);
}
async function createGremlineDatabase(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
try {
const getResponse = await getGremlinDatabase(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
params.databaseId
);
if (getResponse?.properties?.resource) {
throw new Error(`Create database failed: database with id ${params.databaseId} already exists`);
}
} catch (error) {
if (error.code !== "NotFound") {
throw error;
}
}
const options: CreateUpdateOptions = constructRpOptions(params);
const rpPayload: GremlinDatabaseCreateUpdateParameters = {
properties: {
resource: {
id: params.databaseId
},
options
}
};
const createResponse = await createUpdateGremlinDatabase(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
params.databaseId,
rpPayload
);
return createResponse && (createResponse.properties.resource as DataModels.Database);
}
async function createDatabaseWithSDK(params: DataModels.CreateDatabaseParams): Promise<DataModels.Database> {
const createBody: DatabaseRequest = { id: params.databaseId };
if (params.databaseLevelThroughput) {
if (params.autoPilotMaxThroughput) {
createBody.maxThroughput = params.autoPilotMaxThroughput;
} else {
createBody.throughput = params.offerThroughput;
}
}
const response: DatabaseResponse = await client().databases.create(createBody);
return response.resource;
}
function constructRpOptions(params: DataModels.CreateDatabaseParams): CreateUpdateOptions {
if (!params.databaseLevelThroughput) {
return {};
}
if (params.autoPilotMaxThroughput) {
return {
autoscaleSettings: {
maxThroughput: params.autoPilotMaxThroughput
}
};
}
return {
throughput: params.offerThroughput
};
}

View File

@@ -0,0 +1,78 @@
import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
import {
SqlStoredProcedureCreateUpdateParameters,
SqlStoredProcedureResource
} from "../../Utils/arm/generatedClients/2020-04-01/types";
import { client } from "../CosmosClient";
import {
createUpdateSqlStoredProcedure,
getSqlStoredProcedure
} from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { handleError } from "../ErrorHandlingUtils";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext";
export async function createStoredProcedure(
databaseId: string,
collectionId: string,
storedProcedure: StoredProcedureDefinition
): Promise<StoredProcedureDefinition & Resource> {
const clearMessage = logConsoleProgress(`Creating stored procedure ${storedProcedure.id}`);
try {
if (
window.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) {
try {
const getResponse = await getSqlStoredProcedure(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
databaseId,
collectionId,
storedProcedure.id
);
if (getResponse?.properties?.resource) {
throw new Error(
`Create stored procedure failed: stored procedure with id ${storedProcedure.id} already exists`
);
}
} catch (error) {
if (error.code !== "NotFound") {
throw error;
}
}
const createSprocParams: SqlStoredProcedureCreateUpdateParameters = {
properties: {
resource: storedProcedure as SqlStoredProcedureResource,
options: {}
}
};
const rpResponse = await createUpdateSqlStoredProcedure(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
databaseId,
collectionId,
storedProcedure.id,
createSprocParams
);
return rpResponse && (rpResponse.properties?.resource as StoredProcedureDefinition & Resource);
}
const response = await client()
.database(databaseId)
.container(collectionId)
.scripts.storedProcedures.create(storedProcedure);
return response?.resource;
} catch (error) {
handleError(error, `Error while creating stored procedure ${storedProcedure.id}`, "CreateStoredProcedure");
throw error;
} finally {
clearMessage();
}
}

View File

@@ -0,0 +1,76 @@
import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { Resource, TriggerDefinition } from "@azure/cosmos";
import {
SqlTriggerCreateUpdateParameters,
SqlTriggerResource
} from "../../Utils/arm/generatedClients/2020-04-01/types";
import { client } from "../CosmosClient";
import { createUpdateSqlTrigger, getSqlTrigger } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { logError } from "../Logger";
import { sendNotificationForError } from "./sendNotificationForError";
import { userContext } from "../../UserContext";
export async function createTrigger(
databaseId: string,
collectionId: string,
trigger: TriggerDefinition
): Promise<TriggerDefinition & Resource> {
const clearMessage = logConsoleProgress(`Creating trigger ${trigger.id}`);
try {
if (
window.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) {
try {
const getResponse = await getSqlTrigger(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
databaseId,
collectionId,
trigger.id
);
if (getResponse?.properties?.resource) {
throw new Error(`Create trigger failed: ${trigger.id} already exists`);
}
} catch (error) {
if (error.code !== "NotFound") {
throw error;
}
}
const createTriggerParams: SqlTriggerCreateUpdateParameters = {
properties: {
resource: trigger as SqlTriggerResource,
options: {}
}
};
const rpResponse = await createUpdateSqlTrigger(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
databaseId,
collectionId,
trigger.id,
createTriggerParams
);
return rpResponse && (rpResponse.properties?.resource as TriggerDefinition & Resource);
}
const response = await client()
.database(databaseId)
.container(collectionId)
.scripts.triggers.create(trigger);
return response.resource;
} catch (error) {
logConsoleError(`Error while creating trigger ${trigger.id}:\n ${error.message}`);
logError(error.message, "CreateTrigger", error.code);
sendNotificationForError(error);
throw error;
} finally {
clearMessage();
}
}

View File

@@ -0,0 +1,82 @@
import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos";
import {
SqlUserDefinedFunctionCreateUpdateParameters,
SqlUserDefinedFunctionResource
} from "../../Utils/arm/generatedClients/2020-04-01/types";
import { client } from "../CosmosClient";
import {
createUpdateSqlUserDefinedFunction,
getSqlUserDefinedFunction
} from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { handleError } from "../ErrorHandlingUtils";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext";
export async function createUserDefinedFunction(
databaseId: string,
collectionId: string,
userDefinedFunction: UserDefinedFunctionDefinition
): Promise<UserDefinedFunctionDefinition & Resource> {
const clearMessage = logConsoleProgress(`Creating user defined function ${userDefinedFunction.id}`);
try {
if (
window.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) {
try {
const getResponse = await getSqlUserDefinedFunction(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
databaseId,
collectionId,
userDefinedFunction.id
);
if (getResponse?.properties?.resource) {
throw new Error(
`Create user defined function failed: user defined function with id ${userDefinedFunction.id} already exists`
);
}
} catch (error) {
if (error.code !== "NotFound") {
throw error;
}
}
const createUDFParams: SqlUserDefinedFunctionCreateUpdateParameters = {
properties: {
resource: userDefinedFunction as SqlUserDefinedFunctionResource,
options: {}
}
};
const rpResponse = await createUpdateSqlUserDefinedFunction(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
databaseId,
collectionId,
userDefinedFunction.id,
createUDFParams
);
return rpResponse && (rpResponse.properties?.resource as UserDefinedFunctionDefinition & Resource);
}
const response = await client()
.database(databaseId)
.container(collectionId)
.scripts.userDefinedFunctions.create(userDefinedFunction);
return response?.resource;
} catch (error) {
handleError(
error,
`Error while creating user defined function ${userDefinedFunction.id}`,
"CreateUserupdateUserDefinedFunction"
);
throw error;
} finally {
clearMessage();
}
}

View File

@@ -0,0 +1,44 @@
jest.mock("../../Utils/arm/request");
jest.mock("../MessageHandler");
jest.mock("../CosmosClient");
import { deleteCollection } from "./deleteCollection";
import { armRequest } from "../../Utils/arm/request";
import { AuthType } from "../../AuthType";
import { client } from "../CosmosClient";
import { updateUserContext } from "../../UserContext";
import { DatabaseAccount } from "../../Contracts/DataModels";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
describe("deleteCollection", () => {
beforeAll(() => {
updateUserContext({
databaseAccount: {
name: "test"
} as DatabaseAccount,
defaultExperience: DefaultAccountExperienceType.DocumentDB
});
});
it("should call ARM if logged in with AAD", async () => {
window.authType = AuthType.AAD;
await deleteCollection("database", "collection");
expect(armRequest).toHaveBeenCalled();
});
it("should call SDK if not logged in with non-AAD method", async () => {
window.authType = AuthType.MasterKey;
(client as jest.Mock).mockReturnValue({
database: () => {
return {
container: () => {
return {
delete: (): unknown => undefined
};
}
};
}
});
await deleteCollection("database", "collection");
expect(client).toHaveBeenCalled();
});
});

View File

@@ -0,0 +1,53 @@
import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { deleteSqlContainer } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { deleteCassandraTable } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
import { deleteMongoDBCollection } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { deleteGremlinGraph } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import { deleteTable } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
import { handleError } from "../ErrorHandlingUtils";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext";
import { client } from "../CosmosClient";
export async function deleteCollection(databaseId: string, collectionId: string): Promise<void> {
const clearMessage = logConsoleProgress(`Deleting container ${collectionId}`);
try {
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
await deleteCollectionWithARM(databaseId, collectionId);
} else {
await client()
.database(databaseId)
.container(collectionId)
.delete();
}
logConsoleInfo(`Successfully deleted container ${collectionId}`);
} catch (error) {
handleError(error, `Error while deleting container ${collectionId}`, "DeleteCollection");
throw error;
} finally {
clearMessage();
}
}
function deleteCollectionWithARM(databaseId: string, collectionId: string): Promise<void> {
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
const defaultExperience = userContext.defaultExperience;
switch (defaultExperience) {
case DefaultAccountExperienceType.DocumentDB:
return deleteSqlContainer(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
case DefaultAccountExperienceType.MongoDB:
return deleteMongoDBCollection(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
case DefaultAccountExperienceType.Cassandra:
return deleteCassandraTable(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
case DefaultAccountExperienceType.Graph:
return deleteGremlinGraph(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
case DefaultAccountExperienceType.Table:
return deleteTable(subscriptionId, resourceGroup, accountName, collectionId);
default:
throw new Error(`Unsupported default experience type: ${defaultExperience}`);
}
}

View File

@@ -0,0 +1,40 @@
jest.mock("../../Utils/arm/request");
jest.mock("../MessageHandler");
jest.mock("../CosmosClient");
import { deleteDatabase } from "./deleteDatabase";
import { armRequest } from "../../Utils/arm/request";
import { AuthType } from "../../AuthType";
import { client } from "../CosmosClient";
import { updateUserContext } from "../../UserContext";
import { DatabaseAccount } from "../../Contracts/DataModels";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
describe("deleteDatabase", () => {
beforeAll(() => {
updateUserContext({
databaseAccount: {
name: "test"
} as DatabaseAccount,
defaultExperience: DefaultAccountExperienceType.DocumentDB
});
});
it("should call ARM if logged in with AAD", async () => {
window.authType = AuthType.AAD;
await deleteDatabase("database");
expect(armRequest).toHaveBeenCalled();
});
it("should call SDK if not logged in with non-AAD method", async () => {
window.authType = AuthType.MasterKey;
(client as jest.Mock).mockReturnValue({
database: () => {
return {
delete: (): unknown => undefined
};
}
});
await deleteDatabase("database");
expect(client).toHaveBeenCalled();
});
});

View File

@@ -0,0 +1,53 @@
import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { deleteSqlDatabase } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { deleteCassandraKeyspace } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
import { deleteMongoDBDatabase } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { deleteGremlinDatabase } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import { handleError } from "../ErrorHandlingUtils";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext";
import { client } from "../CosmosClient";
export async function deleteDatabase(databaseId: string): Promise<void> {
const clearMessage = logConsoleProgress(`Deleting database ${databaseId}`);
try {
if (userContext.defaultExperience === DefaultAccountExperienceType.Table) {
throw new Error("Deleting database resources is not allowed for tables accounts");
}
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
await deleteDatabaseWithARM(databaseId);
} else {
await client()
.database(databaseId)
.delete();
}
logConsoleInfo(`Successfully deleted database ${databaseId}`);
} catch (error) {
handleError(error, `Error while deleting database ${databaseId}`, "DeleteDatabase");
throw error;
} finally {
clearMessage();
}
}
function deleteDatabaseWithARM(databaseId: string): Promise<void> {
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
const defaultExperience = userContext.defaultExperience;
switch (defaultExperience) {
case DefaultAccountExperienceType.DocumentDB:
return deleteSqlDatabase(subscriptionId, resourceGroup, accountName, databaseId);
case DefaultAccountExperienceType.MongoDB:
return deleteMongoDBDatabase(subscriptionId, resourceGroup, accountName, databaseId);
case DefaultAccountExperienceType.Cassandra:
return deleteCassandraKeyspace(subscriptionId, resourceGroup, accountName, databaseId);
case DefaultAccountExperienceType.Graph:
return deleteGremlinDatabase(subscriptionId, resourceGroup, accountName, databaseId);
default:
throw new Error(`Unsupported default experience type: ${defaultExperience}`);
}
}

View File

@@ -0,0 +1,42 @@
import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { client } from "../CosmosClient";
import { deleteSqlStoredProcedure } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { handleError } from "../ErrorHandlingUtils";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext";
export async function deleteStoredProcedure(
databaseId: string,
collectionId: string,
storedProcedureId: string
): Promise<void> {
const clearMessage = logConsoleProgress(`Deleting stored procedure ${storedProcedureId}`);
try {
if (
window.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) {
await deleteSqlStoredProcedure(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
databaseId,
collectionId,
storedProcedureId
);
} else {
await client()
.database(databaseId)
.container(collectionId)
.scripts.storedProcedure(storedProcedureId)
.delete();
}
} catch (error) {
handleError(error, `Error while deleting stored procedure ${storedProcedureId}`, "DeleteStoredProcedure");
throw error;
} finally {
clearMessage();
}
}

View File

@@ -0,0 +1,38 @@
import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { client } from "../CosmosClient";
import { deleteSqlTrigger } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { handleError } from "../ErrorHandlingUtils";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext";
export async function deleteTrigger(databaseId: string, collectionId: string, triggerId: string): Promise<void> {
const clearMessage = logConsoleProgress(`Deleting trigger ${triggerId}`);
try {
if (
window.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) {
await deleteSqlTrigger(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
databaseId,
collectionId,
triggerId
);
} else {
await client()
.database(databaseId)
.container(collectionId)
.scripts.trigger(triggerId)
.delete();
}
} catch (error) {
handleError(error, `Error while deleting trigger ${triggerId}`, "DeleteTrigger");
throw error;
} finally {
clearMessage();
}
}

View File

@@ -0,0 +1,38 @@
import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { client } from "../CosmosClient";
import { deleteSqlUserDefinedFunction } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { handleError } from "../ErrorHandlingUtils";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext";
export async function deleteUserDefinedFunction(databaseId: string, collectionId: string, id: string): Promise<void> {
const clearMessage = logConsoleProgress(`Deleting user defined function ${id}`);
try {
if (
window.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) {
await deleteSqlUserDefinedFunction(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
databaseId,
collectionId,
id
);
} else {
await client()
.database(databaseId)
.container(collectionId)
.scripts.userDefinedFunction(id)
.delete();
}
} catch (error) {
handleError(error, `Error while deleting user defined function ${id}`, "DeleteUserDefinedFunction");
throw error;
} finally {
clearMessage();
}
}

View File

@@ -0,0 +1,35 @@
jest.mock("../CosmosClient");
import { AuthType } from "../../AuthType";
import { DatabaseAccount } from "../../Contracts/DataModels";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { client } from "../CosmosClient";
import { readCollection } from "./readCollection";
import { updateUserContext } from "../../UserContext";
describe("readCollection", () => {
beforeAll(() => {
updateUserContext({
databaseAccount: {
name: "test"
} as DatabaseAccount,
defaultExperience: DefaultAccountExperienceType.DocumentDB
});
});
it("should call SDK if logged in with resource token", async () => {
window.authType = AuthType.ResourceToken;
(client as jest.Mock).mockReturnValue({
database: () => {
return {
container: () => {
return {
read: (): unknown => ({})
};
}
};
}
});
await readCollection("database", "collection");
expect(client).toHaveBeenCalled();
});
});

View File

@@ -0,0 +1,21 @@
import * as DataModels from "../../Contracts/DataModels";
import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
export async function readCollection(databaseId: string, collectionId: string): Promise<DataModels.Collection> {
let collection: DataModels.Collection;
const clearMessage = logConsoleProgress(`Querying container ${collectionId}`);
try {
const response = await client()
.database(databaseId)
.container(collectionId)
.read();
collection = response.resource as DataModels.Collection;
} catch (error) {
handleError(error, `Error while querying container ${collectionId}`, "ReadCollection");
throw error;
}
clearMessage();
return collection;
}

View File

@@ -0,0 +1,123 @@
import * as DataModels from "../../Contracts/DataModels";
import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { HttpHeaders } from "../Constants";
import { RequestOptions } from "@azure/cosmos/dist-esm";
import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
import { getSqlContainerThroughput } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { getMongoDBCollectionThroughput } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { getCassandraTableThroughput } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
import { getGremlinGraphThroughput } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import { getTableThroughput } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { readOffers } from "./readOffers";
import { userContext } from "../../UserContext";
export const readCollectionOffer = async (
params: DataModels.ReadCollectionOfferParams
): Promise<DataModels.OfferWithHeaders> => {
const clearMessage = logConsoleProgress(`Querying offer for collection ${params.collectionId}`);
let offerId = params.offerId;
if (!offerId) {
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
try {
offerId = await getCollectionOfferIdWithARM(params.databaseId, params.collectionId);
} catch (error) {
clearMessage();
if (error.code !== "NotFound") {
throw error;
}
return undefined;
}
} else {
offerId = await getCollectionOfferIdWithSDK(params.collectionResourceId);
if (!offerId) {
clearMessage();
return undefined;
}
}
}
const options: RequestOptions = {
initialHeaders: {
[HttpHeaders.populateCollectionThroughputInfo]: true
}
};
try {
const response = await client()
.offer(offerId)
.read(options);
return (
response && {
...response.resource,
headers: response.headers
}
);
} catch (error) {
handleError(error, `Error while querying offer for collection ${params.collectionId}`, "ReadCollectionOffer");
throw error;
} finally {
clearMessage();
}
};
const getCollectionOfferIdWithARM = async (databaseId: string, collectionId: string): Promise<string> => {
let rpResponse;
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
const defaultExperience = userContext.defaultExperience;
switch (defaultExperience) {
case DefaultAccountExperienceType.DocumentDB:
rpResponse = await getSqlContainerThroughput(
subscriptionId,
resourceGroup,
accountName,
databaseId,
collectionId
);
break;
case DefaultAccountExperienceType.MongoDB:
rpResponse = await getMongoDBCollectionThroughput(
subscriptionId,
resourceGroup,
accountName,
databaseId,
collectionId
);
break;
case DefaultAccountExperienceType.Cassandra:
rpResponse = await getCassandraTableThroughput(
subscriptionId,
resourceGroup,
accountName,
databaseId,
collectionId
);
break;
case DefaultAccountExperienceType.Graph:
rpResponse = await getGremlinGraphThroughput(
subscriptionId,
resourceGroup,
accountName,
databaseId,
collectionId
);
break;
case DefaultAccountExperienceType.Table:
rpResponse = await getTableThroughput(subscriptionId, resourceGroup, accountName, collectionId);
break;
default:
throw new Error(`Unsupported default experience type: ${defaultExperience}`);
}
return rpResponse?.name;
};
const getCollectionOfferIdWithSDK = async (collectionResourceId: string): Promise<string> => {
const offers = await readOffers();
const offer = offers.find(offer => offer.resource === collectionResourceId);
return offer?.id;
};

View File

@@ -0,0 +1,45 @@
import * as DataModels from "../../Contracts/DataModels";
import * as HeadersUtility from "../HeadersUtility";
import * as ViewModels from "../../Contracts/ViewModels";
import { ContainerDefinition, Resource } from "@azure/cosmos";
import { HttpHeaders } from "../Constants";
import { RequestOptions } from "@azure/cosmos/dist-esm";
import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
interface ResourceWithStatistics {
statistics: DataModels.Statistic[];
}
export const readCollectionQuotaInfo = async (
collection: ViewModels.Collection
): Promise<DataModels.CollectionQuotaInfo> => {
const clearMessage = logConsoleProgress(`Querying containers for database ${collection.id}`);
const options: RequestOptions = {};
options.populateQuotaInfo = true;
options.initialHeaders = options.initialHeaders || {};
options.initialHeaders[HttpHeaders.populatePartitionStatistics] = true;
try {
const response = await client()
.database(collection.databaseId)
.container(collection.id())
.read(options);
const quota: DataModels.CollectionQuotaInfo = HeadersUtility.getQuota(response.headers);
const resource = response.resource as ContainerDefinition & Resource & ResourceWithStatistics;
quota["usageSizeInKB"] = resource.statistics.reduce(
(previousValue: number, currentValue: DataModels.Statistic) => previousValue + currentValue.sizeInKB,
0
);
quota["numPartitions"] = resource.statistics.length;
quota["uniqueKeyPolicy"] = collection.uniqueKeyPolicy; // TODO: Remove after refactoring (#119617)
return quota;
} catch (error) {
handleError(error, `Error while querying quota info for container ${collection.id}`, "ReadCollectionQuotaInfo");
throw error;
} finally {
clearMessage();
}
};

View File

@@ -0,0 +1,45 @@
jest.mock("../../Utils/arm/request");
jest.mock("../CosmosClient");
import { AuthType } from "../../AuthType";
import { DatabaseAccount } from "../../Contracts/DataModels";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { armRequest } from "../../Utils/arm/request";
import { client } from "../CosmosClient";
import { readCollections } from "./readCollections";
import { updateUserContext } from "../../UserContext";
describe("readCollections", () => {
beforeAll(() => {
updateUserContext({
databaseAccount: {
name: "test"
} as DatabaseAccount,
defaultExperience: DefaultAccountExperienceType.DocumentDB
});
});
it("should call ARM if logged in with AAD", async () => {
window.authType = AuthType.AAD;
await readCollections("database");
expect(armRequest).toHaveBeenCalled();
});
it("should call SDK if not logged in with non-AAD method", async () => {
window.authType = AuthType.MasterKey;
(client as jest.Mock).mockReturnValue({
database: () => {
return {
containers: {
readAll: () => {
return {
fetchAll: (): unknown => []
};
}
}
};
}
});
await readCollections("database");
expect(client).toHaveBeenCalled();
});
});

View File

@@ -0,0 +1,67 @@
import * as DataModels from "../../Contracts/DataModels";
import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
import { listSqlContainers } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { listCassandraTables } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
import { listMongoDBCollections } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { listGremlinGraphs } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import { listTables } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext";
export async function readCollections(databaseId: string): Promise<DataModels.Collection[]> {
const clearMessage = logConsoleProgress(`Querying containers for database ${databaseId}`);
try {
if (
window.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience !== DefaultAccountExperienceType.MongoDB &&
userContext.defaultExperience !== DefaultAccountExperienceType.Table
) {
return await readCollectionsWithARM(databaseId);
}
const sdkResponse = await client()
.database(databaseId)
.containers.readAll()
.fetchAll();
return sdkResponse.resources as DataModels.Collection[];
} catch (error) {
handleError(error, `Error while querying containers for database ${databaseId}`, "ReadCollections");
throw error;
} finally {
clearMessage();
}
}
async function readCollectionsWithARM(databaseId: string): Promise<DataModels.Collection[]> {
let rpResponse;
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
const defaultExperience = userContext.defaultExperience;
switch (defaultExperience) {
case DefaultAccountExperienceType.DocumentDB:
rpResponse = await listSqlContainers(subscriptionId, resourceGroup, accountName, databaseId);
break;
case DefaultAccountExperienceType.MongoDB:
rpResponse = await listMongoDBCollections(subscriptionId, resourceGroup, accountName, databaseId);
break;
case DefaultAccountExperienceType.Cassandra:
rpResponse = await listCassandraTables(subscriptionId, resourceGroup, accountName, databaseId);
break;
case DefaultAccountExperienceType.Graph:
rpResponse = await listGremlinGraphs(subscriptionId, resourceGroup, accountName, databaseId);
break;
case DefaultAccountExperienceType.Table:
rpResponse = await listTables(subscriptionId, resourceGroup, accountName);
break;
default:
throw new Error(`Unsupported default experience type: ${defaultExperience}`);
}
return rpResponse?.value?.map(collection => collection.properties?.resource as DataModels.Collection);
}

View File

@@ -0,0 +1,95 @@
import * as DataModels from "../../Contracts/DataModels";
import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { HttpHeaders } from "../Constants";
import { RequestOptions } from "@azure/cosmos/dist-esm";
import { client } from "../CosmosClient";
import { getSqlDatabaseThroughput } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { getMongoDBDatabaseThroughput } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { getCassandraKeyspaceThroughput } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
import { getGremlinDatabaseThroughput } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import { handleError } from "../ErrorHandlingUtils";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { readOffers } from "./readOffers";
import { userContext } from "../../UserContext";
export const readDatabaseOffer = async (
params: DataModels.ReadDatabaseOfferParams
): Promise<DataModels.OfferWithHeaders> => {
const clearMessage = logConsoleProgress(`Querying offer for database ${params.databaseId}`);
let offerId = params.offerId;
if (!offerId) {
offerId = await (window.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience !== DefaultAccountExperienceType.Table
? getDatabaseOfferIdWithARM(params.databaseId)
: getDatabaseOfferIdWithSDK(params.databaseResourceId));
if (!offerId) {
clearMessage();
return undefined;
}
}
const options: RequestOptions = {
initialHeaders: {
[HttpHeaders.populateCollectionThroughputInfo]: true
}
};
try {
const response = await client()
.offer(offerId)
.read(options);
return (
response && {
...response.resource,
headers: response.headers
}
);
} catch (error) {
handleError(error, `Error while querying offer for database ${params.databaseId}`, "ReadDatabaseOffer");
throw error;
} finally {
clearMessage();
}
};
const getDatabaseOfferIdWithARM = async (databaseId: string): Promise<string> => {
let rpResponse;
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
const defaultExperience = userContext.defaultExperience;
try {
switch (defaultExperience) {
case DefaultAccountExperienceType.DocumentDB:
rpResponse = await getSqlDatabaseThroughput(subscriptionId, resourceGroup, accountName, databaseId);
break;
case DefaultAccountExperienceType.MongoDB:
rpResponse = await getMongoDBDatabaseThroughput(subscriptionId, resourceGroup, accountName, databaseId);
break;
case DefaultAccountExperienceType.Cassandra:
rpResponse = await getCassandraKeyspaceThroughput(subscriptionId, resourceGroup, accountName, databaseId);
break;
case DefaultAccountExperienceType.Graph:
rpResponse = await getGremlinDatabaseThroughput(subscriptionId, resourceGroup, accountName, databaseId);
break;
default:
throw new Error(`Unsupported default experience type: ${defaultExperience}`);
}
return rpResponse?.name;
} catch (error) {
if (error.code !== "NotFound") {
throw error;
}
return undefined;
}
};
const getDatabaseOfferIdWithSDK = async (databaseResourceId: string): Promise<string> => {
const offers = await readOffers();
const offer = offers.find(offer => offer.resource === databaseResourceId);
return offer?.id;
};

View File

@@ -0,0 +1,41 @@
jest.mock("../../Utils/arm/request");
jest.mock("../CosmosClient");
import { AuthType } from "../../AuthType";
import { DatabaseAccount } from "../../Contracts/DataModels";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { armRequest } from "../../Utils/arm/request";
import { client } from "../CosmosClient";
import { readDatabases } from "./readDatabases";
import { updateUserContext } from "../../UserContext";
describe("readDatabases", () => {
beforeAll(() => {
updateUserContext({
databaseAccount: {
name: "test"
} as DatabaseAccount,
defaultExperience: DefaultAccountExperienceType.DocumentDB
});
});
it("should call ARM if logged in with AAD", async () => {
window.authType = AuthType.AAD;
await readDatabases();
expect(armRequest).toHaveBeenCalled();
});
it("should call SDK if not logged in with non-AAD method", async () => {
window.authType = AuthType.MasterKey;
(client as jest.Mock).mockReturnValue({
databases: {
readAll: () => {
return {
fetchAll: (): unknown => []
};
}
}
});
await readDatabases();
expect(client).toHaveBeenCalled();
});
});

View File

@@ -0,0 +1,62 @@
import * as DataModels from "../../Contracts/DataModels";
import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
import { listSqlDatabases } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { listCassandraKeyspaces } from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
import { listMongoDBDatabases } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { listGremlinDatabases } from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext";
export async function readDatabases(): Promise<DataModels.Database[]> {
let databases: DataModels.Database[];
const clearMessage = logConsoleProgress(`Querying databases`);
try {
if (
window.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience !== DefaultAccountExperienceType.Table
) {
databases = await readDatabasesWithARM();
} else {
const sdkResponse = await client()
.databases.readAll()
.fetchAll();
databases = sdkResponse.resources as DataModels.Database[];
}
} catch (error) {
handleError(error, `Error while querying databases`, "ReadDatabases");
throw error;
}
clearMessage();
return databases;
}
async function readDatabasesWithARM(): Promise<DataModels.Database[]> {
let rpResponse;
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
const defaultExperience = userContext.defaultExperience;
switch (defaultExperience) {
case DefaultAccountExperienceType.DocumentDB:
rpResponse = await listSqlDatabases(subscriptionId, resourceGroup, accountName);
break;
case DefaultAccountExperienceType.MongoDB:
rpResponse = await listMongoDBDatabases(subscriptionId, resourceGroup, accountName);
break;
case DefaultAccountExperienceType.Cassandra:
rpResponse = await listCassandraKeyspaces(subscriptionId, resourceGroup, accountName);
break;
case DefaultAccountExperienceType.Graph:
rpResponse = await listGremlinDatabases(subscriptionId, resourceGroup, accountName);
break;
default:
throw new Error(`Unsupported default experience type: ${defaultExperience}`);
}
return rpResponse?.value?.map(database => database.properties?.resource as DataModels.Database);
}

View File

@@ -0,0 +1,58 @@
import { userContext } from "../../UserContext";
import { getMongoDBCollection } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { MongoDBCollectionResource } from "../../Utils/arm/generatedClients/2020-04-01/types";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import * as Constants from "../Constants";
import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
import { AuthType } from "../../AuthType";
export async function readMongoDBCollectionThroughRP(
databaseId: string,
collectionId: string
): Promise<MongoDBCollectionResource> {
if (window.authType !== AuthType.AAD) {
return undefined;
}
let collection: MongoDBCollectionResource;
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
const clearMessage = logConsoleProgress(`Reading container ${collectionId}`);
try {
const response = await getMongoDBCollection(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
collection = response.properties.resource;
} catch (error) {
handleError(error, `Error while reading container ${collectionId}`, "ReadMongoDBCollection");
throw error;
}
clearMessage();
return collection;
}
export async function getMongoDBCollectionIndexTransformationProgress(
databaseId: string,
collectionId: string
): Promise<number> {
if (window.authType !== AuthType.AAD) {
return undefined;
}
let indexTransformationPercentage: number;
const clearMessage = logConsoleProgress(`Reading container ${collectionId}`);
try {
const response = await client()
.database(databaseId)
.container(collectionId)
.read({ populateQuotaInfo: true });
indexTransformationPercentage = parseInt(
response.headers[Constants.HttpHeaders.collectionIndexTransformationProgress] as string
);
} catch (error) {
handleError(error, `Error while reading container ${collectionId}`, "ReadMongoDBCollection");
throw error;
}
clearMessage();
return indexTransformationPercentage;
}

View File

@@ -0,0 +1,25 @@
import { Offer } from "../../Contracts/DataModels";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
export const readOffers = async (): Promise<Offer[]> => {
const clearMessage = logConsoleProgress(`Querying offers`);
try {
const response = await client()
.offers.readAll()
.fetchAll();
return response?.resources;
} catch (error) {
// This should be removed when we can correctly identify if an account is serverless when connected using connection string too.
if (error.message.includes("Reading or replacing offers is not supported for serverless accounts")) {
return [];
}
handleError(error, `Error while querying offers`, "ReadOffers");
throw error;
} finally {
clearMessage();
}
};

View File

@@ -0,0 +1,43 @@
import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
import { listSqlStoredProcedures } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext";
export async function readStoredProcedures(
databaseId: string,
collectionId: string
): Promise<(StoredProcedureDefinition & Resource)[]> {
const clearMessage = logConsoleProgress(`Querying stored procedures for container ${collectionId}`);
try {
if (
window.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) {
const rpResponse = await listSqlStoredProcedures(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
databaseId,
collectionId
);
return rpResponse?.value?.map(sproc => sproc.properties?.resource as StoredProcedureDefinition & Resource);
}
const response = await client()
.database(databaseId)
.container(collectionId)
.scripts.storedProcedures.readAll()
.fetchAll();
return response?.resources;
} catch (error) {
handleError(error, `Failed to query stored procedures for container ${collectionId}`, "ReadStoredProcedures");
throw error;
} finally {
clearMessage();
}
}

View File

@@ -0,0 +1,43 @@
import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { Resource, TriggerDefinition } from "@azure/cosmos";
import { client } from "../CosmosClient";
import { listSqlTriggers } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext";
import { handleError } from "../ErrorHandlingUtils";
export async function readTriggers(
databaseId: string,
collectionId: string
): Promise<(TriggerDefinition & Resource)[]> {
const clearMessage = logConsoleProgress(`Querying triggers for container ${collectionId}`);
try {
if (
window.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) {
const rpResponse = await listSqlTriggers(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
databaseId,
collectionId
);
return rpResponse?.value?.map(trigger => trigger.properties?.resource as TriggerDefinition & Resource);
}
const response = await client()
.database(databaseId)
.container(collectionId)
.scripts.triggers.readAll()
.fetchAll();
return response?.resources;
} catch (error) {
handleError(error, `Failed to query triggers for container ${collectionId}`, "ReadTriggers");
throw error;
} finally {
clearMessage();
}
}

View File

@@ -0,0 +1,47 @@
import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos";
import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
import { listSqlUserDefinedFunctions } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext";
export async function readUserDefinedFunctions(
databaseId: string,
collectionId: string
): Promise<(UserDefinedFunctionDefinition & Resource)[]> {
const clearMessage = logConsoleProgress(`Querying user defined functions for container ${collectionId}`);
try {
if (
window.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) {
const rpResponse = await listSqlUserDefinedFunctions(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
databaseId,
collectionId
);
return rpResponse?.value?.map(udf => udf.properties?.resource as UserDefinedFunctionDefinition & Resource);
}
const response = await client()
.database(databaseId)
.container(collectionId)
.scripts.userDefinedFunctions.readAll()
.fetchAll();
return response?.resources;
} catch (error) {
handleError(
error,
`Failed to query user defined functions for container ${collectionId}`,
"ReadUserDefinedFunctions"
);
throw error;
} finally {
clearMessage();
}
}

View File

@@ -0,0 +1,20 @@
import * as Constants from "../Constants";
import { sendMessage } from "../MessageHandler";
import { MessageTypes } from "../../Contracts/ExplorerContracts";
export interface CosmosError {
code: number;
message?: string;
}
export function sendNotificationForError(error: CosmosError): void {
if (error && error.code === Constants.HttpStatusCodes.Forbidden) {
if (error.message && error.message.toLowerCase().indexOf("sharedoffer is disabled for your account") > 0) {
return;
}
sendMessage({
type: MessageTypes.ForbiddenError,
reason: error && error.message ? error.message : error
});
}
}

View File

@@ -0,0 +1,227 @@
import { AuthType } from "../../AuthType";
import { Collection } from "../../Contracts/DataModels";
import { ContainerDefinition } from "@azure/cosmos";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import {
CreateUpdateOptions,
ExtendedResourceProperties,
MongoDBCollectionCreateUpdateParameters,
MongoDBCollectionResource,
SqlContainerCreateUpdateParameters,
SqlContainerResource
} from "../../Utils/arm/generatedClients/2020-04-01/types";
import { RequestOptions } from "@azure/cosmos/dist-esm";
import { client } from "../CosmosClient";
import { createUpdateSqlContainer, getSqlContainer } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import {
createUpdateCassandraTable,
getCassandraTable
} from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
import {
createUpdateMongoDBCollection,
getMongoDBCollection
} from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import {
createUpdateGremlinGraph,
getGremlinGraph
} from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import { createUpdateTable, getTable } from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
import { handleError } from "../ErrorHandlingUtils";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext";
export async function updateCollection(
databaseId: string,
collectionId: string,
newCollection: Collection,
options: RequestOptions = {}
): Promise<Collection> {
let collection: Collection;
const clearMessage = logConsoleProgress(`Updating container ${collectionId}`);
try {
if (
window.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience !== DefaultAccountExperienceType.MongoDB &&
userContext.defaultExperience !== DefaultAccountExperienceType.Table
) {
collection = await updateCollectionWithARM(databaseId, collectionId, newCollection);
} else {
const sdkResponse = await client()
.database(databaseId)
.container(collectionId)
.replace(newCollection as ContainerDefinition, options);
collection = sdkResponse.resource as Collection;
}
logConsoleInfo(`Successfully updated container ${collectionId}`);
return collection;
} catch (error) {
handleError(error, `Failed to update container ${collectionId}`, "UpdateCollection");
throw error;
} finally {
clearMessage();
}
}
async function updateCollectionWithARM(
databaseId: string,
collectionId: string,
newCollection: Collection
): Promise<Collection> {
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
const defaultExperience = userContext.defaultExperience;
switch (defaultExperience) {
case DefaultAccountExperienceType.DocumentDB:
return updateSqlContainer(databaseId, collectionId, subscriptionId, resourceGroup, accountName, newCollection);
case DefaultAccountExperienceType.Cassandra:
return updateCassandraTable(databaseId, collectionId, subscriptionId, resourceGroup, accountName, newCollection);
case DefaultAccountExperienceType.Graph:
return updateGremlinGraph(databaseId, collectionId, subscriptionId, resourceGroup, accountName, newCollection);
case DefaultAccountExperienceType.Table:
return updateTable(collectionId, subscriptionId, resourceGroup, accountName, newCollection);
default:
throw new Error(`Unsupported default experience type: ${defaultExperience}`);
}
}
async function updateSqlContainer(
databaseId: string,
collectionId: string,
subscriptionId: string,
resourceGroup: string,
accountName: string,
newCollection: Collection
): Promise<Collection> {
const getResponse = await getSqlContainer(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
if (getResponse && getResponse.properties && getResponse.properties.resource) {
getResponse.properties.resource = newCollection as SqlContainerResource & ExtendedResourceProperties;
const updateResponse = await createUpdateSqlContainer(
subscriptionId,
resourceGroup,
accountName,
databaseId,
collectionId,
getResponse as SqlContainerCreateUpdateParameters
);
return updateResponse && (updateResponse.properties.resource as Collection);
}
throw new Error(`Sql container to update does not exist. Database id: ${databaseId} Collection id: ${collectionId}`);
}
export async function updateMongoDBCollectionThroughRP(
databaseId: string,
collectionId: string,
newCollection: MongoDBCollectionResource,
updateOptions?: CreateUpdateOptions
): Promise<MongoDBCollectionResource> {
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
const getResponse = await getMongoDBCollection(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
if (getResponse && getResponse.properties && getResponse.properties.resource) {
const updateParams: MongoDBCollectionCreateUpdateParameters = {
properties: {
resource: newCollection,
options: updateOptions
}
};
const updateResponse = await createUpdateMongoDBCollection(
subscriptionId,
resourceGroup,
accountName,
databaseId,
collectionId,
updateParams
);
return updateResponse && (updateResponse.properties.resource as MongoDBCollectionResource);
}
throw new Error(
`MongoDB collection to update does not exist. Database id: ${databaseId} Collection id: ${collectionId}`
);
}
async function updateCassandraTable(
databaseId: string,
collectionId: string,
subscriptionId: string,
resourceGroup: string,
accountName: string,
newCollection: Collection
): Promise<Collection> {
const getResponse = await getCassandraTable(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
if (getResponse && getResponse.properties && getResponse.properties.resource) {
getResponse.properties.resource = newCollection as SqlContainerResource & ExtendedResourceProperties;
const updateResponse = await createUpdateCassandraTable(
subscriptionId,
resourceGroup,
accountName,
databaseId,
collectionId,
getResponse as SqlContainerCreateUpdateParameters
);
return updateResponse && (updateResponse.properties.resource as Collection);
}
throw new Error(
`Cassandra table to update does not exist. Database id: ${databaseId} Collection id: ${collectionId}`
);
}
async function updateGremlinGraph(
databaseId: string,
collectionId: string,
subscriptionId: string,
resourceGroup: string,
accountName: string,
newCollection: Collection
): Promise<Collection> {
const getResponse = await getGremlinGraph(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
if (getResponse && getResponse.properties && getResponse.properties.resource) {
getResponse.properties.resource = newCollection as SqlContainerResource & ExtendedResourceProperties;
const updateResponse = await createUpdateGremlinGraph(
subscriptionId,
resourceGroup,
accountName,
databaseId,
collectionId,
getResponse as SqlContainerCreateUpdateParameters
);
return updateResponse && (updateResponse.properties.resource as Collection);
}
throw new Error(`Gremlin graph to update does not exist. Database id: ${databaseId} Collection id: ${collectionId}`);
}
async function updateTable(
collectionId: string,
subscriptionId: string,
resourceGroup: string,
accountName: string,
newCollection: Collection
): Promise<Collection> {
const getResponse = await getTable(subscriptionId, resourceGroup, accountName, collectionId);
if (getResponse && getResponse.properties && getResponse.properties.resource) {
getResponse.properties.resource = newCollection as SqlContainerResource & ExtendedResourceProperties;
const updateResponse = await createUpdateTable(
subscriptionId,
resourceGroup,
accountName,
collectionId,
getResponse as SqlContainerCreateUpdateParameters
);
return updateResponse && (updateResponse.properties.resource as Collection);
}
throw new Error(`Table to update does not exist. Table id: ${collectionId}`);
}

View File

@@ -0,0 +1,418 @@
import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { HttpHeaders } from "../Constants";
import { Offer, UpdateOfferParams } from "../../Contracts/DataModels";
import { OfferDefinition } from "@azure/cosmos";
import { RequestOptions } from "@azure/cosmos/dist-esm";
import { ThroughputSettingsUpdateParameters } from "../../Utils/arm/generatedClients/2020-04-01/types";
import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { readCollectionOffer } from "./readCollectionOffer";
import { readDatabaseOffer } from "./readDatabaseOffer";
import {
updateSqlDatabaseThroughput,
migrateSqlDatabaseToAutoscale,
migrateSqlDatabaseToManualThroughput,
migrateSqlContainerToAutoscale,
migrateSqlContainerToManualThroughput,
updateSqlContainerThroughput
} from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import {
updateCassandraKeyspaceThroughput,
migrateCassandraKeyspaceToAutoscale,
migrateCassandraKeyspaceToManualThroughput,
migrateCassandraTableToAutoscale,
migrateCassandraTableToManualThroughput,
updateCassandraTableThroughput
} from "../../Utils/arm/generatedClients/2020-04-01/cassandraResources";
import {
updateMongoDBDatabaseThroughput,
migrateMongoDBDatabaseToAutoscale,
migrateMongoDBDatabaseToManualThroughput,
migrateMongoDBCollectionToAutoscale,
migrateMongoDBCollectionToManualThroughput,
updateMongoDBCollectionThroughput
} from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import {
updateGremlinDatabaseThroughput,
migrateGremlinDatabaseToAutoscale,
migrateGremlinDatabaseToManualThroughput,
migrateGremlinGraphToAutoscale,
migrateGremlinGraphToManualThroughput,
updateGremlinGraphThroughput
} from "../../Utils/arm/generatedClients/2020-04-01/gremlinResources";
import { userContext } from "../../UserContext";
import {
migrateTableToAutoscale,
migrateTableToManualThroughput,
updateTableThroughput
} from "../../Utils/arm/generatedClients/2020-04-01/tableResources";
export const updateOffer = async (params: UpdateOfferParams): Promise<Offer> => {
let updatedOffer: Offer;
const offerResourceText: string = params.collectionId
? `collection ${params.collectionId}`
: `database ${params.databaseId}`;
const clearMessage = logConsoleProgress(`Updating offer for ${offerResourceText}`);
try {
if (window.authType === AuthType.AAD && !userContext.useSDKOperations) {
if (params.collectionId) {
updatedOffer = await updateCollectionOfferWithARM(params);
} else if (userContext.defaultExperience === DefaultAccountExperienceType.Table) {
// update table's database offer with SDK since RP doesn't support it
updatedOffer = await updateOfferWithSDK(params);
} else {
updatedOffer = await updateDatabaseOfferWithARM(params);
}
} else {
updatedOffer = await updateOfferWithSDK(params);
}
logConsoleInfo(`Successfully updated offer for ${offerResourceText}`);
return updatedOffer;
} catch (error) {
handleError(error, `Error updating offer for ${offerResourceText}`, "UpdateCollection");
throw error;
} finally {
clearMessage();
}
};
const updateCollectionOfferWithARM = async (params: UpdateOfferParams): Promise<Offer> => {
try {
switch (userContext.defaultExperience) {
case DefaultAccountExperienceType.DocumentDB:
await updateSqlContainerOffer(params);
break;
case DefaultAccountExperienceType.MongoDB:
await updateMongoCollectionOffer(params);
break;
case DefaultAccountExperienceType.Cassandra:
await updateCassandraTableOffer(params);
break;
case DefaultAccountExperienceType.Graph:
await updateGremlinGraphOffer(params);
break;
case DefaultAccountExperienceType.Table:
await updateTableOffer(params);
break;
default:
throw new Error(`Unsupported default experience type: ${userContext.defaultExperience}`);
}
} catch (error) {
if (error.code !== "MethodNotAllowed") {
throw error;
}
}
return await readCollectionOffer({
collectionId: params.collectionId,
databaseId: params.databaseId,
offerId: params.currentOffer.id
});
};
const updateDatabaseOfferWithARM = async (params: UpdateOfferParams): Promise<Offer> => {
try {
switch (userContext.defaultExperience) {
case DefaultAccountExperienceType.DocumentDB:
await updateSqlDatabaseOffer(params);
break;
case DefaultAccountExperienceType.MongoDB:
await updateMongoDatabaseOffer(params);
break;
case DefaultAccountExperienceType.Cassandra:
await updateCassandraKeyspaceOffer(params);
break;
case DefaultAccountExperienceType.Graph:
await updateGremlinDatabaseOffer(params);
break;
default:
throw new Error(`Unsupported default experience type: ${userContext.defaultExperience}`);
}
} catch (error) {
if (error.code !== "MethodNotAllowed") {
throw error;
}
}
return await readDatabaseOffer({
databaseId: params.databaseId,
offerId: params.currentOffer.id
});
};
const updateSqlContainerOffer = async (params: UpdateOfferParams): Promise<void> => {
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
if (params.migrateToAutoPilot) {
await migrateSqlContainerToAutoscale(
subscriptionId,
resourceGroup,
accountName,
params.databaseId,
params.collectionId
);
} else if (params.migrateToManual) {
await migrateSqlContainerToManualThroughput(
subscriptionId,
resourceGroup,
accountName,
params.databaseId,
params.collectionId
);
} else {
const body: ThroughputSettingsUpdateParameters = createUpdateOfferBody(params);
await updateSqlContainerThroughput(
subscriptionId,
resourceGroup,
accountName,
params.databaseId,
params.collectionId,
body
);
}
};
const updateMongoCollectionOffer = async (params: UpdateOfferParams): Promise<void> => {
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
if (params.migrateToAutoPilot) {
await migrateMongoDBCollectionToAutoscale(
subscriptionId,
resourceGroup,
accountName,
params.databaseId,
params.collectionId
);
} else if (params.migrateToManual) {
await migrateMongoDBCollectionToManualThroughput(
subscriptionId,
resourceGroup,
accountName,
params.databaseId,
params.collectionId
);
} else {
const body: ThroughputSettingsUpdateParameters = createUpdateOfferBody(params);
await updateMongoDBCollectionThroughput(
subscriptionId,
resourceGroup,
accountName,
params.databaseId,
params.collectionId,
body
);
}
};
const updateCassandraTableOffer = async (params: UpdateOfferParams): Promise<void> => {
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
if (params.migrateToAutoPilot) {
await migrateCassandraTableToAutoscale(
subscriptionId,
resourceGroup,
accountName,
params.databaseId,
params.collectionId
);
} else if (params.migrateToManual) {
await migrateCassandraTableToManualThroughput(
subscriptionId,
resourceGroup,
accountName,
params.databaseId,
params.collectionId
);
} else {
const body: ThroughputSettingsUpdateParameters = createUpdateOfferBody(params);
await updateCassandraTableThroughput(
subscriptionId,
resourceGroup,
accountName,
params.databaseId,
params.collectionId,
body
);
}
};
const updateGremlinGraphOffer = async (params: UpdateOfferParams): Promise<void> => {
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
if (params.migrateToAutoPilot) {
await migrateGremlinGraphToAutoscale(
subscriptionId,
resourceGroup,
accountName,
params.databaseId,
params.collectionId
);
} else if (params.migrateToManual) {
await migrateGremlinGraphToManualThroughput(
subscriptionId,
resourceGroup,
accountName,
params.databaseId,
params.collectionId
);
} else {
const body: ThroughputSettingsUpdateParameters = createUpdateOfferBody(params);
await updateGremlinGraphThroughput(
subscriptionId,
resourceGroup,
accountName,
params.databaseId,
params.collectionId,
body
);
}
};
const updateTableOffer = async (params: UpdateOfferParams): Promise<void> => {
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
if (params.migrateToAutoPilot) {
await migrateTableToAutoscale(subscriptionId, resourceGroup, accountName, params.collectionId);
} else if (params.migrateToManual) {
await migrateTableToManualThroughput(subscriptionId, resourceGroup, accountName, params.collectionId);
} else {
const body: ThroughputSettingsUpdateParameters = createUpdateOfferBody(params);
await updateTableThroughput(subscriptionId, resourceGroup, accountName, params.collectionId, body);
}
};
const updateSqlDatabaseOffer = async (params: UpdateOfferParams): Promise<void> => {
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
if (params.migrateToAutoPilot) {
await migrateSqlDatabaseToAutoscale(subscriptionId, resourceGroup, accountName, params.databaseId);
} else if (params.migrateToManual) {
await migrateSqlDatabaseToManualThroughput(subscriptionId, resourceGroup, accountName, params.databaseId);
} else {
const body: ThroughputSettingsUpdateParameters = createUpdateOfferBody(params);
await updateSqlDatabaseThroughput(subscriptionId, resourceGroup, accountName, params.databaseId, body);
}
};
const updateMongoDatabaseOffer = async (params: UpdateOfferParams): Promise<void> => {
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
if (params.migrateToAutoPilot) {
await migrateMongoDBDatabaseToAutoscale(subscriptionId, resourceGroup, accountName, params.databaseId);
} else if (params.migrateToManual) {
await migrateMongoDBDatabaseToManualThroughput(subscriptionId, resourceGroup, accountName, params.databaseId);
} else {
const body: ThroughputSettingsUpdateParameters = createUpdateOfferBody(params);
await updateMongoDBDatabaseThroughput(subscriptionId, resourceGroup, accountName, params.databaseId, body);
}
};
const updateCassandraKeyspaceOffer = async (params: UpdateOfferParams): Promise<void> => {
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
if (params.migrateToAutoPilot) {
await migrateCassandraKeyspaceToAutoscale(subscriptionId, resourceGroup, accountName, params.databaseId);
} else if (params.migrateToManual) {
await migrateCassandraKeyspaceToManualThroughput(subscriptionId, resourceGroup, accountName, params.databaseId);
} else {
const body: ThroughputSettingsUpdateParameters = createUpdateOfferBody(params);
await updateCassandraKeyspaceThroughput(subscriptionId, resourceGroup, accountName, params.databaseId, body);
}
};
const updateGremlinDatabaseOffer = async (params: UpdateOfferParams): Promise<void> => {
const subscriptionId = userContext.subscriptionId;
const resourceGroup = userContext.resourceGroup;
const accountName = userContext.databaseAccount.name;
if (params.migrateToAutoPilot) {
await migrateGremlinDatabaseToAutoscale(subscriptionId, resourceGroup, accountName, params.databaseId);
} else if (params.migrateToManual) {
await migrateGremlinDatabaseToManualThroughput(subscriptionId, resourceGroup, accountName, params.databaseId);
} else {
const body: ThroughputSettingsUpdateParameters = createUpdateOfferBody(params);
await updateGremlinDatabaseThroughput(subscriptionId, resourceGroup, accountName, params.databaseId, body);
}
};
const createUpdateOfferBody = (params: UpdateOfferParams): ThroughputSettingsUpdateParameters => {
const body: ThroughputSettingsUpdateParameters = {
properties: {
resource: {}
}
};
if (params.autopilotThroughput) {
body.properties.resource.autoscaleSettings = {
maxThroughput: params.autopilotThroughput
};
} else {
body.properties.resource.throughput = params.manualThroughput;
}
return body;
};
const updateOfferWithSDK = async (params: UpdateOfferParams): Promise<Offer> => {
const currentOffer = params.currentOffer;
const newOffer: Offer = {
content: {
offerThroughput: undefined
},
_etag: undefined,
_ts: undefined,
_rid: currentOffer._rid,
_self: currentOffer._self,
id: currentOffer.id,
offerResourceId: currentOffer.offerResourceId,
offerVersion: currentOffer.offerVersion,
offerType: currentOffer.offerType,
resource: currentOffer.resource
};
if (params.autopilotThroughput) {
newOffer.content.offerAutopilotSettings = {
maxThroughput: params.autopilotThroughput
};
} else {
newOffer.content.offerThroughput = params.manualThroughput;
}
const options: RequestOptions = {};
if (params.migrateToAutoPilot) {
options.initialHeaders = {
[HttpHeaders.migrateOfferToAutopilot]: "true"
};
delete newOffer.content.offerAutopilotSettings;
} else if (params.migrateToManual) {
options.initialHeaders = {
[HttpHeaders.migrateOfferToManualThroughput]: "true"
};
newOffer.content.offerAutopilotSettings = { maxThroughput: 0 };
}
const sdkResponse = await client()
.offer(params.currentOffer.id)
// TODO Remove casting when SDK types are fixed (https://github.com/Azure/azure-sdk-for-js/issues/10660)
.replace((newOffer as unknown) as OfferDefinition, options);
return sdkResponse?.resource;
};

View File

@@ -0,0 +1,24 @@
import { updateOfferThroughputBeyondLimit } from "./updateOfferThroughputBeyondLimit";
describe("updateOfferThroughputBeyondLimit", () => {
it("should call fetch", async () => {
window.fetch = jest.fn(() => {
return {
ok: true
};
});
window.dataExplorer = {
logConsoleData: jest.fn(),
deleteInProgressConsoleDataWithId: jest.fn()
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any;
await updateOfferThroughputBeyondLimit({
subscriptionId: "foo",
resourceGroup: "foo",
databaseAccountName: "foo",
databaseName: "foo",
throughput: 1000000000
});
expect(window.fetch).toHaveBeenCalled();
});
});

View File

@@ -0,0 +1,50 @@
import { Platform, configContext } from "../../ConfigContext";
import { getAuthorizationHeader } from "../../Utils/AuthorizationUtils";
import { AutoPilotOfferSettings } from "../../Contracts/DataModels";
import { logConsoleProgress, logConsoleInfo, logConsoleError } from "../../Utils/NotificationConsoleUtils";
import { HttpHeaders } from "../Constants";
interface UpdateOfferThroughputRequest {
subscriptionId: string;
resourceGroup: string;
databaseAccountName: string;
databaseName: string;
collectionName?: string;
throughput: number;
offerAutopilotSettings?: AutoPilotOfferSettings;
}
export async function updateOfferThroughputBeyondLimit(request: UpdateOfferThroughputRequest): Promise<void> {
if (configContext.platform !== Platform.Portal) {
throw new Error("Updating throughput beyond specified limit is not supported on this platform");
}
const resourceDescriptionInfo = request.collectionName
? `database ${request.databaseName} and container ${request.collectionName}`
: `database ${request.databaseName}`;
const clearMessage = logConsoleProgress(
`Requesting increase in throughput to ${request.throughput} for ${resourceDescriptionInfo}`
);
const url = `${configContext.BACKEND_ENDPOINT}/api/offerthroughputrequest/updatebeyondspecifiedlimit`;
const authorizationHeader = getAuthorizationHeader();
const response = await fetch(url, {
method: "POST",
body: JSON.stringify(request),
headers: { [authorizationHeader.header]: authorizationHeader.token, [HttpHeaders.contentType]: "application/json" }
});
if (response.ok) {
logConsoleInfo(
`Successfully requested an increase in throughput to ${request.throughput} for ${resourceDescriptionInfo}`
);
clearMessage();
return undefined;
}
const error = await response.json();
logConsoleError(`Failed to request an increase in throughput for ${request.throughput}: ${error.message}`);
clearMessage();
throw new Error(error.message);
}

View File

@@ -0,0 +1,72 @@
import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { Resource, StoredProcedureDefinition } from "@azure/cosmos";
import {
SqlStoredProcedureCreateUpdateParameters,
SqlStoredProcedureResource
} from "../../Utils/arm/generatedClients/2020-04-01/types";
import { client } from "../CosmosClient";
import {
createUpdateSqlStoredProcedure,
getSqlStoredProcedure
} from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { handleError } from "../ErrorHandlingUtils";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext";
export async function updateStoredProcedure(
databaseId: string,
collectionId: string,
storedProcedure: StoredProcedureDefinition
): Promise<StoredProcedureDefinition & Resource> {
const clearMessage = logConsoleProgress(`Updating stored procedure ${storedProcedure.id}`);
try {
if (
window.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) {
const getResponse = await getSqlStoredProcedure(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
databaseId,
collectionId,
storedProcedure.id
);
if (getResponse?.properties?.resource) {
const createSprocParams: SqlStoredProcedureCreateUpdateParameters = {
properties: {
resource: storedProcedure as SqlStoredProcedureResource,
options: {}
}
};
const rpResponse = await createUpdateSqlStoredProcedure(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
databaseId,
collectionId,
storedProcedure.id,
createSprocParams
);
return rpResponse && (rpResponse.properties?.resource as StoredProcedureDefinition & Resource);
}
throw new Error(`Failed to update stored procedure: ${storedProcedure.id} does not exist.`);
}
const response = await client()
.database(databaseId)
.container(collectionId)
.scripts.storedProcedure(storedProcedure.id)
.replace(storedProcedure);
return response?.resource;
} catch (error) {
handleError(error, `Error while updating stored procedure ${storedProcedure.id}`, "UpdateStoredProcedure");
throw error;
} finally {
clearMessage();
}
}

View File

@@ -0,0 +1,69 @@
import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import {
SqlTriggerCreateUpdateParameters,
SqlTriggerResource
} from "../../Utils/arm/generatedClients/2020-04-01/types";
import { TriggerDefinition } from "@azure/cosmos";
import { client } from "../CosmosClient";
import { createUpdateSqlTrigger, getSqlTrigger } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { handleError } from "../ErrorHandlingUtils";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext";
export async function updateTrigger(
databaseId: string,
collectionId: string,
trigger: TriggerDefinition
): Promise<TriggerDefinition> {
const clearMessage = logConsoleProgress(`Updating trigger ${trigger.id}`);
try {
if (
window.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) {
const getResponse = await getSqlTrigger(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
databaseId,
collectionId,
trigger.id
);
if (getResponse?.properties?.resource) {
const createTriggerParams: SqlTriggerCreateUpdateParameters = {
properties: {
resource: trigger as SqlTriggerResource,
options: {}
}
};
const rpResponse = await createUpdateSqlTrigger(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
databaseId,
collectionId,
trigger.id,
createTriggerParams
);
return rpResponse && (rpResponse.properties?.resource as TriggerDefinition);
}
throw new Error(`Failed to update trigger: ${trigger.id} does not exist.`);
}
const response = await client()
.database(databaseId)
.container(collectionId)
.scripts.trigger(trigger.id)
.replace(trigger);
return response?.resource;
} catch (error) {
handleError(error, `Error while updating trigger ${trigger.id}`, "UpdateTrigger");
throw error;
} finally {
clearMessage();
}
}

View File

@@ -0,0 +1,76 @@
import { AuthType } from "../../AuthType";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { Resource, UserDefinedFunctionDefinition } from "@azure/cosmos";
import {
SqlUserDefinedFunctionCreateUpdateParameters,
SqlUserDefinedFunctionResource
} from "../../Utils/arm/generatedClients/2020-04-01/types";
import { client } from "../CosmosClient";
import {
createUpdateSqlUserDefinedFunction,
getSqlUserDefinedFunction
} from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { handleError } from "../ErrorHandlingUtils";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext";
export async function updateUserDefinedFunction(
databaseId: string,
collectionId: string,
userDefinedFunction: UserDefinedFunctionDefinition
): Promise<UserDefinedFunctionDefinition & Resource> {
const clearMessage = logConsoleProgress(`Updating user defined function ${userDefinedFunction.id}`);
try {
if (
window.authType === AuthType.AAD &&
!userContext.useSDKOperations &&
userContext.defaultExperience === DefaultAccountExperienceType.DocumentDB
) {
const getResponse = await getSqlUserDefinedFunction(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
databaseId,
collectionId,
userDefinedFunction.id
);
if (getResponse?.properties?.resource) {
const createUDFParams: SqlUserDefinedFunctionCreateUpdateParameters = {
properties: {
resource: userDefinedFunction as SqlUserDefinedFunctionResource,
options: {}
}
};
const rpResponse = await createUpdateSqlUserDefinedFunction(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
databaseId,
collectionId,
userDefinedFunction.id,
createUDFParams
);
return rpResponse && (rpResponse.properties?.resource as UserDefinedFunctionDefinition & Resource);
}
throw new Error(`Failed to update user defined function: ${userDefinedFunction.id} does not exist.`);
}
const response = await client()
.database(databaseId)
.container(collectionId)
.scripts.userDefinedFunction(userDefinedFunction.id)
.replace(userDefinedFunction);
return response?.resource;
} catch (error) {
handleError(
error,
`Error while updating user defined function ${userDefinedFunction.id}`,
"UpdateUserupdateUserDefinedFunction"
);
throw error;
} finally {
clearMessage();
}
}

View File

@@ -1,81 +0,0 @@
export enum Platform {
Portal = "Portal",
Hosted = "Hosted",
Emulator = "Emulator"
}
interface Config {
platform: Platform;
allowedParentFrameOrigins: RegExp;
gitSha?: string;
proxyPath?: string;
AAD_ENDPOINT: string;
ARM_AUTH_AREA: string;
ARM_ENDPOINT: string;
EMULATOR_ENDPOINT?: string;
ARM_API_VERSION: string;
GRAPH_ENDPOINT: string;
GRAPH_API_VERSION: string;
ARCADIA_ENDPOINT: string;
ARCADIA_LIVY_ENDPOINT_DNS_ZONE: string;
BACKEND_ENDPOINT?: string;
MONGO_BACKEND_ENDPOINT?: string;
PROXY_PATH?: string;
JUNO_ENDPOINT: string;
GITHUB_CLIENT_ID: string;
GITHUB_CLIENT_SECRET?: string; // No need to inject secret for prod. Juno already knows it.
hostedExplorerURL: string;
}
// Default configuration
let config: Config = {
platform: Platform.Portal,
allowedParentFrameOrigins: /^https:\/\/portal\.azure\.com$|^https:\/\/portal\.azure\.us$|^https:\/\/portal\.azure\.cn$|^https:\/\/portal\.microsoftazure\.de$|^https:\/\/.+\.portal\.azure\.com$|^https:\/\/.+\.portal\.azure\.us$|^https:\/\/.+\.portal\.azure\.cn$|^https:\/\/.+\.portal\.microsoftazure\.de$|^https:\/\/main\.documentdb\.ext\.azure\.com$|^https:\/\/main\.documentdb\.ext\.microsoftazure\.de$|^https:\/\/main\.documentdb\.ext\.azure\.cn$|^https:\/\/main\.documentdb\.ext\.azure\.us$/,
// Webpack injects this at build time
gitSha: process.env.GIT_SHA,
hostedExplorerURL: "https://cosmos.azure.com/",
AAD_ENDPOINT: "https://login.microsoftonline.com/",
ARM_AUTH_AREA: "https://management.azure.com/",
ARM_ENDPOINT: "https://management.azure.com/",
ARM_API_VERSION: "2016-06-01",
GRAPH_ENDPOINT: "https://graph.windows.net",
GRAPH_API_VERSION: "1.6",
ARCADIA_ENDPOINT: "https://workspaceartifacts.projectarcadia.net",
ARCADIA_LIVY_ENDPOINT_DNS_ZONE: "dev.azuresynapse.net",
GITHUB_CLIENT_ID: "6cb2f63cf6f7b5cbdeca", // Registered OAuth app: https://github.com/settings/applications/1189306
JUNO_ENDPOINT: "https://tools.cosmos.azure.com"
};
// Injected for local develpment. These will be removed in the production bundle by webpack
if (process.env.NODE_ENV === "development") {
const port: string = process.env.PORT || "1234";
config.BACKEND_ENDPOINT = "https://localhost:" + port;
config.MONGO_BACKEND_ENDPOINT = "https://localhost:" + port;
config.PROXY_PATH = "/proxy";
config.EMULATOR_ENDPOINT = "https://localhost:8081";
}
export async function initializeConfiguration(): Promise<Config> {
try {
const response = await fetch("./config.json");
if (response.status === 200) {
try {
const externalConfig = await response.json();
config = Object.assign({}, config, externalConfig);
} catch (error) {
console.error("Unable to parse json in config file");
console.error(error);
}
}
// Allow override of any config value with URL query parameters
const params = new URLSearchParams(window.location.search);
params.forEach((value, key) => {
(config as any)[key] = value;
});
} catch (error) {
console.log("No configuration file found using defaults");
}
return config;
}
export { config };

121
src/ConfigContext.ts Normal file
View File

@@ -0,0 +1,121 @@
export enum Platform {
Portal = "Portal",
Hosted = "Hosted",
Emulator = "Emulator"
}
interface ConfigContext {
platform: Platform;
allowedParentFrameOrigins: string[];
gitSha?: string;
proxyPath?: string;
AAD_ENDPOINT: string;
ARM_AUTH_AREA: string;
ARM_ENDPOINT: string;
EMULATOR_ENDPOINT?: string;
ARM_API_VERSION: string;
GRAPH_ENDPOINT: string;
GRAPH_API_VERSION: string;
ARCADIA_ENDPOINT: string;
ARCADIA_LIVY_ENDPOINT_DNS_ZONE: string;
BACKEND_ENDPOINT?: string;
MONGO_BACKEND_ENDPOINT?: string;
PROXY_PATH?: string;
JUNO_ENDPOINT: string;
GITHUB_CLIENT_ID: string;
GITHUB_CLIENT_SECRET?: string; // No need to inject secret for prod. Juno already knows it.
hostedExplorerURL: string;
armAPIVersion?: string;
}
// Default configuration
let configContext: Readonly<ConfigContext> = {
platform: Platform.Portal,
allowedParentFrameOrigins: [
`^https:\\/\\/cosmos\\.azure\\.(com|cn|us)$`,
`^https:\\/\\/[\\.\\w]*portal\\.azure\\.(com|cn|us)$`,
`^https:\\/\\/[\\.\\w]*portal\\.microsoftazure.de$`,
`^https:\\/\\/[\\.\\w]*ext\\.azure\\.(com|cn|us)$`,
`^https:\\/\\/[\\.\\w]*\\.ext\\.microsoftazure\\.de$`,
`^https://cosmos-db-dataexplorer-germanycentral.azurewebsites.de$`
],
// Webpack injects this at build time
gitSha: process.env.GIT_SHA,
hostedExplorerURL: "https://cosmos.azure.com/",
AAD_ENDPOINT: "https://login.microsoftonline.com/",
ARM_AUTH_AREA: "https://management.azure.com/",
ARM_ENDPOINT: "https://management.azure.com/",
ARM_API_VERSION: "2016-06-01",
GRAPH_ENDPOINT: "https://graph.windows.net",
GRAPH_API_VERSION: "1.6",
ARCADIA_ENDPOINT: "https://workspaceartifacts.projectarcadia.net",
ARCADIA_LIVY_ENDPOINT_DNS_ZONE: "dev.azuresynapse.net",
GITHUB_CLIENT_ID: "6cb2f63cf6f7b5cbdeca", // Registered OAuth app: https://github.com/settings/applications/1189306
JUNO_ENDPOINT: "https://tools.cosmos.azure.com",
BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com"
};
export function resetConfigContext(): void {
if (process.env.NODE_ENV !== "test") {
throw new Error("resetConfigContext can only becalled in a test environment");
}
configContext = {} as ConfigContext;
}
export function updateConfigContext(newContext: Partial<ConfigContext>): void {
Object.assign(configContext, newContext);
}
// Injected for local develpment. These will be removed in the production bundle by webpack
if (process.env.NODE_ENV === "development") {
const port: string = process.env.PORT || "1234";
updateConfigContext({
BACKEND_ENDPOINT: "https://localhost:" + port,
MONGO_BACKEND_ENDPOINT: "https://localhost:" + port,
PROXY_PATH: "/proxy",
EMULATOR_ENDPOINT: "https://localhost:8081"
});
}
export async function initializeConfiguration(): Promise<ConfigContext> {
try {
const response = await fetch("./config.json");
if (response.status === 200) {
try {
const { allowedParentFrameOrigins, ...externalConfig } = await response.json();
Object.assign(configContext, externalConfig);
if (allowedParentFrameOrigins && allowedParentFrameOrigins.length > 0) {
updateConfigContext({
allowedParentFrameOrigins: [...configContext.allowedParentFrameOrigins, ...allowedParentFrameOrigins]
});
}
} catch (error) {
console.error("Unable to parse json in config file");
console.error(error);
}
}
// Allow override of platform value with URL query parameter
const params = new URLSearchParams(window.location.search);
if (params.has("armAPIVersion")) {
const armAPIVersion = params.get("armAPIVersion") || "";
updateConfigContext({ armAPIVersion });
}
if (params.has("platform")) {
const platform = params.get("platform");
switch (platform) {
default:
console.log("Invalid platform query parameter given, ignoring");
break;
case Platform.Portal:
case Platform.Hosted:
case Platform.Emulator:
updateConfigContext({ platform });
}
}
} catch (error) {
console.log("No configuration file found using defaults");
}
return configContext;
}
export { configContext };

View File

@@ -88,10 +88,6 @@ export interface Resource {
id: string;
}
export interface ResourceRequest {
id: string;
}
export interface Collection extends Resource {
defaultTtl?: number;
indexingPolicy?: IndexingPolicy;
@@ -104,39 +100,12 @@ export interface Collection extends Resource {
geospatialConfig?: GeospatialConfig;
}
export interface CreateCollectionWithRpResponse extends Resource {
properties: Collection;
name: string;
type: string;
}
export interface CollectionRequest extends ResourceRequest {
defaultTtl?: number;
indexingPolicy?: IndexingPolicy;
partitionKey?: PartitionKey;
uniqueKeyPolicy?: UniqueKeyPolicy;
conflictResolutionPolicy?: ConflictResolutionPolicy;
}
export interface Database extends Resource {
collections?: Collection[];
}
export interface DocumentId extends Resource {}
export interface Script extends Resource {
body: string;
}
export interface StoredProcedure extends Script {}
export interface UserDefinedFunction extends Script {}
export interface Trigger extends Script {
triggerType: string;
triggerOperation: string;
}
export interface ConflictId extends Resource {
resourceId?: string;
resourceType?: string;
@@ -153,7 +122,14 @@ export interface KeyResource {
Token: string;
}
export interface IndexingPolicy {}
export interface IndexingPolicy {
automatic: boolean;
indexingMode: string;
includedPaths: any;
excludedPaths: any;
compositeIndexes?: any;
spatialIndexes?: any;
}
export interface PartitionKey {
paths: string[];
@@ -203,7 +179,6 @@ export interface Offer extends Resource {
offerType?: string;
content?: {
offerThroughput: number;
offerIsRUPerMinuteThroughputEnabled: boolean;
collectionThroughputInfo?: OfferThroughputInfo;
offerAutopilotSettings?: AutoPilotOfferSettings;
};
@@ -252,59 +227,22 @@ export interface ErrorDataModel {
code?: string;
}
/**
* Defines a property bag for telemetry e.g. see ITelemetryError.
*/
export interface ITelemetryProperties {
[propertyName: string]: string;
}
/**
* Defines a property bag for telemetry e.g. see ITelemetryError.
*/
export interface ITelemetryEvent {
name: string;
properties?: ITelemetryProperties;
}
/**
* Defines an error to be logged as telemetry data.
*/
export interface ITelemetryError extends ITelemetryEvent {
error: any;
}
export interface CreateDatabaseAndCollectionRequest {
databaseId: string;
collectionId: string;
offerThroughput: number;
databaseLevelThroughput: boolean;
rupmEnabled?: boolean;
partitionKey?: PartitionKey;
indexingPolicy?: IndexingPolicy;
uniqueKeyPolicy?: UniqueKeyPolicy;
autoPilot?: AutoPilotCreationSettings;
analyticalStorageTtl?: number;
hasAutoPilotV2FeatureFlag?: boolean;
}
export interface AutoPilotCreationSettings {
autopilotTier?: AutopilotTier;
maxThroughput?: number;
}
export enum AutopilotTier {
Tier1 = 1,
Tier2 = 2,
Tier3 = 3,
Tier4 = 4
}
export interface RpOptions {
// tier is sent as string, autoscale as object (AutoPilotCreationSettings)
[key: string]: string | AutoPilotCreationSettings;
}
export interface Query {
id: string;
resourceId: string;
@@ -312,37 +250,54 @@ export interface Query {
query: string;
}
export interface UpdateOfferThroughputRequest {
subscriptionId: string;
resourceGroup: string;
databaseAccountName: string;
databaseName: string;
collectionName: string;
throughput: number;
offerIsRUPerMinuteThroughputEnabled: boolean;
offerAutopilotSettings?: AutoPilotOfferSettings;
}
export interface AutoPilotOfferSettings {
tier?: AutopilotTier;
maximumTierThroughput?: number;
targetTier?: AutopilotTier;
maxThroughput?: number;
targetMaxThroughput?: number;
}
export interface CreateDatabaseRequest {
export interface CreateDatabaseParams {
autoPilotMaxThroughput?: number;
databaseId: string;
databaseLevelThroughput?: boolean;
offerThroughput?: number;
autoPilot?: AutoPilotCreationSettings;
hasAutoPilotV2FeatureFlag?: boolean;
}
export interface SharedThroughputRange {
minimumRU: number;
maximumRU: number;
defaultRU: number;
export interface CreateCollectionParams {
createNewDatabase: boolean;
collectionId: string;
databaseId: string;
databaseLevelThroughput: boolean;
offerThroughput: number;
analyticalStorageTtl?: number;
autoPilotMaxThroughput?: number;
indexingPolicy?: IndexingPolicy;
partitionKey?: PartitionKey;
uniqueKeyPolicy?: UniqueKeyPolicy;
createMongoWildcardIndex?: boolean;
}
export interface ReadDatabaseOfferParams {
databaseId: string;
databaseResourceId?: string;
offerId?: string;
}
export interface ReadCollectionOfferParams {
collectionId: string;
databaseId: string;
collectionResourceId?: string;
offerId?: string;
}
export interface UpdateOfferParams {
currentOffer: Offer;
databaseId: string;
autopilotThroughput: number;
manualThroughput: number;
collectionId?: string;
migrateToAutoPilot?: boolean;
migrateToManual?: boolean;
}
export interface Notification {
@@ -487,25 +442,6 @@ export interface NotebookConfigurationEndpointInfo {
token: string;
}
export interface SparkCluster {
id: string;
name: string;
type: string;
properties: {
kind: string;
driverSize: string;
workerSize: string;
workerInstanceCount: number;
creationTime: string;
status: string;
libraries?: SparkClusterLibrary[];
};
}
export interface SparkClusterFeedResponse {
value: SparkCluster[];
}
export interface SparkClusterConnectionInfo {
userName: string;
password: string;
@@ -542,84 +478,14 @@ export interface MongoParameters extends RpParameters {
rid?: string;
rtype?: string;
isAutoPilot?: Boolean;
autoPilotTier?: string;
autoPilotThroughput?: string;
analyticalStorageTtl?: number;
}
export interface GraphParameters extends RpParameters {
pk: string;
coll: string;
cd: Boolean;
indexingPolicy?: IndexingPolicy;
}
export interface CreationRequest {
properties: {
resource: {
id: string;
};
options: RpOptions;
};
}
export interface SqlCollectionParameters extends RpParameters {
uniqueKeyPolicy?: UniqueKeyPolicy;
pk: string;
coll: string;
cd: Boolean;
analyticalStorageTtl?: number;
indexingPolicy?: IndexingPolicy;
}
export interface MongoCreationRequest extends CreationRequest {
properties: {
resource: {
id: string;
analyticalStorageTtl?: number;
shardKey?: {};
};
options: RpOptions;
};
}
export interface GraphCreationRequest extends CreationRequest {
properties: {
resource: {
id: string;
partitionKey: {};
indexingPolicy?: IndexingPolicy;
};
options: RpOptions;
};
}
export interface CreateDatabaseWithRpResponse {
id: string;
name: string;
type: string;
properties: {
id: string;
};
}
export interface SparkClusterLibrary {
name: string;
}
export interface SqlCollectionCreationRequest extends CreationRequest {
properties: {
resource: {
uniqueKeyPolicy?: UniqueKeyPolicy;
id: string;
partitionKey: {};
analyticalStorageTtl?: number;
indexingPolicy?: IndexingPolicy;
};
options: RpOptions;
};
}
export interface Library extends SparkClusterLibrary {
properties: {
kind: "Jar";

View File

@@ -1,41 +1,22 @@
import * as DataModels from "./DataModels";
import * as monaco from "monaco-editor";
import DocumentClientUtilityBase from "../Common/DocumentClientUtilityBase";
import {
QueryMetrics,
Resource,
StoredProcedureDefinition,
TriggerDefinition,
UserDefinedFunctionDefinition
} from "@azure/cosmos";
import Q from "q";
import { AccessibleVerticalList } from "../Explorer/Tree/AccessibleVerticalList";
import { CassandraTableKey, CassandraTableKeys } from "../Explorer/Tables/TableDataClient";
import { CommandButtonComponentProps } from "../Explorer/Controls/CommandButton/CommandButtonComponent";
import { ConsoleData } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
import { GitHubClient } from "../GitHub/GitHubClient";
import { JunoClient, IGalleryItem } from "../Juno/JunoClient";
import { NotebookContentItem } from "../Explorer/Notebook/NotebookContentItem";
import { QueryMetrics } from "@azure/cosmos";
import { UploadDetails } from "../workers/upload/definitions";
import Explorer from "../Explorer/Explorer";
import UserDefinedFunction from "../Explorer/Tree/UserDefinedFunction";
import { ConsoleData } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
import { CassandraTableKey, CassandraTableKeys } from "../Explorer/Tables/TableDataClient";
import ConflictId from "../Explorer/Tree/ConflictId";
import DocumentId from "../Explorer/Tree/DocumentId";
import StoredProcedure from "../Explorer/Tree/StoredProcedure";
import ConflictsTab from "../Explorer/Tabs/ConflictsTab";
import Trigger from "../Explorer/Tree/Trigger";
export interface ExplorerOptions {
documentClientUtility: DocumentClientUtilityBase;
notificationsClient: NotificationsClient;
isEmulator: boolean;
}
export interface Capability extends DataModels.Capability {}
export interface ConfigurationOverrides extends DataModels.ConfigurationOverrides {}
export interface NavbarButtonConfig extends CommandButtonComponentProps {}
export interface DatabaseAccount extends DataModels.DatabaseAccount {}
export interface KernelConnectionMetadata {
name: string;
configurationEndpoints: DataModels.NotebookConfigurationEndpoints;
notebookConnectionInfo: DataModels.NotebookWorkspaceConnectionInfo;
}
import UserDefinedFunction from "../Explorer/Tree/UserDefinedFunction";
import { UploadDetails } from "../workers/upload/definitions";
import * as DataModels from "./DataModels";
export interface TokenProvider {
getAuthHeader(): Promise<Headers>;
@@ -75,11 +56,6 @@ export interface WaitsForTemplate {
isTemplateReady: ko.Observable<boolean>;
}
export interface AdHocAccessData {
readWriteUrl: string;
readUrl: string;
}
export interface TreeNode {
nodeKind: string;
rid: string;
@@ -105,15 +81,15 @@ export interface Database extends TreeNode {
selectedSubnodeKind: ko.Observable<CollectionTabKind>;
selectDatabase(): void;
expandDatabase(): void;
expandDatabase(): Promise<void>;
collapseDatabase(): void;
loadCollections(): Q.Promise<void>;
findCollectionWithId(collectionRid: string): Collection;
loadCollections(): Promise<void>;
findCollectionWithId(collectionId: string): Collection;
openAddCollection(database: Database, event: MouseEvent): void;
onDeleteDatabaseContextMenuClick(source: Database, event: MouseEvent | KeyboardEvent): void;
readSettings(): void;
onSettingsClick: () => void;
loadOffer(): Promise<void>;
}
export interface CollectionBase extends TreeNode {
@@ -157,8 +133,7 @@ export interface Collection extends CollectionBase {
onMongoDBDocumentsClick(): void;
openTab(): void;
onSettingsClick: () => void;
readSettings(): Q.Promise<void>;
onSettingsClick: () => Promise<void>;
onDeleteCollectionContextMenuClick(source: Collection, event: MouseEvent): void;
onNewGraphClick(): void;
@@ -183,13 +158,14 @@ export interface Collection extends CollectionBase {
collapseUserDefinedFunctions(): void;
collapseTriggers(): void;
loadUserDefinedFunctions(): Q.Promise<any>;
loadStoredProcedures(): Q.Promise<any>;
loadTriggers(): Q.Promise<any>;
loadUserDefinedFunctions(): Promise<any>;
loadStoredProcedures(): Promise<any>;
loadTriggers(): Promise<any>;
loadOffer(): Promise<void>;
createStoredProcedureNode(data: DataModels.StoredProcedure): StoredProcedure;
createUserDefinedFunctionNode(data: DataModels.UserDefinedFunction): UserDefinedFunction;
createTriggerNode(data: DataModels.Trigger): Trigger;
createStoredProcedureNode(data: StoredProcedureDefinition & Resource): StoredProcedure;
createUserDefinedFunctionNode(data: UserDefinedFunctionDefinition & Resource): UserDefinedFunction;
createTriggerNode(data: TriggerDefinition & Resource): Trigger;
findStoredProcedureWithId(sprocRid: string): StoredProcedure;
findTriggerWithId(triggerRid: string): Trigger;
findUserDefinedFunctionWithId(udfRid: string): UserDefinedFunction;
@@ -201,118 +177,15 @@ export interface Collection extends CollectionBase {
getLabel(): string;
}
export interface DocumentId {
container: DocumentsTab;
rid: string;
self: string;
ts: string;
partitionKeyValue: any;
partitionKeyProperty: string;
partitionKey: DataModels.PartitionKey;
stringPartitionKeyValue: string;
id: ko.Observable<string>;
isDirty: ko.Observable<boolean>;
click(): void;
getPartitionKeyValueAsString(): string;
loadDocument(): Q.Promise<any>;
partitionKeyHeader(): Object;
}
export interface ConflictId {
container: ConflictsTab;
rid: string;
self: string;
ts: string;
partitionKeyValue: any;
partitionKeyProperty: string;
partitionKey: DataModels.PartitionKey;
stringPartitionKeyValue: string;
id: ko.Observable<string>;
operationType: string;
resourceId: string;
resourceType: string;
isDirty: ko.Observable<boolean>;
click(): void;
buildDocumentIdFromConflict(partitionKeyValue: any): DocumentId;
getPartitionKeyValueAsString(): string;
loadConflict(): Q.Promise<any>;
}
/**
* Options used to initialize pane
*/
export interface PaneOptions {
id: string;
documentClientUtility: DocumentClientUtilityBase;
visible: ko.Observable<boolean>;
container?: Explorer;
}
export interface ContextualPane {
documentClientUtility: DocumentClientUtilityBase;
formErrors: ko.Observable<string>;
formErrorsDetails: ko.Observable<string>;
id: string;
title: ko.Observable<string>;
visible: ko.Observable<boolean>;
firstFieldHasFocus: ko.Observable<boolean>;
isExecuting: ko.Observable<boolean>;
submit: () => void;
cancel: () => void;
open: () => void;
close: () => void;
resetData: () => void;
showErrorDetails: () => void;
onCloseKeyPress(source: any, event: KeyboardEvent): void;
onPaneKeyDown(source: any, event: KeyboardEvent): boolean;
}
export interface GitHubReposPaneOptions extends PaneOptions {
gitHubClient: GitHubClient;
junoClient: JunoClient;
}
export interface PublishNotebookPaneOptions extends PaneOptions {
junoClient: JunoClient;
}
export interface PublishNotebookPaneOpenOptions {
name: string;
author: string;
content: string;
}
export interface AddCollectionPaneOptions extends PaneOptions {
isPreferredApiTable: ko.Computed<boolean>;
databaseId?: string;
databaseSelfLink?: string;
}
export interface UploadFilePaneOpenOptions {
paneTitle: string;
selectFileInputLabel: string;
errorMessage: string; // Could not upload notebook
inProgressMessage: string; // Uploading notebook
successMessage: string; // Successfully uploaded notebook
onSubmit: (file: File) => Promise<any>;
extensions?: string; // input accept field. E.g: .ipynb
submitButtonLabel?: string;
}
export interface StringInputPaneOpenOptions {
paneTitle: string;
inputLabel: string;
errorMessage: string;
inProgressMessage: string;
successMessage: string;
onSubmit: (input: string) => Promise<any>;
submitButtonLabel: string;
defaultInput?: string;
}
/**
* Graph configuration
*/
@@ -382,19 +255,6 @@ export interface DocumentRequestContainer {
resourceName?: string;
}
export interface NotificationsClient {
fetchNotifications(): Q.Promise<DataModels.Notification[]>;
setExtensionEndpoint(extensionEndpoint: string): void;
}
export interface QueriesClient {
setupQueriesCollection(): Promise<DataModels.Collection>;
saveQuery(query: DataModels.Query): Promise<void>;
getQueries(): Promise<DataModels.Query[]>;
deleteQuery(query: DataModels.Query): Promise<void>;
getResourceId(): string;
}
export interface DocumentClientOption {
endpoint?: string;
masterKey?: string;
@@ -406,11 +266,9 @@ export interface TabOptions {
tabKind: CollectionTabKind;
title: string;
tabPath: string;
documentClientUtility: DocumentClientUtilityBase;
selfLink: string;
isActive: ko.Observable<boolean>;
hashLocation: string;
onUpdateTabsButtons: (buttons: NavbarButtonConfig[]) => void;
onUpdateTabsButtons: (buttons: CommandButtonComponentProps[]) => void;
isTabsContentExpanded?: ko.Observable<boolean>;
onLoadStartKey?: number;
@@ -423,47 +281,6 @@ export interface TabOptions {
theme?: string;
}
export interface SparkMasterTabOptions extends TabOptions {
clusterConnectionInfo: DataModels.SparkClusterConnectionInfo;
container: Explorer;
}
export interface GraphTabOptions extends TabOptions {
account: DatabaseAccount;
masterKey: string;
collectionId: string;
databaseId: string;
collectionPartitionKeyProperty: string;
}
export interface NotebookTabOptions extends TabOptions {
account: DatabaseAccount;
masterKey: string;
container: Explorer;
notebookContentItem: NotebookContentItem;
}
export interface TerminalTabOptions extends TabOptions {
account: DatabaseAccount;
container: Explorer;
kind: TerminalKind;
}
export interface GalleryTabOptions extends TabOptions {
account: DatabaseAccount;
container: Explorer;
junoClient: JunoClient;
notebookUrl?: string;
galleryItem?: IGalleryItem;
isFavorite?: boolean;
}
export interface NotebookViewerTabOptions extends TabOptions {
account: DatabaseAccount;
container: Explorer;
notebookUrl: string;
}
export interface DocumentsTabOptions extends TabOptions {
partitionKey: DataModels.PartitionKey;
documentIds: ko.ObservableArray<DocumentId>;
@@ -472,6 +289,10 @@ export interface DocumentsTabOptions extends TabOptions {
resourceTokenPartitionKey?: string;
}
export interface SettingsTabV2Options extends TabOptions {
getPendingNotification: Q.Promise<DataModels.Notification>;
}
export interface ConflictsTabOptions extends TabOptions {
partitionKey: DataModels.PartitionKey;
conflictIds: ko.ObservableArray<ConflictId>;
@@ -487,161 +308,14 @@ export interface QueryTabOptions extends TabOptions {
export interface ScriptTabOption extends TabOptions {
resource: any;
isNew: boolean;
collectionSelfLink?: string;
partitionKey?: DataModels.PartitionKey;
}
// Tabs
export interface Tab {
documentClientUtility: DocumentClientUtilityBase;
node: TreeNode; // Can be null
collection: CollectionBase;
rid: string;
tabKind: CollectionTabKind;
tabId: string;
isActive: ko.Observable<boolean>;
isMouseOver: ko.Observable<boolean>;
tabPath: ko.Observable<string>;
tabTitle: ko.Observable<string>;
hashLocation: ko.Observable<string>;
closeTabButton: Button;
onCloseTabButtonClick(): void;
onTabClick(): Q.Promise<any>;
onKeyPressActivate(source: any, event: KeyboardEvent): void;
onKeyPressClose(source: any, event: KeyboardEvent): void;
onActivate(): Q.Promise<any>;
refresh(): void;
closeButtonTabIndex: ko.Computed<number>;
isExecutionError: ko.Observable<boolean>;
isExecuting: ko.Observable<boolean>;
}
export interface DocumentsTab extends Tab {
/* Documents Grid */
selectDocument(documentId: DocumentId): Q.Promise<any>;
selectedDocumentId: ko.Observable<DocumentId>;
selectedDocumentContent: Editable<any>;
onDocumentIdClick(documentId: DocumentId): Q.Promise<any>;
dataContentsGridScrollHeight: ko.Observable<string>;
accessibleDocumentList: AccessibleVerticalList;
documentContentsGridId: string;
partitionKey: DataModels.PartitionKey;
idHeader: string;
partitionKeyPropertyHeader: string;
partitionKeyProperty: string;
documentIds: ko.ObservableArray<DocumentId>;
/* Documents Filter */
filterContent: ko.Observable<string>;
appliedFilter: ko.Observable<string>;
lastFilterContents: ko.ObservableArray<string>;
isFilterExpanded: ko.Observable<boolean>;
applyFilterButton: Button;
onShowFilterClick(): Q.Promise<any>;
onHideFilterClick(): Q.Promise<any>;
onApplyFilterClick(): Q.Promise<any>;
/* Document Editor */
isEditorDirty: ko.Computed<boolean>;
editorState: ko.Observable<DocumentExplorerState>;
onValidDocumentEdit(content: any): Q.Promise<any>;
onInvalidDocumentEdit(content: any): Q.Promise<any>;
onNewDocumentClick(): Q.Promise<any>;
onSaveNewDocumentClick(): Q.Promise<any>;
onRevertNewDocumentClick(): Q.Promise<any>;
onSaveExisitingDocumentClick(): Q.Promise<any>;
onRevertExisitingDocumentClick(): Q.Promise<any>;
onDeleteExisitingDocumentClick(): Q.Promise<any>;
/* Errors */
displayedError: ko.Observable<string>;
initDocumentEditor(documentId: DocumentId, content: any): Q.Promise<any>;
loadNextPage(): Q.Promise<any>;
}
export interface WaitsForTemplate {
isTemplateReady: ko.Observable<boolean>;
}
export interface QueryTab extends Tab {
queryEditorId: string;
isQueryMetricsEnabled: ko.Computed<boolean>;
activityId: ko.Observable<string>;
/* Command Bar */
executeQueryButton: Button;
fetchNextPageButton: Button;
saveQueryButton: Button;
onExecuteQueryClick(): Q.Promise<any>;
onFetchNextPageClick(): Q.Promise<any>;
/*Query Editor*/
initialEditorContent: ko.Observable<string>;
sqlQueryEditorContent: ko.Observable<string>;
sqlStatementToExecute: ko.Observable<string>;
/* Results */
allResultsMetadata: ko.ObservableArray<QueryResultsMetadata>;
/* Errors */
errors: ko.ObservableArray<QueryError>;
/* Status */
statusMessge: ko.Observable<string>;
statusIcon: ko.Observable<string>;
}
export interface ScriptTab extends Tab {
id: Editable<string>;
editorId: string;
saveButton: Button;
updateButton: Button;
discardButton: Button;
deleteButton: Button;
editorState: ko.Observable<ScriptEditorState>;
editorContent: ko.Observable<string>;
editor: ko.Observable<monaco.editor.IStandaloneCodeEditor>;
errors: ko.ObservableArray<QueryError>;
statusMessge: ko.Observable<string>;
statusIcon: ko.Observable<string>;
formFields: ko.ObservableArray<Editable<any>>;
formIsValid: ko.Computed<boolean>;
formIsDirty: ko.Computed<boolean>;
isNew: ko.Observable<boolean>;
resource: ko.Observable<DataModels.Resource>;
setBaselines(): void;
}
export interface StoredProcedureTab extends ScriptTab {
onExecuteSprocsResult(result: any, logsData: any): void;
onExecuteSprocsError(error: string): void;
}
export interface UserDefinedFunctionTab extends ScriptTab {}
export interface TriggerTab extends ScriptTab {
triggerType: Editable<string>;
triggerOperation: Editable<string>;
}
export interface GraphTab extends Tab {}
export interface EditorPosition {
line: number;
column: number;
}
export interface MongoShellTab extends Tab {}
export enum DocumentExplorerState {
noDocumentSelected,
newDocumentValid,
@@ -683,7 +357,8 @@ export enum CollectionTabKind {
NotebookV2 = 15,
SparkMasterTab = 16,
Gallery = 17,
NotebookViewer = 18
NotebookViewer = 18,
SettingsV2 = 19
}
export enum TerminalKind {
@@ -715,6 +390,7 @@ export interface DataExplorerInputsFrame {
dataExplorerVersion?: string;
isAuthWithresourceToken?: boolean;
defaultCollectionThroughput?: CollectionCreationDefaults;
flights?: readonly string[];
}
export interface CollectionCreationDefaults {
@@ -759,40 +435,8 @@ export interface AuthorizationTokenHeaderMetadata {
token: string;
}
export interface TelemetryActions {
sendEvent(name: string, telemetryProperties?: { [propertyName: string]: string }): Q.Promise<any>;
sendError(errorInfo: DataModels.ITelemetryError): Q.Promise<any>;
sendMetric(
name: string,
metricNumber: number,
telemetryProperties?: { [propertyName: string]: string }
): Q.Promise<any>;
}
export interface ConfigurationOverrides {
EnableBsonSchema: string;
}
export interface CosmosDbApi {
isSystemDatabasePredicate: (database: Database) => boolean;
}
export interface DropdownOption<T> {
text: string;
value: T;
disable?: boolean;
}
export interface INotebookContainerClient {
resetWorkspace: () => Promise<void>;
}
export interface INotebookContentClient {
updateItemChildren: (item: NotebookContentItem) => Promise<void>;
createNewNotebookFile: (parent: NotebookContentItem) => Promise<NotebookContentItem>;
deleteContentItem: (item: NotebookContentItem) => Promise<void>;
uploadFileAsync: (name: string, content: string, parent: NotebookContentItem) => Promise<NotebookContentItem>;
renameNotebook: (item: NotebookContentItem, targetName: string) => Promise<NotebookContentItem>;
createDirectory: (parent: NotebookContentItem, newDirectoryName: string) => Promise<NotebookContentItem>;
readFileContent: (filePath: string) => Promise<string>;
}

View File

@@ -12,7 +12,7 @@ import {
PortalTheme
} from "./HeatmapDatatypes";
import { isInvalidParentFrameOrigin } from "../../Utils/MessageValidation";
import { MessageHandler } from "../../Common/MessageHandler";
import { sendCachedDataMessage, sendMessage } from "../../Common/MessageHandler";
import { MessageTypes } from "../../Contracts/ExplorerContracts";
import { StyleConstants } from "../../Common/Constants";
import "./Heatmap.less";
@@ -209,7 +209,7 @@ export class Heatmap {
for (let i = 0; i < this._chartData.dataPoints.length; i++) {
output.push(this._chartData.dataPoints[i][xAxisIndex]);
}
MessageHandler.sendCachedDataMessage(MessageTypes.LogInfo, output);
sendCachedDataMessage(MessageTypes.LogInfo, output);
});
}
}
@@ -266,4 +266,4 @@ export function handleMessage(event: MessageEvent) {
}
window.addEventListener("message", handleMessage, false);
MessageHandler.sendMessage("ready");
sendMessage("ready");

View File

@@ -0,0 +1,8 @@
export enum DefaultAccountExperienceType {
DocumentDB = "DocumentDB",
Graph = "Graph",
MongoDB = "MongoDB",
Table = "Table",
Cassandra = "Cassandra",
ApiForMongoDB = "Azure Cosmos DB for MongoDB API"
}

View File

@@ -1,42 +0,0 @@
// Type definitions for jQuery contextMenu 1.7.0
// Project: http://medialize.github.com/jQuery-contextMenu/
// Definitions by: Natan Vivo <https://github.com/nvivo/>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
/// <reference path="jquery.d.ts" />
interface JQueryContextMenuOptions {
selector: string;
appendTo?: string;
trigger?: string;
autoHide?: boolean;
delay?: number;
determinePosition?: (menu: JQuery) => void;
position?: (opt: JQuery, x: number, y: number) => void;
positionSubmenu?: (menu: JQuery) => void;
zIndex?: number;
animation?: {
duration?: number;
show?: string;
hide?: string;
};
events?: {
show?: () => void;
hide?: () => void;
};
callback?: (key: any, options: any) => any;
items?: any;
build?: (triggerElement: JQuery, e: Event) => any;
reposition?: boolean;
className?: string;
itemClickEvent?: string;
}
interface JQueryStatic {
contextMenu(options?: JQueryContextMenuOptions): JQuery;
contextMenu(type: string, selector?: any): JQuery;
}
interface JQuery {
contextMenu(options?: any): JQuery;
}

View File

@@ -48,6 +48,10 @@ describe("Component Registerer", () => {
expect(ko.components.isRegistered("settings-tab")).toBe(true);
});
it("should register settings-tab-v2 component", () => {
expect(ko.components.isRegistered("settings-tab-v2")).toBe(true);
});
it("should register query-tab component", () => {
expect(ko.components.isRegistered("query-tab")).toBe(true);
});
@@ -119,8 +123,4 @@ describe("Component Registerer", () => {
it("should register dynamic-list component", () => {
expect(ko.components.isRegistered("dynamic-list")).toBe(true);
});
it("should register throughput-input component", () => {
expect(ko.components.isRegistered("throughput-input")).toBe(true);
});
});

View File

@@ -11,11 +11,8 @@ import { InputTypeaheadComponent } from "./Controls/InputTypeahead/InputTypeahea
import { JsonEditorComponent } from "./Controls/JsonEditor/JsonEditorComponent";
import { NewVertexComponent } from "./Graph/NewVertexComponent/NewVertexComponent";
import { TabsManagerKOComponent } from "./Tabs/TabsManager";
import { ThroughputInputComponent } from "./Controls/ThroughputInput/ThroughputInputComponent";
import { ThroughputInputComponentAutoPilotV3 } from "./Controls/ThroughputInput/ThroughputInputComponentAutoPilotV3";
import { ToolbarComponent } from "./Controls/Toolbar/Toolbar";
ko.components.register("toolbar", new ToolbarComponent());
ko.components.register("input-typeahead", new InputTypeaheadComponent());
ko.components.register("new-vertex-form", NewVertexComponent);
ko.components.register("error-display", new ErrorDisplayComponent());
@@ -25,7 +22,6 @@ ko.components.register("editor", new EditorComponent());
ko.components.register("json-editor", new JsonEditorComponent());
ko.components.register("diff-editor", new DiffEditorComponent());
ko.components.register("dynamic-list", DynamicListComponent);
ko.components.register("throughput-input", ThroughputInputComponent);
ko.components.register("throughput-input-autopilot-v3", ThroughputInputComponentAutoPilotV3);
ko.components.register("tabs-manager", TabsManagerKOComponent());
@@ -36,6 +32,7 @@ ko.components.register("stored-procedure-tab", new TabComponents.StoredProcedure
ko.components.register("trigger-tab", new TabComponents.TriggerTab());
ko.components.register("user-defined-function-tab", new TabComponents.UserDefinedFunctionTab());
ko.components.register("settings-tab", new TabComponents.SettingsTab());
ko.components.register("settings-tab-v2", new TabComponents.SettingsTabV2());
ko.components.register("query-tab", new TabComponents.QueryTab());
ko.components.register("tables-query-tab", new TabComponents.QueryTablesTab());
ko.components.register("graph-tab", new TabComponents.GraphTab());

View File

@@ -16,6 +16,8 @@ import Explorer from "./Explorer";
import UserDefinedFunction from "./Tree/UserDefinedFunction";
import StoredProcedure from "./Tree/StoredProcedure";
import Trigger from "./Tree/Trigger";
import { userContext } from "../UserContext";
import { DefaultAccountExperienceType } from "../DefaultAccountExperienceType";
export interface CollectionContextMenuButtonParams {
databaseId: string;
@@ -29,22 +31,24 @@ export interface DatabaseContextMenuButtonParams {
* New resource tree (in ReactJS)
*/
export class ResourceTreeContextMenuButtonFactory {
public static createDatabaseContextMenu(
container: Explorer,
selectedDatabase: ViewModels.Database
): TreeNodeMenuItem[] {
const newCollectionMenuItem: TreeNodeMenuItem = {
public static createDatabaseContextMenu(container: Explorer): TreeNodeMenuItem[] {
const items: TreeNodeMenuItem[] = [
{
iconSrc: AddCollectionIcon,
onClick: () => container.onNewCollectionClicked(),
label: container.addCollectionText()
};
}
];
const deleteDatabaseMenuItem = {
if (userContext.defaultExperience !== DefaultAccountExperienceType.Table) {
items.push({
iconSrc: DeleteDatabaseIcon,
onClick: () => container.deleteDatabaseConfirmationPane.open(),
label: container.deleteDatabaseText()
};
return [newCollectionMenuItem, deleteDatabaseMenuItem];
label: container.deleteDatabaseText(),
styleClass: "deleteDatabaseMenuItem"
});
}
return items;
}
public static createCollectionContextMenuButton(
@@ -112,7 +116,8 @@ export class ResourceTreeContextMenuButtonFactory {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
selectedCollection && selectedCollection.onDeleteCollectionContextMenuClick(selectedCollection, null);
},
label: container.deleteCollectionText()
label: container.deleteCollectionText(),
styleClass: "deleteCollectionMenuItem"
});
return items;

View File

@@ -0,0 +1,14 @@
import { shallow } from "enzyme";
import React from "react";
import { CollapsibleSectionComponent, CollapsibleSectionProps } from "./CollapsibleSectionComponent";
describe("CollapsibleSectionComponent", () => {
it("renders", () => {
const props: CollapsibleSectionProps = {
title: "Sample title"
};
const wrapper = shallow(<CollapsibleSectionComponent {...props} />);
expect(wrapper).toMatchSnapshot();
});
});

View File

@@ -0,0 +1,36 @@
import { Icon, Label, Stack } from "office-ui-fabric-react";
import * as React from "react";
import { accordionIconStyles, accordionStackTokens } from "../Settings/SettingsRenderUtils";
export interface CollapsibleSectionProps {
title: string;
}
export interface CollapsibleSectionState {
isExpanded: boolean;
}
export class CollapsibleSectionComponent extends React.Component<CollapsibleSectionProps, CollapsibleSectionState> {
constructor(props: CollapsibleSectionProps) {
super(props);
this.state = {
isExpanded: true
};
}
private toggleCollapsed = (): void => {
this.setState({ isExpanded: !this.state.isExpanded });
};
public render(): JSX.Element {
return (
<>
<Stack className="collapsibleSection" horizontal tokens={accordionStackTokens} onClick={this.toggleCollapsed}>
<Icon iconName={this.state.isExpanded ? "ChevronDown" : "ChevronRight"} styles={accordionIconStyles} />
<Label>{this.props.title}</Label>
</Stack>
{this.state.isExpanded && this.props.children}
</>
);
}
}

View File

@@ -0,0 +1,30 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`CollapsibleSectionComponent renders 1`] = `
<Fragment>
<Stack
className="collapsibleSection"
horizontal={true}
onClick={[Function]}
tokens={
Object {
"childrenGap": 10,
}
}
>
<StyledIconBase
iconName="ChevronDown"
styles={
Object {
"root": Object {
"paddingTop": 7,
},
}
}
/>
<StyledLabelBase>
Sample title
</StyledLabelBase>
</Stack>
</Fragment>
`;

View File

@@ -1,6 +1,6 @@
import { StringUtils } from "../../../Utils/StringUtils";
import { KeyCodes } from "../../../Common/Constants";
import TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
import CollapseChevronDownIcon from "../../../../images/QueryBuilder/CollapseChevronDown_16x.png";

View File

@@ -3,7 +3,7 @@ import { Dialog, DialogType, DialogFooter, IDialogProps } from "office-ui-fabric
import { IButtonProps, PrimaryButton, DefaultButton } from "office-ui-fabric-react/lib/Button";
import { ITextFieldProps, TextField } from "office-ui-fabric-react/lib/TextField";
import { Link } from "office-ui-fabric-react/lib/Link";
import { FontIcon } from "office-ui-fabric-react";
import { ChoiceGroup, FontIcon, IChoiceGroupProps } from "office-ui-fabric-react";
export interface TextFieldProps extends ITextFieldProps {
label: string;
@@ -24,6 +24,7 @@ export interface DialogProps {
subText: string;
isModal: boolean;
visible: boolean;
choiceGroupProps?: IChoiceGroupProps;
textFieldProps?: TextFieldProps;
linkProps?: LinkProps;
primaryButtonText: string;
@@ -65,6 +66,7 @@ export class DialogComponent extends React.Component<DialogProps, {}> {
minWidth: DIALOG_MIN_WIDTH,
maxWidth: DIALOG_MAX_WIDTH
};
const choiceGroupProps: IChoiceGroupProps = this.props.choiceGroupProps;
const textFieldProps: ITextFieldProps = this.props.textFieldProps;
const linkProps: LinkProps = this.props.linkProps;
const primaryButtonProps: IButtonProps = {
@@ -82,6 +84,7 @@ export class DialogComponent extends React.Component<DialogProps, {}> {
return (
<Dialog {...dialogProps}>
{choiceGroupProps && <ChoiceGroup {...choiceGroupProps} />}
{textFieldProps && <TextField {...textFieldProps} />}
{linkProps && (
<Link href={linkProps.linkUrl} target="_blank">

View File

@@ -265,6 +265,9 @@ exports[`test render renders with filters 1`] = `
"buttonTextDisabled": "#a19f9d",
"buttonTextHovered": "#201f1e",
"buttonTextPressed": "#201f1e",
"cardShadow": "0 1.6px 3.6px 0 rgba(0, 0, 0, 0.132), 0 0.3px 0.9px 0 rgba(0, 0, 0, 0.108)",
"cardShadowHovered": "0 3.2px 7.2px 0 rgba(0, 0, 0, 0.132), 0 0.6px 1.8px 0 rgba(0, 0, 0, 0.108)",
"cardStandoutBackground": "#ffffff",
"defaultStateBackground": "#faf9f8",
"disabledBackground": "#f3f2f1",
"disabledBodySubtext": "#c8c6c4",
@@ -604,6 +607,9 @@ exports[`test render renders with filters 1`] = `
"buttonTextDisabled": "#a19f9d",
"buttonTextHovered": "#201f1e",
"buttonTextPressed": "#201f1e",
"cardShadow": "0 1.6px 3.6px 0 rgba(0, 0, 0, 0.132), 0 0.3px 0.9px 0 rgba(0, 0, 0, 0.108)",
"cardShadowHovered": "0 3.2px 7.2px 0 rgba(0, 0, 0, 0.132), 0 0.6px 1.8px 0 rgba(0, 0, 0, 0.108)",
"cardStandoutBackground": "#ffffff",
"defaultStateBackground": "#faf9f8",
"disabledBackground": "#f3f2f1",
"disabledBodySubtext": "#c8c6c4",
@@ -997,6 +1003,9 @@ exports[`test render renders with filters 1`] = `
"buttonTextDisabled": "#a19f9d",
"buttonTextHovered": "#201f1e",
"buttonTextPressed": "#201f1e",
"cardShadow": "0 1.6px 3.6px 0 rgba(0, 0, 0, 0.132), 0 0.3px 0.9px 0 rgba(0, 0, 0, 0.108)",
"cardShadowHovered": "0 3.2px 7.2px 0 rgba(0, 0, 0, 0.132), 0 0.6px 1.8px 0 rgba(0, 0, 0, 0.108)",
"cardStandoutBackground": "#ffffff",
"defaultStateBackground": "#faf9f8",
"disabledBackground": "#f3f2f1",
"disabledBodySubtext": "#c8c6c4",
@@ -1113,6 +1122,11 @@ exports[`test render renders with filters 1`] = `
},
"iconDisabled": Object {
"color": "#a19f9d",
"selectors": Object {
"@media screen and (-ms-high-contrast: active)": Object {
"color": "GrayText",
},
},
},
"label": Array [
Object {
@@ -1134,6 +1148,11 @@ exports[`test render renders with filters 1`] = `
},
"menuIconDisabled": Object {
"color": "#a19f9d",
"selectors": Object {
"@media screen and (-ms-high-contrast: active)": Object {
"color": "GrayText",
},
},
},
"root": Array [
Object {
@@ -1150,7 +1169,6 @@ exports[`test render renders with filters 1`] = `
"right": 2,
"selectors": Object {
"@media screen and (-ms-high-contrast: active)": Object {
"border": "none",
"bottom": -2,
"left": -2,
"outlineColor": "ButtonText",
@@ -1230,7 +1248,6 @@ exports[`test render renders with filters 1`] = `
"right": 2,
"selectors": Object {
"@media screen and (-ms-high-contrast: active)": Object {
"border": "none",
"bottom": -2,
"left": -2,
"outlineColor": "ButtonText",
@@ -1259,10 +1276,6 @@ exports[`test render renders with filters 1`] = `
":hover": Object {
"outline": 0,
},
"@media screen and (-ms-high-contrast: active)": Object {
"borderColor": "grayText",
"color": "grayText",
},
},
},
Object {
@@ -1362,13 +1375,21 @@ exports[`test render renders with filters 1`] = `
"selectors": Object {
"@media screen and (-ms-high-contrast: active)": Object {
"MsHighContrastAdjust": "none",
"backgroundColor": "WindowText",
"color": "Window",
"backgroundColor": "Window",
"border": "1px solid WindowText",
"borderRightWidth": "0",
"color": "WindowText",
},
},
},
".ms-Button--primary + .ms-Button": Object {
"border": "none",
"selectors": Object {
"@media screen and (-ms-high-contrast: active)": Object {
"border": "1px solid WindowText",
"borderLeftWidth": "0",
},
},
},
},
},
@@ -1408,6 +1429,9 @@ exports[`test render renders with filters 1`] = `
"borderColor": "GrayText",
"color": "GrayText",
},
"@media screen and (forced-colors: active)": Object {
"forcedColorAdjust": "none",
},
},
},
"splitButtonContainerFocused": Object {
@@ -1554,6 +1578,13 @@ exports[`test render renders with filters 1`] = `
},
},
},
".ms-Button-menuIcon": Object {
"selectors": Object {
"@media screen and (-ms-high-contrast: active)": Object {
"color": "GrayText",
},
},
},
":hover": Object {
"cursor": "default",
},
@@ -1775,6 +1806,9 @@ exports[`test render renders with filters 1`] = `
"buttonTextDisabled": "#a19f9d",
"buttonTextHovered": "#201f1e",
"buttonTextPressed": "#201f1e",
"cardShadow": "0 1.6px 3.6px 0 rgba(0, 0, 0, 0.132), 0 0.3px 0.9px 0 rgba(0, 0, 0, 0.108)",
"cardShadowHovered": "0 3.2px 7.2px 0 rgba(0, 0, 0, 0.132), 0 0.6px 1.8px 0 rgba(0, 0, 0, 0.108)",
"cardStandoutBackground": "#ffffff",
"defaultStateBackground": "#faf9f8",
"disabledBackground": "#f3f2f1",
"disabledBodySubtext": "#c8c6c4",

View File

@@ -86,6 +86,7 @@ export class DynamicListViewModel extends WaitsForTemplateViewModel {
public onRemoveItemKeyPress = (data: any, event: KeyboardEvent, source: any): boolean => {
if (event.keyCode === KeyCodes.Enter || event.keyCode === KeyCodes.Space) {
this.removeItem(data, event);
(document.querySelector(".dynamicListItem:last-of-type input") as HTMLElement).focus();
event.stopPropagation();
return false;
}
@@ -94,7 +95,7 @@ export class DynamicListViewModel extends WaitsForTemplateViewModel {
public addItem(): void {
this.listItems.push({ value: ko.observable("") });
document.getElementById("uniqueKeyItems").focus();
(document.querySelector(".dynamicListItem:last-of-type input") as HTMLElement).focus();
}
public onAddItemKeyPress = (source: any, event: KeyboardEvent): boolean => {

View File

@@ -44,11 +44,16 @@ export const FeaturePanelComponent: React.FunctionComponent = () => {
onChange?: (_?: React.FormEvent<HTMLElement | HTMLInputElement>, checked?: boolean) => void;
}[] = [
{ key: "feature.enablechangefeedpolicy", label: "Enable change feed policy", value: "true" },
{ key: "feature.enablerupm", label: "Enable RUPM", value: "true" },
{ key: "feature.dataexplorerexecutesproc", label: "Execute stored procedure", value: "true" },
{ key: "feature.hosteddataexplorerenabled", label: "Hosted Data Explorer (deprecated?)", value: "true" },
{ key: "feature.enablettl", label: "Enable TTL", value: "true" },
{ key: "feature.enablegallerypublish", label: "Enable Notebook Gallery Publishing", value: "true" },
{ key: "feature.enablecodeofconduct", label: "Enable Code Of Conduct Acknowledgement", value: "true" },
{
key: "feature.enableLinkInjection",
label: "Enable Injecting Notebook Viewer Link into the first cell",
value: "true"
},
{ key: "feature.canexceedmaximumvalue", label: "Can exceed max value", value: "true" },
{
key: "feature.enablefixedcollectionwithsharedthroughput",

View File

@@ -131,12 +131,6 @@ exports[`Feature panel renders all flags 1`] = `
label="Enable change feed policy"
onChange={[Function]}
/>
<StyledCheckboxBase
checked={false}
key="feature.enablerupm"
label="Enable RUPM"
onChange={[Function]}
/>
<StyledCheckboxBase
checked={false}
key="feature.dataexplorerexecutesproc"
@@ -163,8 +157,14 @@ exports[`Feature panel renders all flags 1`] = `
/>
<StyledCheckboxBase
checked={false}
key="feature.canexceedmaximumvalue"
label="Can exceed max value"
key="feature.enablecodeofconduct"
label="Enable Code Of Conduct Acknowledgement"
onChange={[Function]}
/>
<StyledCheckboxBase
checked={false}
key="feature.enableLinkInjection"
label="Enable Injecting Notebook Viewer Link into the first cell"
onChange={[Function]}
/>
</Stack>
@@ -172,6 +172,12 @@ exports[`Feature panel renders all flags 1`] = `
className="checkboxRow"
horizontalAlign="space-between"
>
<StyledCheckboxBase
checked={false}
key="feature.canexceedmaximumvalue"
label="Can exceed max value"
onChange={[Function]}
/>
<StyledCheckboxBase
checked={false}
key="feature.enablefixedcollectionwithsharedthroughput"

View File

@@ -6,7 +6,7 @@ import { RepoListItem } from "./GitHubReposComponent";
import { ChildrenMargin } from "./GitHubStyleConstants";
import * as GitHubUtils from "../../../Utils/GitHubUtils";
import { IGitHubRepo } from "../../../GitHub/GitHubClient";
import TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import UrlUtility from "../../../Common/UrlUtility";
import Explorer from "../../Explorer";

View File

@@ -66,7 +66,9 @@ export class GitHubReposComponent extends React.Component<GitHubReposComponentPr
return (
<>
<div className={"firstdivbg headerline"}>{header}</div>
<div className={"firstdivbg headerline"} role="heading" aria-level={2}>
{header}
</div>
<div className={"paneMainContent"}>{content}</div>
{!this.props.showAuthorizeAccess && (
<>

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