Compare commits

..

110 Commits

Author SHA1 Message Date
Jade Welton
827d3888c0 Remove redundant snapshot. 2024-08-15 09:00:14 -07:00
Jade Welton
46055c2cde Remove calls to the backend notifications API and related code. 2024-08-14 16:08:14 -07:00
Asier Isayas
cc89691da3 Activate Mongo Proxy in Prod (#1936)
* activate mongo proxy in mpac

* activate mongo proxy in mpac

* activate mongo proxy in prod

* fixed parition key unit test

* remove three part partition key value test

---------

Co-authored-by: Asier Isayas <aisayas@microsoft.com>
2024-08-14 14:15:11 -04:00
sunghyunkang1111
24860a6842 revert extract partition key (#1935) 2024-08-14 02:54:20 -05:00
Asier Isayas
bf6b362610 Activate Mongo Proxy in MPAC (#1934)
* activate mongo proxy in mpac

* activate mongo proxy in mpac

---------

Co-authored-by: Asier Isayas <aisayas@microsoft.com>
2024-08-13 16:34:34 -04:00
sunghyunkang1111
baca7922b4 move nps survey open dialog call to after explorer initialization (#1932) 2024-08-13 14:16:20 -05:00
Ashley Stanton-Nurse
b59ba20ed0 fix #1929 by using flex instead of grid to lay out the tabs view (#1930) 2024-08-13 11:19:24 -07:00
vchske
7f55de7aa2 Removing check for value when populating partition key values (#1928)
* Removing check for value when populating partition key values

* Removed invalid test case

* Added unit test for empty partition key value
2024-08-13 10:00:34 -07:00
SATYA SB
62c76cc264 [accessibility-2280341]:[Keyboard Navigation - Azure Cosmos DB - Graph]: Keyboard focus order is not logical after selecting any tab control. (#1854)
* [accessibility-2280341]:[Keyboard Navigation - Azure Cosmos DB - Graph]: Keyboard focus order is not logical after selecting any tab control.

* updated module structure.

---------

Co-authored-by: Satyapriya Bai <v-satybai@microsoft.com>
2024-08-12 11:06:35 +05:30
Ashley Stanton-Nurse
99d95a4cec swap splitter directions in query results view (#1896)
* swap splitter directions in query results view

* refmt *sigh*
2024-08-09 10:27:48 -07:00
SATYA SB
647cca09b3 [accessibility-2724013]: [Screen reader - Cosmos DB - Data Explorer -> Entities -> Add entity]: Screen reader announces incorrect role when focus lands on the "Edit" and "Delete" buttons. (#1923)
Co-authored-by: Satyapriya Bai <v-satybai@microsoft.com>
2024-08-09 21:02:10 +05:30
SATYA SB
2c5f4e9666 [accessibility-2950560}:[Programmatic Access-Try Cosmos DB-Welcome! What is Cosmos DB?]: 'Welcome! What is Cosmos DB?' is not defined programmatically as heading. (#1882)
Co-authored-by: Satyapriya Bai <v-satybai@microsoft.com>
2024-08-09 21:00:47 +05:30
Laurent Nguyen
58ae64193f Fix resource tree styling (#1926)
* Fix style for when no global command

* Fix style override expression

---------

Co-authored-by: Laurent Nguyen <languye@microsoft.com>
2024-08-09 17:00:09 +02:00
sindhuba
806a0657df Fix Login for Entra ID text (#1925)
* Fix API endpoint for CassandraProxy query API

* activate Mongo Proxy and Cassandra Proxy in Prod

* Add CP Prod endpoint

* Run npm format and tests

* Revert code

* fix bug that blocked local mongo proxy and cassandra proxy development

* Add prod endpoint

* fix pr check tests

* Remove prod

* Remove prod endpoint

* Remove dev endpoint

* Support data plane RBAC

* Support data plane RBAC

* Add additional changes for Portal RBAC functionality

* Remove unnecessary code

* Remove unnecessary code

* Add code to fix VCoreMongo/PG bug

* Address feedback

* Add more logs for RBAC feature

* Add more logs for RBAC features

* Fix login for Entra ID text

* Address comments

---------

Co-authored-by: Asier Isayas <aisayas@microsoft.com>
2024-08-05 15:41:54 -07:00
Laurent Nguyen
bc479fb808 Add Word Wrap menu option to Editor React monaco context menu in Documents Tab (#1922)
* Remove "Go to Symbol..." menu option by default in monaco. Add option to toggle word wrap.

* Remove code that removes "Go to symbol" as it is not a public API

* Move WordWrap context menu item to its own section. Remove unnecessary parameters.

* Fix format
2024-08-02 17:53:00 +02:00
Ashley Stanton-Nurse
31773ee73b Redesign resource tree (#1865)
* start redesign work

* add left padding to all tree nodes

* fiddling with padding

* align tab bar line with first item in resource tree

* final touch ups

* fix a strange password manager autofill prompt

* add keyboard shortcuts

* revert testing change

* nudge messagebar to layout row height

* tidy up

* switch to Allotment to stop ResizeObserver issues with monaco

* refmt and fix lints

* fabric touch-ups

* update snapshots

* remove explicit react-icons dependency

* reinstall packages

* remove background from FluentProvider

* fix alignment of message bar

* undo temporary workaround

* restore refresh button

* fix e2e tests and reformat

* fix compiler error

* remove uiw/react-split

* uncomment selection change on expand
2024-08-01 10:02:36 -07:00
Asier Isayas
3d1f280378 Allow connection string users to add database to their Mongo serverless account (#1924)
* Allow connection string users to create databases mongo ser

* Allow connection string users to create databases for the mongo serverless accounts

* Allow connection string users to create databases for the mongo serverless accounts

---------

Co-authored-by: Asier Isayas <aisayas@microsoft.com>
2024-08-01 12:29:55 -04:00
SATYA SB
2ef036ee94 [accessibility-3100032]:[Programmatic Access - Azure Cosmos DB - Data Explorer]: Close button does not have discernible text under 'Data Explorer' pane. (#1872)
* [accessibility-3100032]:[Programmatic Access - Azure Cosmos DB - Data Explorer]: Close button does not have discernible text under 'Data Explorer' pane.

* [accessibility-3100032]:[Programmatic Access - Azure Cosmos DB - Data Explorer]: Close button does not have discernible text under 'Data Explorer' pane.

---------

Co-authored-by: Satyapriya Bai <v-satybai@microsoft.com>
2024-07-31 20:42:19 +05:30
Laurent Nguyen
77c758714d Fix initial condition for shift/ctrl selection to work (#1908) 2024-07-31 16:52:23 +02:00
Laurent Nguyen
bcd8b7229f Upgrade typescript to 4.9.5 and jest to 29.7.0 (and related packages) (#1884)
* Upgrade typescript to 4.9.5

* Fix compile issue and put back files in tsconfig.strict.json

* Update test snapshots

* Fix jest tests by upgrading jest and other related packages.

* Attempt to fix playwright test

* Revert "Attempt to fix playwright test"

This reverts commit 8293f34c9c.

* 2nd attempt to fix example test

* fix waitFor in playwright

* Remove unused describe section

* Attempt to fix e2e test

* Revert "Attempt to fix e2e test"

This reverts commit 9745bcd2ef.

* Upgrade playwright to latest

* Revert "Upgrade playwright to latest"

This reverts commit e2ea1d0189.

* Error test on e2e

* Revert "Error test on e2e"

This reverts commit 124e3764f7.

* Try to select dropdown item by xpath selector

* Revert "Try to select dropdown item by xpath selector"

This reverts commit 8eb42a64e2.

* Attempt to wait until page is fully loaded

* Revert "Attempt to wait until page is fully loaded"

This reverts commit bb43fcea6e.

* Use playwright selectOption to select dropdown option

* Revert "Use playwright selectOption to select dropdown option"

This reverts commit daa8cd0930.

* Select dropdown option with playwright api instead of manual click

* c7ab4c7ecf7b05f32a85568bce1a667ad8c62703Revert "Select dropdown option with playwright api instead of manual click"

This reverts commit c7ab4c7ecf.

* Wait for 5s after dropdown click

* Revert "Wait for 5s after dropdown click"

This reverts commit 847e9ad33f.

* Try forcing click

* Revert "Try forcing click"

This reverts commit 29b9fa1bda.

* Force click on the dropdown and set viewport size bigger.

* Force click on the dropdown and set viewport size bigger.

* try force clicking option

* Skip container test on webkit

* Add branded browsers to e2e tests

---------

Co-authored-by: Ashley Stanton-Nurse <ashleyst@microsoft.com>
2024-07-30 15:41:41 -07:00
Nitesh Vijay
0a1d16de1b Fix vector search capability name (#1918) (#1921)
Co-authored-by: Nitesh Vijay <niteshvijay@microsoft.com>
2024-07-29 22:26:25 +05:30
Nitesh Vijay
1e6c40eabf Fix vector search capability name (#1918) 2024-07-23 07:33:24 +05:30
Nitesh Vijay
70d1dc6f74 Add vector search capability for emulator (#1917) 2024-07-20 00:02:42 +05:30
sindhuba
d07d2c7c0d Add readOnlyKeys call to support accounts with Reader role (#1916)
* Fix API endpoint for CassandraProxy query API

* activate Mongo Proxy and Cassandra Proxy in Prod

* Add CP Prod endpoint

* Run npm format and tests

* Revert code

* fix bug that blocked local mongo proxy and cassandra proxy development

* Add prod endpoint

* fix pr check tests

* Remove prod

* Remove prod endpoint

* Remove dev endpoint

* Support data plane RBAC

* Support data plane RBAC

* Add additional changes for Portal RBAC functionality

* Remove unnecessary code

* Remove unnecessary code

* Add code to fix VCoreMongo/PG bug

* Address feedback

* Add more logs for RBAC feature

* Add more logs for RBAC features

* Add readOnlyKeys call for accounts with Reader role

* Resolve conflicts

---------

Co-authored-by: Asier Isayas <aisayas@microsoft.com>
2024-07-19 08:02:44 -07:00
Asier Isayas
7a1aa89cd1 Add Cassandra Shell tab (#1913)
Co-authored-by: Asier Isayas <aisayas@microsoft.com>
2024-07-17 16:12:15 -04:00
sindhuba
e67c3f6774 Revert RBAC feature flag behavior (#1910)
* Fix API endpoint for CassandraProxy query API

* activate Mongo Proxy and Cassandra Proxy in Prod

* Add CP Prod endpoint

* Run npm format and tests

* Revert code

* fix bug that blocked local mongo proxy and cassandra proxy development

* Add prod endpoint

* fix pr check tests

* Remove prod

* Remove prod endpoint

* Remove dev endpoint

* Support data plane RBAC

* Support data plane RBAC

* Add additional changes for Portal RBAC functionality

* Remove unnecessary code

* Remove unnecessary code

* Add code to fix VCoreMongo/PG bug

* Address feedback

* Add more logs for RBAC feature

* Add more logs for RBAC features

* Revert enableAADDataPlane feature flag behavior

* Address feedback

* Address comment

---------

Co-authored-by: Asier Isayas <aisayas@microsoft.com>
2024-07-17 10:12:36 -07:00
Laurent Nguyen
bd334a118a Prevent tabsManagerContainer width to grow beyond parent's width (#1911) 2024-07-17 17:30:12 +02:00
sindhuba
5871c1e2d0 Add more logs for RBAC (#1906)
* Fix API endpoint for CassandraProxy query API

* activate Mongo Proxy and Cassandra Proxy in Prod

* Add CP Prod endpoint

* Run npm format and tests

* Revert code

* fix bug that blocked local mongo proxy and cassandra proxy development

* Add prod endpoint

* fix pr check tests

* Remove prod

* Remove prod endpoint

* Remove dev endpoint

* Support data plane RBAC

* Support data plane RBAC

* Add additional changes for Portal RBAC functionality

* Remove unnecessary code

* Remove unnecessary code

* Add code to fix VCoreMongo/PG bug

* Address feedback

* Add more logs for RBAC feature

* Add more logs for RBAC features

---------

Co-authored-by: Asier Isayas <aisayas@microsoft.com>
2024-07-10 10:16:05 -07:00
sindhuba
81dccbe5be Fix vCoreMongo/PostGres quickstart bug (#1903)
* Fix API endpoint for CassandraProxy query API

* activate Mongo Proxy and Cassandra Proxy in Prod

* Add CP Prod endpoint

* Run npm format and tests

* Revert code

* fix bug that blocked local mongo proxy and cassandra proxy development

* Add prod endpoint

* fix pr check tests

* Remove prod

* Remove prod endpoint

* Remove dev endpoint

* Support data plane RBAC

* Support data plane RBAC

* Add additional changes for Portal RBAC functionality

* Remove unnecessary code

* Remove unnecessary code

* Add code to fix VCoreMongo/PG bug

* Address feedback

---------

Co-authored-by: Asier Isayas <aisayas@microsoft.com>
2024-07-09 10:58:13 -07:00
vchske
49c3d0f0cb Reorder Asier's commits in order to deploy CRI fixes (#1905)
* Set AllowPartialScopes flag to true (#1900)

* add partial scopes flag

* add partial scopes flag

* add partial scopes flag

---------

Co-authored-by: Asier Isayas <aisayas@microsoft.com>

* Adding CRI fixes to pre RBAC commit (#1902)

Co-authored-by: Asier Isayas <aisayas@microsoft.com>

* Add Data Plane RBAC functionality (#1878)

* Fix API endpoint for CassandraProxy query API

* activate Mongo Proxy and Cassandra Proxy in Prod

* Add CP Prod endpoint

* Run npm format and tests

* Revert code

* fix bug that blocked local mongo proxy and cassandra proxy development

* Add prod endpoint

* fix pr check tests

* Remove prod

* Remove prod endpoint

* Remove dev endpoint

* Support data plane RBAC

* Support data plane RBAC

* Add additional changes for Portal RBAC functionality

* Address errors and checks

* Cleanup DP RBAC code

* Run format

* Fix unit tests

* Remove unnecessary code

* Run npm format

* Fix enableAadDataPlane feature flag behavior

* Fix  enable AAD dataplane feature flag behavior

* Address feedback comments

* Minor fix

* Add new fixes

* Fix Tables test

* Run npm format

---------

Co-authored-by: Asier Isayas <aisayas@microsoft.com>

* Fix LMS regression when using old backend (#1890)

Co-authored-by: Asier Isayas <aisayas@microsoft.com>

* Address RBAC local storage default setting issue (#1892)

* Fix API endpoint for CassandraProxy query API

* activate Mongo Proxy and Cassandra Proxy in Prod

* Add CP Prod endpoint

* Run npm format and tests

* Revert code

* fix bug that blocked local mongo proxy and cassandra proxy development

* Add prod endpoint

* fix pr check tests

* Remove prod

* Remove prod endpoint

* Remove dev endpoint

* Support data plane RBAC

* Support data plane RBAC

* Add additional changes for Portal RBAC functionality

* Address errors and checks

* Cleanup DP RBAC code

* Run format

* Fix unit tests

* Remove unnecessary code

* Run npm format

* Fix enableAadDataPlane feature flag behavior

* Fix  enable AAD dataplane feature flag behavior

* Address feedback comments

* Minor fix

* Add new fixes

* Fix Tables test

* Run npm format

* Address Local storage default setting issue

* Run npm format

* Address lint error

* Run format

---------

Co-authored-by: Asier Isayas <aisayas@microsoft.com>

* Fix bug in viewing tables account databases (#1899)

* Fix API endpoint for CassandraProxy query API

* activate Mongo Proxy and Cassandra Proxy in Prod

* Add CP Prod endpoint

* Run npm format and tests

* Revert code

* fix bug that blocked local mongo proxy and cassandra proxy development

* Add prod endpoint

* fix pr check tests

* Remove prod

* Remove prod endpoint

* Remove dev endpoint

* Support data plane RBAC

* Support data plane RBAC

* Add additional changes for Portal RBAC functionality

* Address errors and checks

* Cleanup DP RBAC code

* Run format

* Fix unit tests

* Remove unnecessary code

* Run npm format

* Fix enableAadDataPlane feature flag behavior

* Fix  enable AAD dataplane feature flag behavior

* Address feedback comments

* Minor fix

* Add new fixes

* Fix Tables test

* Run npm format

* Address Local storage default setting issue

* Run npm format

* Address lint error

* Run format

* Address bug in fetching data for Tables Account

* Add fetchAndUpdate Keys

* Add fix for MPAC Tables account issue

* Fix issue with Cosmos Client

* Run np format

* Address bugs

* Remove unused import

---------

Co-authored-by: Asier Isayas <aisayas@microsoft.com>

---------

Co-authored-by: Asier Isayas <asier.isayas@gmail.com>
Co-authored-by: Asier Isayas <aisayas@microsoft.com>
Co-authored-by: sindhuba <122321535+sindhuba@users.noreply.github.com>
2024-07-09 12:27:57 -04:00
Asier Isayas
375bb5f567 Adding CRI fixes to pre RBAC commit (#1902)
Co-authored-by: Asier Isayas <aisayas@microsoft.com>
2024-07-08 17:27:34 -04:00
Asier Isayas
e9f83a8efd Set AllowPartialScopes flag to true (#1900)
* add partial scopes flag

* add partial scopes flag

* add partial scopes flag

---------

Co-authored-by: Asier Isayas <aisayas@microsoft.com>
2024-07-08 16:30:14 -04:00
sindhuba
093ddba2db Fix bug in viewing tables account databases (#1899)
* Fix API endpoint for CassandraProxy query API

* activate Mongo Proxy and Cassandra Proxy in Prod

* Add CP Prod endpoint

* Run npm format and tests

* Revert code

* fix bug that blocked local mongo proxy and cassandra proxy development

* Add prod endpoint

* fix pr check tests

* Remove prod

* Remove prod endpoint

* Remove dev endpoint

* Support data plane RBAC

* Support data plane RBAC

* Add additional changes for Portal RBAC functionality

* Address errors and checks

* Cleanup DP RBAC code

* Run format

* Fix unit tests

* Remove unnecessary code

* Run npm format

* Fix enableAadDataPlane feature flag behavior

* Fix  enable AAD dataplane feature flag behavior

* Address feedback comments

* Minor fix

* Add new fixes

* Fix Tables test

* Run npm format

* Address Local storage default setting issue

* Run npm format

* Address lint error

* Run format

* Address bug in fetching data for Tables Account

* Add fetchAndUpdate Keys

* Add fix for MPAC Tables account issue

* Fix issue with Cosmos Client

* Run np format

* Address bugs

* Remove unused import

---------

Co-authored-by: Asier Isayas <aisayas@microsoft.com>
2024-07-08 08:48:22 -07:00
sindhuba
dfe79b20f5 Address RBAC local storage default setting issue (#1892)
* Fix API endpoint for CassandraProxy query API

* activate Mongo Proxy and Cassandra Proxy in Prod

* Add CP Prod endpoint

* Run npm format and tests

* Revert code

* fix bug that blocked local mongo proxy and cassandra proxy development

* Add prod endpoint

* fix pr check tests

* Remove prod

* Remove prod endpoint

* Remove dev endpoint

* Support data plane RBAC

* Support data plane RBAC

* Add additional changes for Portal RBAC functionality

* Address errors and checks

* Cleanup DP RBAC code

* Run format

* Fix unit tests

* Remove unnecessary code

* Run npm format

* Fix enableAadDataPlane feature flag behavior

* Fix  enable AAD dataplane feature flag behavior

* Address feedback comments

* Minor fix

* Add new fixes

* Fix Tables test

* Run npm format

* Address Local storage default setting issue

* Run npm format

* Address lint error

* Run format

---------

Co-authored-by: Asier Isayas <aisayas@microsoft.com>
2024-07-01 16:58:05 -07:00
Asier Isayas
1021e9c969 Fix LMS regression when using old backend (#1890)
Co-authored-by: Asier Isayas <aisayas@microsoft.com>
2024-07-01 12:40:07 -04:00
sindhuba
c30a9681fe Add Data Plane RBAC functionality (#1878)
* Fix API endpoint for CassandraProxy query API

* activate Mongo Proxy and Cassandra Proxy in Prod

* Add CP Prod endpoint

* Run npm format and tests

* Revert code

* fix bug that blocked local mongo proxy and cassandra proxy development

* Add prod endpoint

* fix pr check tests

* Remove prod

* Remove prod endpoint

* Remove dev endpoint

* Support data plane RBAC

* Support data plane RBAC

* Add additional changes for Portal RBAC functionality

* Address errors and checks

* Cleanup DP RBAC code

* Run format

* Fix unit tests

* Remove unnecessary code

* Run npm format

* Fix enableAadDataPlane feature flag behavior

* Fix  enable AAD dataplane feature flag behavior

* Address feedback comments

* Minor fix

* Add new fixes

* Fix Tables test

* Run npm format

---------

Co-authored-by: Asier Isayas <aisayas@microsoft.com>
2024-07-01 09:33:07 -07:00
Asier Isayas
17754cba05 Revert to old Mongo Proxy (#1886)
* revert to old mongo proxy

* revert to old mongo proxy

* cleanup

* cleanup

---------

Co-authored-by: Asier Isayas <aisayas@microsoft.com>
2024-06-27 11:40:05 -07:00
Asier Isayas
b07fa89a23 fix legacy mongo shell regression (#1883)
Co-authored-by: Asier Isayas <aisayas@microsoft.com>
2024-06-26 14:33:57 -04:00
sunghyunkang1111
28db549fa1 pagination loading of subscription and databaseaccounts (#1877) 2024-06-21 14:28:00 -05:00
Laurent Nguyen
fe892dcc62 Temporarily disable bulk Delete for old non-partitioned NoSQL containers (#1880)
* Disable Delete button and document selection when partitionKey.systemKey = true for noSql.

* Update unit tests

* Always show delete button. Use single delete API for systemKey containers
2024-06-21 09:37:34 +02:00
Asier Isayas
380caba5f5 Cassandra: Delete a row for a table that has multiple partition keys (#1879)
* Delete a row with multiple parition keys

* clean up

---------

Co-authored-by: Asier Isayas <aisayas@microsoft.com>
2024-06-20 12:55:14 -04:00
sunghyunkang1111
62ab0e3e60 Fix codeql issues (#1875) 2024-06-19 10:12:38 -05:00
SATYA SB
d199311633 Ensures ARIA attributes are allowed for an element's role. (#1846)
* [accessibility-3048277]:[Programmatic Access - Azure Cosmos DB - Data Explorer>New Container]: Ensures ARIA attributes are allowed for an element's role

* updated PartitionKeyPane

---------

Co-authored-by: Satyapriya Bai <v-satybai@microsoft.com>
2024-06-19 16:02:09 +05:30
sunghyunkang1111
bf225f91c4 Remove notification for sample collection loading (#1874) 2024-06-18 12:15:06 -05:00
jawelton74
4d0b1a6db8 Switch accountrestrictions call to use new backend in MPAC. (#1868) 2024-06-18 09:41:24 -07:00
SATYA SB
e66c8a1b5c [accessibility-1249101]:[Usable - Azure Cosmos DB - New Keyspace]: Link "capacity calculator" overlaps with the keyboard focus indicator boundary and not visible properly. (#1866)
Co-authored-by: Satyapriya Bai <v-satybai@microsoft.com>
2024-06-13 17:23:19 +05:30
SATYA SB
7e1a738f8e [accessibility-2819505]:[Programmatic access - Cosmos DB Query Copilot - Copilot]: "Read preview terms" link does not meet minimum contrast ratio 3:1 with the surrounding text. (#1858)
Co-authored-by: Satyapriya Bai <v-satybai@microsoft.com>
2024-06-13 17:21:49 +05:30
SATYA SB
dabb91e9e9 [accessibility-3101316]:[Programmatic Access - Azure Cosmos DB - Data Explorer]: 'Recents', 'Top 3 things you need to know' and 'Learning Resources' text appears as a heading but is not defined programmatically under 'Data Explorer' pane. (#1845)
Co-authored-by: Satyapriya Bai <v-satybai@microsoft.com>
2024-06-13 17:20:59 +05:30
sunghyunkang1111
7570d6b91d handle sampledb error handling to load the data explorer (#1870) 2024-06-12 20:46:22 -07:00
jawelton74
b8d6a0188a Add more details to the E2E Test README. Configure dev/test to use MPAC (#1869)
instances of the new backend services.
2024-06-12 12:32:48 -07:00
tarazou9
8c25742304 Update UPAPI for Dedicated Gateway in Self Serve Portal (#1825)
Update UPAPI for Dedicated Gateway in Self Serve Portal
2024-06-11 16:01:46 -04:00
Laurent Nguyen
1ba3a6c761 Tabs container now adapts its size with flex (#1867) 2024-06-11 17:17:45 +02:00
sunghyunkang1111
c680481fe0 Add form and validation for vector search (#1856)
* Add form and validation for vector search

* Add form and validation for vector search

* Add unit tests and merge forms
2024-06-10 13:37:51 -05:00
Laurent Nguyen
06d4829422 Fix click to show document issue. Table doesn't auto-select first document anymore. (#1864) 2024-06-07 16:28:08 +02:00
Ashley Stanton-Nurse
416743c548 stop tree nodes from expanding to full height, causing overlap (#1861) 2024-06-06 09:33:25 -07:00
Ashley Stanton-Nurse
b5d4509d49 fix a broken test snapshot (#1863) 2024-06-05 13:40:51 -07:00
Ashley Stanton-Nurse
417ef899f0 Update Playwright, improve E2E test reliability, add scripts to deploy test resources (#1857) 2024-06-05 12:46:32 -07:00
Ashley Stanton-Nurse
736731474f show an expand icon for nodes with non-null children arrray (#1862) 2024-06-05 12:27:29 -07:00
Ashley Stanton-Nurse
9b12775151 Allow query result view to be toggled from command bar (#1833)
* allow query result view to be toggled from command bar

also provides a default results view option that's stored in the
browser's local storage

* update SettingsPane test snapshot
2024-06-05 12:16:28 -07:00
Laurent Nguyen
7002da0b51 Implement ctrl-shift click to select multiple documents (#1851)
* Initial implementation of shift and ctrl click to select

* Implement shift-ctrl selection

* Fix snapshot, update selectionHelper comment

* Fix missing type

* Properly disable cursor selection

* Update snapshots

* Do not enable (multiselect) if readonly

* Consider meta key for mac and ctrl for everything else
2024-06-05 17:47:27 +02:00
SATYA SB
7c5fb1b697 [accessibility-3102730]:[Visual Requirement - Azure Cosmos DB - Data Explorer]: Luminosity ratio of 'Learn More' link with surrounding text is less than required 3:1 under 'Data Explorer' pane. (#1847) 2024-06-05 20:46:35 +05:30
SATYA SB
06e28ae3e7 [Visual Requirement - Azure Cosmos DB - Add Table]: Luminosity ratio of links with surrounding text is less than required 3:1 under 'Add Table' pane. (#1840) 2024-06-05 20:45:55 +05:30
SATYA SB
52c2cfe419 [Accessibility-3100036]: Close button is nested under 'Home' tab control under 'Data Explorer' pane. (#1818)
* [3100036]: [Programmatic Access - Azure Cosmos DB - Data Explorer]: Close button is nested under 'Home' tab control under 'Data Explorer' pane.

* Fabric-Less updated.

* Added specific width for contentWrapper.

* less update.

* Fixed out-scope space issue

---------

Co-authored-by: Satyapriya Bai <v-satybai@microsoft.com>
2024-06-05 20:42:48 +05:30
Laurent Nguyen
b76d83d8e1 Disable table selection for Fabric/read-only (#1855)
* Disable table selection for Fabric/read-only

* Update unit tests

* Fix format

---------

Co-authored-by: Laurent Nguyen <languye@microsoft.com>
2024-06-03 19:11:16 +02:00
Laurent Nguyen
495296602a Fix delete documents on mongo bug (#1852) 2024-06-03 15:23:09 +02:00
Asier Isayas
96ba0a9729 Reactivate Mongo Proxy (#1850)
* Reactivate Mongo Proxy

* Reactivate Mongo Proxy

---------

Co-authored-by: Asier Isayas <aisayas@microsoft.com>
2024-05-31 13:31:24 -04:00
Laurent Nguyen
6276464e0d Fix refreshgrid behaving like load more (#1853) 2024-05-31 17:57:11 +02:00
Ashley Stanton-Nurse
98c5fe65e6 Use new Fluent-based Resource Tree for all environments (#1841)
Co-authored-by: Laurent Nguyen <laurent.nguyen@microsoft.com>
2024-05-29 09:56:27 -07:00
Laurent Nguyen
cebf044803 Fix typo (#1849) 2024-05-29 18:20:07 +02:00
Laurent Nguyen
f669a99228 Separate Fabric-specific message types (#1848)
* Update message de->fabric to v3

* Reinstate get authorization token path which doesn't get called every 5 minutes anymore

* Remove obsolete comment

* Add missing types

* Fix format

* Fix build issue

* Revert "Reinstate get authorization token path which doesn't get called every 5 minutes anymore"

This reverts commit a3f3511043.

* Keep 3 old fabric message types enums for compatibility with the portal

* Re-add warning comment about not changing existing message type enums

---------

Co-authored-by: Laurent Nguyen <languye@microsoft.com>
2024-05-29 16:03:51 +02:00
Laurent Nguyen
36736882ee Migrate DocumentsTab to React and add bulk delete and column resize (#1770)
* Document page now loads list of docs and displays selection

* DocumentsTabV2 now properly loads documents, show partition keys and display first doc with proper selection behavior. Move it to its own folder.

* Extract table in a separate component

* Resizable columns on the document table

* Fix selection behavior and some layout issue

* Adding table scrolling

* Fix NaN height issue

* Fix NaN height issue

* Fix column sizing + cell selection

* Improvement in width size. Add Load More

* Add react editor and pass column headers

* Dynamic columns for pk

* Fix initial columns size

* Add nav buttons

* Editing content updates buttons state

* Discard and save buttons working

* Fix save new document. Implement delete.

* Remove debug display

* Fix unexpand filter and reformat

* Fix compil issues

* Add refresh button

* Update column header placeholder style

* Implement delete multiple docs

* Fix multi delete

* Fix show/hide delete button

* Fix selection behavior

* Fix UX with buttons behavior and editor display

* Fix UX issue with not discarding edit changes

* Add some TODO's

* Remove debugging info and reformat

* Add mongo support

* Fix build issues

* Fix table header. Remove debug statement

* Restore broken nosql

* Fix mongo save new document/update document

* Fix bugs with clicking on newly created documents

* Fix comment

* Fix double fetch issue when clicking on an item

* Auto-select last document when saving new document

* Fix resourceTokenPartitionKey code

* Fix format

* Fix isQueryCopilotSampleContainer flag

* Fix unused code

* Call tab when updating error flag

* Destructure props to make useEffect dependencies work

* Fix loadStartKey

* minor update

* Fix format

* Add title to table

* Fix table coming off its container with unwanted horizontal scrollbar

* Increase table width. Fix eslint issue.

* Move refresh documents button from table back to DocumentsTabV2

* Fix load more text centering

* Don't show Load More if nothing to show

* Fix columns min width

* Add keyboard shortcuts

* Add keyboard handlers to load more and refresh button

* Add keyboard support to select table row

* Disable eslint issue from fluent library

* Connect cancel query button

* Add Fluent V9 theme for Fabric (#1821)

* Clean up dependencies and memoize internal functions and object. Move methods and object that don't depend on state outside of component.

* Fix filter disappearing when clicking Apply Filter

* Fix typo and format

* Implement bulk delete for nosql

* Replace filter ui components with fluent ui

* Remove jquery calls

* Migrate unit test to DocumentsTabV2

* Remove DocumentsTab and MongoDocumentsTab. Fix build issues.

* Properly handle activetab

* Remove comments and unused code

* Port keyboard shortcuts from commitId 1f4d0f2

* Port item editor shortcuts to improved Items tab branch (#1831)

* set filter focus on Ctrl+Shift+F

* implement filter enter/esc keybinds

* remove debugging

* Collapse filter when query is executed

* Fix monaco editor not happy when parent is null

* Fix how bulk delete operation gets called when no partition key

* Fix update id list after delete

* Fix deleteDocuments

* Fix build issue

* Fix bug in mongo delete

* Fix mongo delete flow

* Proper error handling in mongo

* Handle >100 bulk delete operations

* Add unit tests for DocumentsTableComponent

* More improvements to table unit tests

* Fix import. Disable selection test for now

* Add more DocumentsTab unit react tests

* Remove selection test

* Add more unit tests. Add lcov coverage report to display in vscode

* Move unit tests to correct file

* Add unit test on command bar

* Fix build issues

* Add more unit tests

* Remove unneeded call

* Add DocumentsTab for Mongo API

* Fix linting errors

* Update fluent ui v9 dependency. Color columns separation. Fix refresh button placement to not interfere with header cell width.

* Revert @fluentui/react-components to a safe version that compiles

* Add confirmation window when documents have been deleted

* Fix mongo unit tests

* Fix format

* Update src/Common/dataAccess/deleteDocument.ts

Co-authored-by: Ashley Stanton-Nurse <ashleyst@microsoft.com>

* Update src/Common/dataAccess/deleteDocument.ts

Co-authored-by: Ashley Stanton-Nurse <ashleyst@microsoft.com>

* Update src/Common/dataAccess/deleteDocument.ts

Co-authored-by: Ashley Stanton-Nurse <ashleyst@microsoft.com>

* Fix bug with markup. Simplify code.

* Protect against creating React editor without parent node

* Replace rendering tests with snapshot match

* Add test screenshot to troubleshoot e2e test

* Revert "Add test screenshot to troubleshoot e2e test"

This reverts commit 1b8138ade0.

* Attempt 2 at troubleshooting failing test

* Revert "Attempt 2 at troubleshooting failing test"

This reverts commit 3e51a593bf.

* Delete button now shows if one or more rows are selected

---------

Co-authored-by: Vsevolod Kukol <sevoku@microsoft.com>
Co-authored-by: Ashley Stanton-Nurse <ashleyst@microsoft.com>
2024-05-29 09:09:13 +02:00
sunghyunkang1111
19d1e0d1df allow serverless accounts to have vector search embeddings (#1844) 2024-05-20 17:24:04 -07:00
sunghyunkang1111
ceeead8458 Vector search for NoSQL accounts (#1843)
* Add container vector policy and indexing policy support

* Add vector search capability

* hide vector settings for shared throughput DB

* update package-lock

* fix pipeline

* remove comments

* Address comments

* Address comments
2024-05-20 13:30:30 -05:00
sunghyunkang1111
4da3363cf7 add capacityMode (#1826)
* add capacityMode

* add check for capacityMode for serverless
2024-05-17 12:19:23 -05:00
jawelton74
ff4bc78d6c Remove preview label from Computed Properties. (#1842) 2024-05-17 06:57:35 -07:00
SATYA SB
b6e3e5ea1c Screen Reader does not announce status message after invoking 'Add Row' control under 'Add Table Row' pane. (#1837)
* [accessibility-3100026]: [Screen Reader - Azure Cosmos DB - Add Table Row]: Screen Reader does not announce status message after invoking 'Add Row' control under 'Add Table Row' pane.

* Fixed format.

* Snap update.

---------

Co-authored-by: Satyapriya Bai <v-satybai@microsoft.com>
2024-05-14 09:57:50 +05:30
SATYA SB
9e9d270b65 [accessibility-3102877]:[Programmatic Access - Azure CosmosDB – Data Explorer]: Ensures every ARIA input field has an accessible name. (#1835)
Co-authored-by: Satyapriya Bai <v-satybai@microsoft.com>
2024-05-14 09:56:53 +05:30
SATYA SB
f56e5e64b9 [accessibility-3102916]:[Keyboard Navigation - Azure CosmosDB - Data … (#1834)
* [accessibility-3102916]:[Keyboard Navigation - Azure CosmosDB - Data Explorer]: Keyboard focus is moving to non-interactive control after checkbox control of Advanced button.

* Updated Snap.

---------

Co-authored-by: Satyapriya Bai <v-satybai@microsoft.com>
2024-05-14 09:56:16 +05:30
Asier Isayas
14e5efcebf Point Mongo requests to old backend (#1838)
* point mongo requests to old backend

* point mongo requests to old backend

---------

Co-authored-by: Asier Isayas <aisayas@microsoft.com>
2024-05-09 13:42:59 -04:00
Ashley Stanton-Nurse
5c3f18f5f8 add link to keyboard shortcuts doc to home tab (#1836) 2024-05-07 12:30:46 -07:00
jawelton74
6ebc48ad28 Remove some Notebooks code (#1832)
* Remove onNewNotebookClicked, openUploadFilePanel functions and
UploadFilePane.

* Remove resetNotebookWorkspace function.

* Remove Notebooks related resource tree node generation.

* Fix test snapshots.
2024-05-02 07:14:31 -07:00
jawelton74
298197b1b8 Revert "First set of changes for Notebooks removal. (#1816)" (#1830)
This reverts commit b023250e67.
2024-05-01 07:21:50 -07:00
Ashley Stanton-Nurse
81a5b7cb6d add shortcuts for the Items tab (#1827)
* add shortcuts for the Items tab

* Add shortcut to clear Items tab filter.
2024-04-30 10:03:27 -07:00
jawelton74
b023250e67 First set of changes for Notebooks removal. (#1816)
* First set of changes for Notebooks removal.

* Fix unit test snapshots.
2024-04-29 15:46:24 -07:00
Asier Isayas
92246144f7 Enable Legacy Mongo Shell in Fairfax (#1829)
* enable Mongo Proxy and LMS in sovereign clouds

* remove mooncake

---------

Co-authored-by: Asier Isayas <aisayas@microsoft.com>
2024-04-29 16:25:58 -04:00
SATYA SB
a08415e7bc [3100018:[Programmatic Access - Azure Cosmos DB - Edit Property]: Text Area edit field does not have a label under 'Edit Property' pane. (#1819)
Co-authored-by: Satyapriya Bai <v-satybai@microsoft.com>
2024-04-29 22:26:27 +05:30
SATYA SB
b94ce28e96 [accessibility-2724013]:[Screen reader - Cosmos DB - Data Explorer -> Entities -> Add entity]: Screen reader announces incorrect role when focus lands on the "Edit" and "Delete" buttons. (#1822)
Co-authored-by: Satyapriya Bai <v-satybai@microsoft.com>
2024-04-29 22:23:49 +05:30
sunghyunkang1111
f8f7ea34bd Copilot rewording (#1824)
* Copilot rebranding to query advisor

* fix the subquery link
2024-04-26 14:09:55 -05:00
Asier Isayas
cbd5e6bf76 open Legacy Mongo SHell with correct base URL in sovereign clouds (#1823)
Co-authored-by: Asier Isayas <aisayas@microsoft.com>
2024-04-26 14:55:47 -04:00
Ashley Stanton-Nurse
618c5ec0fe Add button (and keyboard shortcut) to download query (#1817) 2024-04-24 15:11:51 -07:00
Asier Isayas
afc82845b5 activate Token Controller (#1820)
Co-authored-by: Asier Isayas <aisayas@microsoft.com>
2024-04-24 15:04:01 -04:00
Ashley Stanton-Nurse
f4bcee5461 initialize new documents with their partition key (#1815)
* initialize new documents with their partition key

* refmt
2024-04-23 15:47:04 -07:00
Ashley Stanton-Nurse
17207624a9 add more intl-friendly tab nav shortcuts (#1814) 2024-04-23 15:46:41 -07:00
jawelton74
d36e511b18 Update d3, webpack-dev-server, typedoc dependencies. (#1812)
* Update d3, webpack-dev-server, typedoc dependencies.

* Fix unit test failures.

* Revert change to snapshot as it doesn't seem required when running in
github.
2024-04-23 10:15:48 -07:00
Ashley Stanton-Nurse
c1a28793ba bind F5 to execute query (#1813) 2024-04-23 09:08:29 -07:00
Asier Isayas
acf5acfdb4 Remove Legacy Mongo Shell feature flag (#1810)
* LMS Mongo Proxy support

* change stirng to url for get mongo shell url

* fix tests

* enable feature flag

* fixed unit test

* add mongoshell to path

* remove LMS feature flag

---------

Co-authored-by: Asier Isayas <aisayas@microsoft.com>
2024-04-23 08:20:27 -04:00
jawelton74
7b81767ded Enable new backend for Settings API in Prod. (#1791) 2024-04-22 14:34:20 -07:00
jawelton74
c12eced120 Update node-fetch, react-dev-utils and azure/identity dependencies. (#1809) 2024-04-22 07:10:16 -07:00
jawelton74
2b15a4d43d Update package.json (#1807) 2024-04-19 15:03:11 -07:00
Ashley Stanton-Nurse
c220a8b070 [Task 3071878] Tab Navigation Keyboard Shortcuts (#1808)
* [Task 3071878] Tab Navigation Keyboard Shortcuts

* throw in development on duplicate handlers

* refmt
2024-04-19 13:44:30 -07:00
Ashley Stanton-Nurse
a5a5a95973 [Task 3061766] Additional Keyboard Shortcuts (#1805)
* [Task 3061766] Additional Keyboard Shortcuts

refmt and fix lints

shortcuts for: discard, new item/sproc/udf/trigger, delete
item/sproc/udf/trigger

copilot shortcut

* remove 'Ctrl+I' due to conflict with Monaco Autocomplete
2024-04-19 09:43:27 -07:00
Asier Isayas
e3fab9b5bf Add 'mongoshell' to Legacy Mongo Shell path (#1806)
* LMS Mongo Proxy support

* change stirng to url for get mongo shell url

* fix tests

* enable feature flag

* fixed unit test

* add mongoshell to path

---------

Co-authored-by: Asier Isayas <aisayas@microsoft.com>
2024-04-18 15:39:13 -04:00
Asier Isayas
98000a27f0 Legacy Mongo Shell Mongo Proxy support (#1802)
* LMS Mongo Proxy support

* change stirng to url for get mongo shell url

* fix tests

* enable feature flag

* fixed unit test

---------

Co-authored-by: Asier Isayas <aisayas@microsoft.com>
2024-04-17 19:01:12 -04:00
Ashley Stanton-Nurse
af664326ea Fix issues with the command bar when switching through React and Trigger tabs (#1804)
* fix bug in trigger tab that takes over the command bar while open

* clear context buttons when a react tab is active

* restore unintentionally removed code

* reformat
2024-04-17 15:57:29 -07:00
Ashley Stanton-Nurse
a44ed1f45c [Task 3061766] Global Keyboard Shortcuts, implemented through the Command Bar (#1789)
* keyboard shortcuts using tinykeys

* refmt and fix lints

* retarget keyboard shortcuts to the body instead of the root element of the React component tree

* refmt

* Update src/Explorer/Menus/CommandBar/CommandBarUtil.tsx

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

* add Save binding to New Item command bar

---------

Co-authored-by: Laurent Nguyen <laurent.nguyen@microsoft.com>
2024-04-17 11:19:09 -07:00
JustinKol
e0cb3da6aa Is executing is false (#1801) 2024-04-16 18:47:43 -04:00
JustinKol
6c9673975a Added hyphen to prohibited characters in keyspace name title (#1800) 2024-04-16 09:24:14 -04:00
JustinKol
d35e2a325e Cassandra API create table error messages swallowed by the Portal and shown as "undefined". (#1790)
* changed error message variable

* changed other error messages

* Added check in case responseJSON is missing

* created error const
2024-04-15 15:47:58 -04:00
sunghyunkang1111
00a816c488 set the value in the editor for results (#1799) 2024-04-13 15:19:56 -05:00
jawelton74
953bef404b Set backend endpoints in testExplorer to use MPAC. (#1797) 2024-04-11 15:43:46 -07:00
294 changed files with 37041 additions and 30952 deletions

10
.editorconfig Normal file
View File

@@ -0,0 +1,10 @@
# NOTE: Prettier reads EditorConfig settings, so be careful adjusting settings here and assuming they'll only affect your editor ;).
# top-most EditorConfig file
root = true
[*.yml]
indent_size = 2
[*.{js,jsx,ts,tsx}]
indent_size = 2

View File

@@ -1,3 +1,5 @@
playwright.config.ts
**/node_modules/ **/node_modules/
src/**/__mocks__/**/* src/**/__mocks__/**/*
dist/ dist/
@@ -89,10 +91,7 @@ src/Explorer/Tables/TableEntityProcessor.ts
src/Explorer/Tables/Utilities.ts src/Explorer/Tables/Utilities.ts
src/Explorer/Tabs/ConflictsTab.ts src/Explorer/Tabs/ConflictsTab.ts
src/Explorer/Tabs/DatabaseSettingsTab.ts src/Explorer/Tabs/DatabaseSettingsTab.ts
src/Explorer/Tabs/DocumentsTab.test.ts
src/Explorer/Tabs/DocumentsTab.ts
src/Explorer/Tabs/GraphTab.ts src/Explorer/Tabs/GraphTab.ts
src/Explorer/Tabs/MongoDocumentsTab.ts
src/Explorer/Tabs/NotebookV2Tab.ts src/Explorer/Tabs/NotebookV2Tab.ts
src/Explorer/Tabs/ScriptTabBase.ts src/Explorer/Tabs/ScriptTabBase.ts
src/Explorer/Tabs/TabComponents.ts src/Explorer/Tabs/TabComponents.ts
@@ -128,7 +127,7 @@ src/Explorer/Controls/InputTypeahead/InputTypeaheadComponent.tsx
src/Explorer/Controls/Notebook/NotebookTerminalComponent.test.tsx src/Explorer/Controls/Notebook/NotebookTerminalComponent.test.tsx
src/Explorer/Controls/Notebook/NotebookTerminalComponent.tsx src/Explorer/Controls/Notebook/NotebookTerminalComponent.tsx
src/Explorer/Controls/NotebookViewer/NotebookViewerComponent.tsx src/Explorer/Controls/NotebookViewer/NotebookViewerComponent.tsx
src/Explorer/Controls/TreeComponent/TreeComponent.tsx src/Explorer/Controls/TreeComponent/LegacyTreeComponent.tsx
src/Explorer/Graph/GraphExplorerComponent/GraphExplorer.test.tsx src/Explorer/Graph/GraphExplorerComponent/GraphExplorer.test.tsx
src/Explorer/Graph/GraphExplorerComponent/NodePropertiesComponent.tsx src/Explorer/Graph/GraphExplorerComponent/NodePropertiesComponent.tsx
src/Explorer/Graph/GraphExplorerComponent/ReadOnlyNodePropertiesComponent.test.tsx src/Explorer/Graph/GraphExplorerComponent/ReadOnlyNodePropertiesComponent.test.tsx

View File

@@ -104,79 +104,6 @@ jobs:
run: az storage blob upload -c '$web' -f ./preview/config.json --account-name cosmosexplorerpreview --name "${{github.event.pull_request.head.sha || github.sha}}/config.json" --account-key="${PREVIEW_STORAGE_KEY}" --overwrite true run: az storage blob upload -c '$web' -f ./preview/config.json --account-name cosmosexplorerpreview --name "${{github.event.pull_request.head.sha || github.sha}}/config.json" --account-key="${PREVIEW_STORAGE_KEY}" --overwrite true
env: env:
PREVIEW_STORAGE_KEY: ${{ secrets.PREVIEW_STORAGE_KEY }} PREVIEW_STORAGE_KEY: ${{ secrets.PREVIEW_STORAGE_KEY }}
endtoendemulator:
name: "End To End Emulator Tests"
# Temporarily disabled. This test needs to be rewritten in playwright
if: false
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- name: Use Node.js 18.x
uses: actions/setup-node@v4
with:
node-version: 18.x
- uses: southpolesteve/cosmos-emulator-github-action@v1
- name: End to End Tests
run: |
npm ci
npm start &
npm run wait-for-server
npx jest -c ./jest.config.e2e.js --detectOpenHandles test/sql/container.spec.ts
shell: bash
env:
DATA_EXPLORER_ENDPOINT: "https://localhost:1234/explorer.html?platform=Emulator"
PLATFORM: "Emulator"
NODE_TLS_REJECT_UNAUTHORIZED: 0
- uses: actions/upload-artifact@v3
if: failure()
with:
name: screenshots
path: failed-*
endtoend:
name: "E2E"
runs-on: ubuntu-latest
env:
NODE_TLS_REJECT_UNAUTHORIZED: 0
AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
strategy:
fail-fast: false
matrix:
test-file:
- ./test/cassandra/container.spec.ts
- ./test/graph/container.spec.ts
- ./test/sql/container.spec.ts
- ./test/mongo/container.spec.ts
- ./test/mongo/container32.spec.ts
- ./test/selfServe/selfServeExample.spec.ts
- ./test/sql/resourceToken.spec.ts
- ./test/tables/container.spec.ts
steps:
- uses: actions/checkout@v4
- name: "Az CLI login"
uses: azure/login@v1
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Use Node.js 18.x
uses: actions/setup-node@v4
with:
node-version: 18.x
- run: npm ci
- run: npm start &
- run: npm run wait-for-server
- name: ${{ matrix['test-file'] }}
run: |
# Run tests up to three times
for i in $(seq 1 3); do npx jest -c ./jest.config.playwright.js ${{ matrix['test-file'] }} && s=0 && break || s=$? && sleep 1; done; (exit $s)
shell: bash
- uses: actions/upload-artifact@v3
if: failure()
with:
name: screenshots
path: screenshots/
nuget: nuget:
name: Publish Nuget name: Publish Nuget
if: github.ref == 'refs/heads/master' || contains(github.ref, 'hotfix/') || contains(github.ref, 'release/') if: github.ref == 'refs/heads/master' || contains(github.ref, 'hotfix/') || contains(github.ref, 'release/')
@@ -226,3 +153,70 @@ jobs:
name: packages name: packages
with: with:
path: "*.nupkg" path: "*.nupkg"
playwright-tests:
name: "Run Playwright Tests (Shard ${{ matrix.shardIndex }} of ${{ matrix.shardTotal }})"
runs-on: ubuntu-latest
env:
NODE_TLS_REJECT_UNAUTHORIZED: 0
AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
strategy:
fail-fast: false
matrix:
shardIndex: [1, 2, 3, 4, 5, 6, 7, 8]
shardTotal: [8]
steps:
- uses: actions/checkout@v4
- name: "Az CLI login"
uses: azure/login@v1
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Use Node.js 18.x
uses: actions/setup-node@v4
with:
node-version: 18.x
- run: npm ci
- run: npx playwright install --with-deps
- name: Run test shard ${{ matrix['shardIndex'] }} of ${{ matrix['shardTotal']}}
run: npx playwright test --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
- name: Upload blob report to GitHub Actions Artifacts
if: ${{ !cancelled() }}
uses: actions/upload-artifact@v4
with:
name: blob-report-${{ matrix.shardIndex }}
path: blob-report
retention-days: 1
merge-playwright-reports:
name: "Merge Playwright Reports"
# Merge reports after playwright-tests, even if some shards have failed
if: ${{ !cancelled() }}
needs: [playwright-tests]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18
- name: Install dependencies
run: npm ci
- name: Download blob reports from GitHub Actions Artifacts
uses: actions/download-artifact@v4
with:
path: all-blob-reports
pattern: blob-report-*
merge-multiple: true
- name: Merge into HTML Report
run: npx playwright merge-reports --reporter html ./all-blob-reports
- name: Upload HTML report
uses: actions/upload-artifact@v4
with:
name: html-report--attempt-${{ github.run_attempt }}
path: playwright-report
retention-days: 14

6
.gitignore vendored
View File

@@ -16,4 +16,8 @@ Contracts/*
.env .env
failure.png failure.png
screenshots/* screenshots/*
GettingStarted-ignore*.ipynb GettingStarted-ignore*.ipynb
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/

5
.npmrc
View File

@@ -1 +1,4 @@
save-exact=true save-exact=true
# Ignore peer dependency conflicts
force=true # TODO: Remove this when we update to React 17 or higher!

View File

@@ -1,7 +0,0 @@
# Why?
This adds a mock module for `canvas`. Nteract has a ignored require and undeclared dependency on this module. `cavnas` is a server side node module and is not used in browser side code for nteract.
Installing it locally (`npm install canvas`) will resolve the problem, but it is a native module so it is flaky depending on the system, node version, processor arch, etc. This module provides a simpler, more robust solution.
Remove this workaround if [this bug](https://github.com/nteract/any-vega/issues/2) ever gets resolved

View File

@@ -1 +0,0 @@
module.exports = {}

View File

@@ -1,11 +0,0 @@
{
"name": "canvas",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}

9
images/EntraID.svg Normal file
View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="uuid-f8d4d392-7c12-4bd9-baff-66fbf7814b91" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18">
<path d="m3.802,14.032c.388.242,1.033.511,1.715.511.621,0,1.198-.18,1.676-.487,0,0,.001,0,.002-.001l1.805-1.128v4.073c-.286,0-.574-.078-.824-.234l-4.374-2.734Z" fill="#225086"/>
<path d="m7.853,1.507L.353,9.967c-.579.654-.428,1.642.323,2.111,0,0,2.776,1.735,3.126,1.954.388.242,1.033.511,1.715.511.621,0,1.198-.18,1.676-.487,0,0,.001,0,.002-.001l1.805-1.128-4.364-2.728,4.365-4.924V1s0,0,0,0c-.424,0-.847.169-1.147.507Z" fill="#6df"/>
<polygon points="4.636 10.199 4.688 10.231 9 12.927 9.001 12.927 9.001 12.927 9.001 5.276 9 5.275 4.636 10.199" fill="#cbf8ff"/>
<path d="m17.324,12.078c.751-.469.902-1.457.323-2.111l-4.921-5.551c-.397-.185-.842-.291-1.313-.291-.925,0-1.752.399-2.302,1.026l-.109.123h0s4.364,4.924,4.364,4.924h0s0,0,0,0l-4.365,2.728v4.073c.287,0,.573-.078.823-.234l7.5-4.688Z" fill="#074793"/>
<path d="m9.001,1v4.275s.109-.123.109-.123c.55-.627,1.377-1.026,2.302-1.026.472,0,.916.107,1.313.291l-2.579-2.909c-.299-.338-.723-.507-1.146-.507Z" fill="#0294e4"/>
<polygon points="13.365 10.199 13.365 10.199 13.365 10.199 9.001 5.276 9.001 12.926 13.365 10.199" fill="#96bcc2"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -1,13 +0,0 @@
const isCI = require("is-ci");
module.exports = {
exitOnPageError: false,
launchOptions: {
headless: isCI,
slowMo: 10,
timeout: 60000,
},
contextOptions: {
ignoreHTTPSErrors: true,
},
};

View File

@@ -31,7 +31,7 @@ module.exports = {
coveragePathIgnorePatterns: ["/node_modules/"], coveragePathIgnorePatterns: ["/node_modules/"],
// A list of reporter names that Jest uses when writing coverage reports // A list of reporter names that Jest uses when writing coverage reports
coverageReporters: ["json", "text", "cobertura"], coverageReporters: ["json", "text", "cobertura", "lcov"],
// An object that configures minimum threshold enforcement for coverage results // An object that configures minimum threshold enforcement for coverage results
coverageThreshold: { coverageThreshold: {
@@ -76,6 +76,11 @@ module.exports = {
"^dnd-core$": "dnd-core/dist/cjs", "^dnd-core$": "dnd-core/dist/cjs",
"^react-dnd$": "react-dnd/dist/cjs", "^react-dnd$": "react-dnd/dist/cjs",
"^react-dnd-html5-backend$": "react-dnd-html5-backend/dist/cjs", "^react-dnd-html5-backend$": "react-dnd-html5-backend/dist/cjs",
"d3-force": "<rootDir>/node_modules/d3-force/dist/d3-force.min.js",
"d3-quadtree": "<rootDir>/node_modules/d3-quadtree/dist/d3-quadtree.min.js",
"d3-scale-chromatic": "<rootDir>/node_modules/d3-scale-chromatic/dist/d3-scale-chromatic.min.js",
"d3-zoom": "<rootDir>/node_modules/d3-zoom/dist/d3-zoom.min.js",
uuid: require.resolve("uuid"), // Force module uuid to resolve with the CJS entry point, because Jest does not support package.json.exports. See https://github.com/uuidjs/uuid/issues/451
}, },
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
@@ -129,8 +134,7 @@ module.exports = {
snapshotSerializers: ["enzyme-to-json/serializer"], snapshotSerializers: ["enzyme-to-json/serializer"],
// The test environment that will be used for testing // The test environment that will be used for testing
// testEnvironment: "jest-environment-jsdom", testEnvironment: "jsdom",
modulePaths: ["node_modules", "<rootDir>/src"], modulePaths: ["node_modules", "<rootDir>/src"],
// Options that will be passed to the testEnvironment // Options that will be passed to the testEnvironment
@@ -154,7 +158,7 @@ module.exports = {
// testResultsProcessor: "./trxProcessor.js", // testResultsProcessor: "./trxProcessor.js",
// This option allows use of a custom test runner // This option allows use of a custom test runner
// testRunner: "jasmine2", testRunner: "jest-circus/runner",
// This option sets the URL for the jsdom environment. It is reflected in properties such as location.href // This option sets the URL for the jsdom environment. It is reflected in properties such as location.href
// testURL: "http://localhost", // testURL: "http://localhost",
@@ -164,9 +168,9 @@ module.exports = {
// A map from regular expressions to paths to transformers // A map from regular expressions to paths to transformers
transform: { transform: {
"^.+\\.html?$": "html-loader-jest", "^.+\\.html?$": "jest-html-loader",
"^.+\\.[t|j]sx?$": "babel-jest", "^.+\\.[t|j]sx?$": "babel-jest",
"^.+\\.svg$": "<rootDir>/svgTransform.js", "^.+\\.svg$": "<rootDir>/jest/svgTransform.js",
}, },
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
@@ -183,4 +187,7 @@ module.exports = {
// Whether to use watchman for file crawling // Whether to use watchman for file crawling
// watchman: true, // watchman: true,
// TODO: toMatchInlineSnapshot() does not work with prettier 3. Remove when fixed: https://github.com/jestjs/jest/issues/14305
prettierPath: null,
}; };

View File

@@ -1,7 +0,0 @@
module.exports = {
preset: "jest-playwright-preset",
testMatch: ["<rootDir>/test/**/*.spec.[jt]s?(x)"],
setupFiles: ["dotenv/config"],
testEnvironment: "./test/playwrightEnv.js",
setupFilesAfterEnv: ["expect-playwright"],
};

View File

@@ -130,6 +130,7 @@
@ActiveTabWidth: 141px; @ActiveTabWidth: 141px;
@TabsHeight: 30px; @TabsHeight: 30px;
@TabsWidth: 140px; @TabsWidth: 140px;
@ContentWrapper: 111px;
@StatusIconContainerSize: 18px; @StatusIconContainerSize: 18px;
@LoadingErrorIconSize: 14px; @LoadingErrorIconSize: 14px;
@ErrorIconContainer: 16px; @ErrorIconContainer: 16px;
@@ -167,7 +168,7 @@
@FabricBoxBorderRadius: 8px; @FabricBoxBorderRadius: 8px;
@FabricBoxBorderShadow: rgba(0, 0, 0, 0.133) 0px 1.6px 3.6px 0px, rgba(0, 0, 0, 0.11) 0px 0.3px 0.9px 0px; @FabricBoxBorderShadow: rgba(0, 0, 0, 0.133) 0px 1.6px 3.6px 0px, rgba(0, 0, 0, 0.11) 0px 0.3px 0.9px 0px;
@FabricBoxMargin: 4px 3px 4px 3px; @FabricBoxMargin: 4px 8px 4px 8px;
@FabricAccentMediumHigh: #0c695a; @FabricAccentMediumHigh: #0c695a;
@FabricAccentMedium: #117865; @FabricAccentMedium: #117865;
@@ -335,4 +336,11 @@
width: 0; width: 0;
height: 0; height: 0;
border-color: @InfoPointerColor transparent; border-color: @InfoPointerColor transparent;
}
/*********************************************************************************************************
Screen Reader Only
**********************************************************************************************************/
.screenReaderOnly {
position: absolute;
left: -9999px;
} }

View File

@@ -1906,7 +1906,7 @@ input::-webkit-calendar-picker-indicator::after {
} }
.nav-tabs-margin { .nav-tabs-margin {
padding-top: 8px; padding-top: 5px;
background-color: #f2f2f2; background-color: #f2f2f2;
} }
@@ -2074,14 +2074,6 @@ a:link {
display: inline; display: inline;
} }
.resourceTreeAndTabs {
display: flex;
flex: 1 1 auto;
overflow-x: clip;
overflow-y: auto;
height: 100%;
}
.collectiontitle { .collectiontitle {
font-size: 14px; font-size: 14px;
text-transform: uppercase; text-transform: uppercase;
@@ -2264,33 +2256,33 @@ a:link {
width: 82px; width: 82px;
} }
.tabdocuments .scrollable { // .tabdocuments .scrollable {
height: 100%; // height: 100%;
overflow-y: auto; // overflow-y: auto;
overflow-x: hidden; // overflow-x: hidden;
padding-left: 5px; // padding-left: 5px;
padding-right: 5px; // padding-right: 5px;
width: 100%; // width: 100%;
} // }
.tabdocuments > .tabdocumentsGridElement { // .tabdocuments > .tabdocumentsGridElement {
width: 50%; // width: 50%;
} // }
.tabdocuments > .evenlySpacedHeader { // .tabdocuments > .evenlySpacedHeader {
width: 30%; // width: 30%;
} // }
.tabdocuments.scrollable:focus, // .tabdocuments.scrollable:focus,
.tabdocuments.scrollable:active { // .tabdocuments.scrollable:active {
outline: 1px dotted; // outline: 1px dotted;
} // }
.tabdocuments .scrollable table td { // .tabdocuments .scrollable table td {
white-space: nowrap; // white-space: nowrap;
overflow: hidden; // overflow: hidden;
text-overflow: ellipsis; // text-overflow: ellipsis;
} // }
.mongoDocumentEditor .monaco-editor.vs .redsquiggly { .mongoDocumentEditor .monaco-editor.vs .redsquiggly {
display: none !important; display: none !important;
@@ -2316,21 +2308,15 @@ td a:hover {
} }
.loadMore { .loadMore {
display: block;
width: 100%; width: 100%;
padding-left: 30%; text-align: center;
padding-top: 2px;
cursor: pointer;
} }
.loadMore > a:focus { .loadMore > a:focus {
outline: 1px dotted; outline: 1px dotted;
} }
#content.active .tabdocuments .scrollable {
height: 100%;
overflow-y: auto;
}
.table-fixed thead { .table-fixed thead {
width: 97%; width: 97%;
padding-left: 18px; padding-left: 18px;
@@ -2366,10 +2352,9 @@ a:link {
.tabsManagerContainer { .tabsManagerContainer {
height: 100%; height: 100%;
flex-grow: 1; display: flex;
overflow: hidden; flex-direction: column;
min-height: 300px; min-width: 0; // This prevents it to grow past the parent's width if its content is too wide
overflow-y: scroll;
} }
.tabs { .tabs {
@@ -2558,10 +2543,12 @@ a:link {
} }
.filterdivs { .filterdivs {
padding-top: 15px; margin: 10px 0px;
height: 45px;
margin-bottom: 8px;
white-space: nowrap; white-space: nowrap;
input {
line-height: 14px; // To correct vertical position of the down arrow of the input
outline: none; // Remove the dotted border on focus, because fluent has its own focus style (underlined)
}
} }
.editFilterContainer { .editFilterContainer {
@@ -2624,7 +2611,7 @@ a:link {
.tabPanesContainer { .tabPanesContainer {
display: flex; display: flex;
height: 100%; flex-grow: 1;
overflow: hidden; overflow: hidden;
} }
@@ -2658,7 +2645,7 @@ a:link {
width: @ActiveTabWidth; width: @ActiveTabWidth;
} }
.nav-tabs > li.active > .tabNavContentContainer > .tab_Content > .tabNavText { .nav-tabs > li.active > .tabNavContentContainer > .tab_Content > .contentWrapper > .tabNavText {
font-weight: bolder; font-weight: bolder;
border-bottom: 2px solid rgba(0, 120, 212, 1); border-bottom: 2px solid rgba(0, 120, 212, 1);
} }
@@ -2694,67 +2681,71 @@ a:link {
width: @TabsWidth; width: @TabsWidth;
border-right: @ButtonBorderWidth solid @BaseMedium; border-right: @ButtonBorderWidth solid @BaseMedium;
white-space: nowrap; white-space: nowrap;
.contentWrapper {
.flex-display();
width: @ContentWrapper;
.statusIconContainer { .statusIconContainer {
width: @StatusIconContainerSize; width: @StatusIconContainerSize;
height: @StatusIconContainerSize; height: @StatusIconContainerSize;
margin-left: @SmallSpace; margin-left: @SmallSpace;
display: inline-flex; display: inline-flex;
.errorIconContainer { .errorIconContainer {
width: @ErrorIconContainer; width: @ErrorIconContainer;
height: @ErrorIconContainer; height: @ErrorIconContainer;
margin-top: 1px; margin-top: 1px;
.errorIcon { .errorIcon {
width: @ErrorIconWidth; width: @ErrorIconWidth;
height: @LoadingErrorIconSize;
background-image: url(../images/error_no_outline.svg);
background-repeat: no-repeat;
background-position: center;
background-size: 3px;
display: block;
margin: 1px 0px 0px 6px;
}
}
.errorIconContainer.actionsEnabled {
&:hover {
.hover();
}
&:focus {
.focus();
}
&:active {
.active();
}
}
.errorIconContainer[tabindex]:active {
outline: none;
}
.loadingIcon {
width: @LoadingErrorIconSize;
height: @LoadingErrorIconSize; height: @LoadingErrorIconSize;
background-image: url(../images/error_no_outline.svg); margin: 0px 0px @SmallSpace @SmallSpace;
background-repeat: no-repeat;
background-position: center;
background-size: 3px;
display: block;
margin: 1px 0px 0px 6px;
} }
} }
.errorIconContainer.actionsEnabled { .tabNavText {
&:hover { margin-left: @SmallSpace;
.hover(); margin-right: 2px;
} color: @BaseDark;
text-overflow: ellipsis;
&:focus { overflow: hidden;
.focus(); white-space: nowrap;
} flex-grow: 1;
&:active {
.active();
}
} }
.errorIconContainer[tabindex]:active {
outline: none;
}
.loadingIcon {
width: @LoadingErrorIconSize;
height: @LoadingErrorIconSize;
margin: 0px 0px @SmallSpace @SmallSpace;
}
}
.tabNavText {
margin-left: @SmallSpace;
margin-right: 2px;
color: @BaseDark;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
flex-grow: 1;
} }
.tabIconSection { .tabIconSection {
width: 30px; width: 29px;
position: relative; position: relative;
padding-top: 2px; padding-top: 2px;

View File

@@ -38,7 +38,7 @@ a:focus {
} }
.nav-tabs-margin { .nav-tabs-margin {
padding-top: 8px; padding-top: 5px;
background-color: #ffffff background-color: #ffffff
} }
@@ -75,7 +75,7 @@ a:focus {
border-bottom: 2px solid @FabricAccentMedium; border-bottom: 2px solid @FabricAccentMedium;
} }
.nav-tabs>li.active>.tabNavContentContainer>.tab_Content>.tabNavText { .nav-tabs>li.active>.tabNavContentContainer>.tab_Content>.contentWrapper>.tabNavText {
border-bottom: 0px none transparent; border-bottom: 0px none transparent;
} }
@@ -93,9 +93,11 @@ a:focus {
width: calc(@TabsWidth - (@SmallSpace * 2)); width: calc(@TabsWidth - (@SmallSpace * 2));
padding-bottom: @SmallSpace; padding-bottom: @SmallSpace;
.statusIconContainer { .contentWrapper {
margin-left: 0px; .statusIconContainer {
} margin-left: 0px;
}
}
.tabIconSection { .tabIconSection {
.cancelButton { .cancelButton {

View File

@@ -3,19 +3,6 @@
.dataResourceTree { .dataResourceTree {
margin-left: @MediumSpace; margin-left: @MediumSpace;
overflow: auto; overflow: auto;
.databaseHeader {
padding: 1px;
font-size: 14px;
}
.collectionHeader {
font-size: 12px;
}
.loadMoreHeader {
color: RGB(5, 99, 193);
}
} }
.notebookResourceTree { .notebookResourceTree {

22686
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -5,21 +5,21 @@
"main": "index.js", "main": "index.js",
"dependencies": { "dependencies": {
"@azure/arm-cosmosdb": "9.1.0", "@azure/arm-cosmosdb": "9.1.0",
"@azure/cosmos": "4.0.1-beta.2", "@azure/cosmos": "4.0.1-beta.3",
"@azure/cosmos-language-service": "0.0.5", "@azure/cosmos-language-service": "0.0.5",
"@azure/identity": "1.2.1", "@azure/identity": "1.5.2",
"@azure/ms-rest-nodeauth": "3.0.7", "@azure/ms-rest-nodeauth": "3.1.1",
"@azure/msal-browser": "2.14.2", "@azure/msal-browser": "2.14.2",
"@babel/plugin-proposal-class-properties": "7.12.1", "@babel/plugin-proposal-class-properties": "7.12.1",
"@babel/plugin-proposal-decorators": "7.12.12", "@babel/plugin-proposal-decorators": "7.12.12",
"@fluentui/react": "8.112.1", "@fluentui/react": "8.119.0",
"@fluentui/react-components": "9.34.0", "@fluentui/react-components": "9.54.2",
"@jupyterlab/services": "6.0.2", "@jupyterlab/services": "6.0.2",
"@jupyterlab/terminal": "3.0.3", "@jupyterlab/terminal": "3.0.3",
"@microsoft/applicationinsights-web": "2.6.1", "@microsoft/applicationinsights-web": "2.6.1",
"@nteract/commutable": "7.5.1", "@nteract/commutable": "7.5.1",
"@nteract/connected-components": "6.8.2", "@nteract/connected-components": "6.8.2",
"@nteract/core": "15.1.0", "@nteract/core": "15.1.9",
"@nteract/data-explorer": "8.0.3", "@nteract/data-explorer": "8.0.3",
"@nteract/directory-listing": "2.0.6", "@nteract/directory-listing": "2.0.6",
"@nteract/dropdown-menu": "1.0.1", "@nteract/dropdown-menu": "1.0.1",
@@ -42,19 +42,21 @@
"@nteract/transform-vega": "7.0.6", "@nteract/transform-vega": "7.0.6",
"@octokit/rest": "17.9.2", "@octokit/rest": "17.9.2",
"@phosphor/widgets": "1.9.3", "@phosphor/widgets": "1.9.3",
"@testing-library/jest-dom": "5.11.9", "@testing-library/jest-dom": "6.4.6",
"@types/lodash": "4.14.171", "@types/lodash": "4.14.171",
"@types/mkdirp": "1.0.1", "@types/mkdirp": "1.0.1",
"@types/node-fetch": "2.5.7", "@types/node-fetch": "2.5.7",
"@xmldom/xmldom": "0.7.13",
"allotment": "1.20.2",
"applicationinsights": "1.8.0", "applicationinsights": "1.8.0",
"bootstrap": "3.4.1", "bootstrap": "3.4.1",
"canvas": "file:./canvas", "canvas": "2.11.2",
"clean-webpack-plugin": "4.0.0", "clean-webpack-plugin": "4.0.0",
"clipboard-copy": "4.0.1", "clipboard-copy": "4.0.1",
"copy-webpack-plugin": "11.0.0", "copy-webpack-plugin": "11.0.0",
"crossroads": "0.12.2", "crossroads": "0.12.2",
"css-element-queries": "1.1.1", "css-element-queries": "1.1.1",
"d3": "6.1.1", "d3": "7.8.5",
"datatables.net-colreorder-dt": "1.7.0", "datatables.net-colreorder-dt": "1.7.0",
"datatables.net-dt": "1.13.8", "datatables.net-dt": "1.13.8",
"date-fns": "1.29.0", "date-fns": "1.29.0",
@@ -65,16 +67,18 @@
"eslint-plugin-react": "7.33.2", "eslint-plugin-react": "7.33.2",
"hasher": "1.2.0", "hasher": "1.2.0",
"html2canvas": "1.0.0-rc.5", "html2canvas": "1.0.0-rc.5",
"i18next": "19.8.4", "i18next": "23.11.5",
"i18next-browser-languagedetector": "6.0.1", "i18next-browser-languagedetector": "6.0.1",
"i18next-http-backend": "1.0.23", "i18next-http-backend": "1.0.23",
"iframe-resizer-react": "1.1.0", "iframe-resizer-react": "1.1.0",
"immer": "9.0.6",
"immutable": "4.0.0-rc.12", "immutable": "4.0.0-rc.12",
"is-ci": "2.0.0", "is-ci": "2.0.0",
"jquery": "3.7.1", "jquery": "3.7.1",
"jquery-typeahead": "2.11.1", "jquery-typeahead": "2.11.1",
"jquery-ui-dist": "1.13.2", "jquery-ui-dist": "1.13.2",
"knockout": "3.5.1", "knockout": "3.5.1",
"loader-utils": "2.0.3",
"mkdirp": "1.0.4", "mkdirp": "1.0.4",
"monaco-editor": "0.44.0", "monaco-editor": "0.44.0",
"ms": "2.1.3", "ms": "2.1.3",
@@ -89,27 +93,31 @@
"react-dnd-html5-backend": "14.0.0", "react-dnd-html5-backend": "14.0.0",
"react-dom": "16.14.0", "react-dom": "16.14.0",
"react-hotkeys": "2.0.0", "react-hotkeys": "2.0.0",
"react-i18next": "11.8.5", "react-i18next": "14.1.2",
"react-notification-system": "0.2.17", "react-notification-system": "0.2.17",
"react-redux": "7.1.3", "react-redux": "7.1.3",
"react-splitter-layout": "4.0.0", "react-splitter-layout": "4.0.0",
"react-string-format": "1.0.1", "react-string-format": "1.0.1",
"react-window": "1.8.10",
"react-youtube": "9.0.1", "react-youtube": "9.0.1",
"reflect-metadata": "0.1.13", "reflect-metadata": "0.1.13",
"rx-jupyter": "5.5.12", "rx-jupyter": "5.5.12",
"sanitize-html": "2.3.3", "sanitize-html": "2.3.3",
"shell-quote": "1.7.3",
"styled-components": "5.0.1", "styled-components": "5.0.1",
"swr": "0.4.0", "swr": "0.4.0",
"terser-webpack-plugin": "5.3.9", "terser-webpack-plugin": "5.3.9",
"underscore": "1.9.1", "tinykeys": "2.1.0",
"underscore": "1.12.1",
"utility-types": "3.10.0", "utility-types": "3.10.0",
"zustand": "3.5.0" "zustand": "3.5.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "7.9.0", "@babel/core": "7.24.7",
"@babel/preset-env": "7.9.0", "@babel/preset-env": "7.24.7",
"@babel/preset-react": "7.9.4", "@babel/preset-react": "7.24.7",
"@babel/preset-typescript": "7.9.0", "@babel/preset-typescript": "7.24.7",
"@playwright/test": "1.44.0",
"@testing-library/react": "11.2.3", "@testing-library/react": "11.2.3",
"@types/applicationinsights-js": "1.0.7", "@types/applicationinsights-js": "1.0.7",
"@types/codemirror": "0.0.56", "@types/codemirror": "0.0.56",
@@ -118,19 +126,20 @@
"@types/datatables.net": "1.10.28", "@types/datatables.net": "1.10.28",
"@types/datatables.net-colreorder": "1.4.5", "@types/datatables.net-colreorder": "1.4.5",
"@types/dom-to-image": "2.6.2", "@types/dom-to-image": "2.6.2",
"@types/enzyme": "3.10.7", "@types/enzyme": "3.10.12",
"@types/enzyme-adapter-react-16": "1.0.6", "@types/enzyme-adapter-react-16": "1.0.9",
"@types/hasher": "0.0.31", "@types/hasher": "0.0.31",
"@types/jest": "26.0.20", "@types/jest": "29.5.12",
"@types/jquery": "3.5.29", "@types/jquery": "3.5.29",
"@types/node": "12.11.1", "@types/node": "12.11.1",
"@types/post-robot": "10.0.1", "@types/post-robot": "10.0.1",
"@types/q": "1.5.1", "@types/q": "1.5.1",
"@types/react": "17.0.3", "@types/react": "17.0.44",
"@types/react-dom": "17.0.3", "@types/react-dom": "17.0.15",
"@types/react-notification-system": "0.2.39", "@types/react-notification-system": "0.2.39",
"@types/react-redux": "7.1.7", "@types/react-redux": "7.1.7",
"@types/react-splitter-layout": "3.0.1", "@types/react-splitter-layout": "3.0.1",
"@types/react-window": "1.8.8",
"@types/sanitize-html": "1.27.2", "@types/sanitize-html": "1.27.2",
"@types/sinon": "2.3.3", "@types/sinon": "2.3.3",
"@types/styled-components": "5.1.1", "@types/styled-components": "5.1.1",
@@ -139,56 +148,55 @@
"@typescript-eslint/eslint-plugin": "6.7.4", "@typescript-eslint/eslint-plugin": "6.7.4",
"@typescript-eslint/parser": "6.7.4", "@typescript-eslint/parser": "6.7.4",
"@webpack-cli/serve": "2.0.5", "@webpack-cli/serve": "2.0.5",
"babel-jest": "24.9.0", "babel-jest": "29.7.0",
"babel-loader": "8.1.0", "babel-loader": "8.1.0",
"buffer": "5.1.0", "buffer": "5.1.0",
"case-sensitive-paths-webpack-plugin": "2.4.0", "case-sensitive-paths-webpack-plugin": "2.4.0",
"create-file-webpack": "1.0.2", "create-file-webpack": "1.0.2",
"css-loader": "6.8.1", "css-loader": "6.8.1",
"enzyme": "3.11.0", "enzyme": "3.11.0",
"enzyme-adapter-react-16": "1.15.5", "enzyme-adapter-react-16": "1.15.8",
"enzyme-to-json": "3.6.1", "enzyme-to-json": "3.6.2",
"eslint": "8.50.0", "eslint": "8.50.0",
"eslint-cli": "1.1.1", "eslint-cli": "1.1.1",
"eslint-plugin-no-null": "1.0.2", "eslint-plugin-no-null": "1.0.2",
"eslint-plugin-prefer-arrow": "1.2.3", "eslint-plugin-prefer-arrow": "1.2.3",
"eslint-plugin-react-hooks": "4.6.0", "eslint-plugin-react-hooks": "4.6.0",
"expect-playwright": "0.3.3",
"fast-glob": "3.2.5", "fast-glob": "3.2.5",
"fs-extra": "7.0.0", "fs-extra": "7.0.0",
"html-inline-css-webpack-plugin": "1.11.2", "html-inline-css-webpack-plugin": "1.11.2",
"html-loader": "0.5.5", "html-loader": "5.0.0",
"html-loader-jest": "0.2.1",
"html-webpack-plugin": "5.5.3", "html-webpack-plugin": "5.5.3",
"jest": "26.6.3", "jest": "29.7.0",
"jest-canvas-mock": "2.3.1", "jest-canvas-mock": "2.5.2",
"jest-playwright-preset": "1.5.1", "jest-circus": "29.7.0",
"jest-html-loader": "1.0.0",
"jest-react-hooks-shallow": "1.5.1", "jest-react-hooks-shallow": "1.5.1",
"jest-trx-results-processor": "0.0.7", "jest-trx-results-processor": "3.0.2",
"jest-environment-jsdom": "29.7.0",
"less": "3.8.1", "less": "3.8.1",
"less-loader": "11.1.3", "less-loader": "11.1.3",
"less-vars-loader": "1.1.0", "less-vars-loader": "1.1.0",
"mini-css-extract-plugin": "2.1.0", "mini-css-extract-plugin": "2.1.0",
"monaco-editor-webpack-plugin": "7.1.0", "monaco-editor-webpack-plugin": "7.1.0",
"node-fetch": "2.6.1", "node-fetch": "2.6.7",
"playwright": "1.13.0",
"prettier": "3.0.3", "prettier": "3.0.3",
"process": "0.11.10", "process": "0.11.10",
"querystring-es3": "0.2.1", "querystring-es3": "0.2.1",
"raw-loader": "0.5.1", "raw-loader": "0.5.1",
"react-dev-utils": "11.0.4", "react-dev-utils": "12.0.1",
"rimraf": "3.0.0", "rimraf": "3.0.0",
"sinon": "3.2.1", "sinon": "3.2.1",
"style-loader": "0.23.0", "style-loader": "0.23.0",
"ts-loader": "9.2.4", "ts-loader": "9.2.4",
"typedoc": "0.21.5", "typedoc": "0.26.2",
"typescript": "4.3.5", "typescript": "4.9.5",
"url-loader": "4.1.1", "url-loader": "4.1.1",
"wait-on": "4.0.2", "wait-on": "4.0.2",
"webpack": "5.88.2", "webpack": "5.88.2",
"webpack-bundle-analyzer": "4.9.1", "webpack-bundle-analyzer": "4.9.1",
"webpack-cli": "5.1.4", "webpack-cli": "5.1.4",
"webpack-dev-server": "4.15.1" "webpack-dev-server": "4.15.2"
}, },
"scripts": { "scripts": {
"postinstall": "patch-package", "postinstall": "patch-package",
@@ -203,6 +211,7 @@
"test": "rimraf coverage && jest", "test": "rimraf coverage && jest",
"test:debug": "jest --runInBand", "test:debug": "jest --runInBand",
"test:e2e": "jest -c ./jest.config.playwright.js --detectOpenHandles", "test:e2e": "jest -c ./jest.config.playwright.js --detectOpenHandles",
"test:file": "jest --coverage=false",
"watch": "npm run start", "watch": "npm run start",
"wait-for-server": "wait-on -t 240000 -i 5000 -v https-get://0.0.0.0:1234/", "wait-for-server": "wait-on -t 240000 -i 5000 -v https-get://0.0.0.0:1234/",
"build:ase": "gulp build:ase", "build:ase": "gulp build:ase",
@@ -238,4 +247,4 @@
"printWidth": 120, "printWidth": 120,
"endOfLine": "auto" "endOfLine": "auto"
} }
} }

View File

@@ -0,0 +1,13 @@
diff --git a/node_modules/@phosphor/virtualdom/lib/index.d.ts b/node_modules/@phosphor/virtualdom/lib/index.d.ts
index 95682b9..73e2daa 100644
--- a/node_modules/@phosphor/virtualdom/lib/index.d.ts
+++ b/node_modules/@phosphor/virtualdom/lib/index.d.ts
@@ -58,7 +58,7 @@ export declare type ElementEventMap = {
ondrop: DragEvent;
ondurationchange: Event;
onemptied: Event;
- onended: MediaStreamErrorEvent;
+ onended: ErrorEvent;
onerror: ErrorEvent;
onfocus: FocusEvent;
oninput: Event;

60
playwright.config.ts Normal file
View File

@@ -0,0 +1,60 @@
import { defineConfig, devices } from "@playwright/test";
/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: "test",
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 3 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: process.env.CI ? "blob" : "html",
timeout: 10 * 60 * 1000,
use: {
actionTimeout: 5 * 60 * 1000,
trace: "off",
video: "off",
screenshot: "on",
testIdAttribute: "data-test",
contextOptions: {
ignoreHTTPSErrors: true,
},
},
expect: {
timeout: 5 * 60 * 1000,
},
projects: [
{
name: "chromium",
use: { ...devices["Desktop Chrome"] },
},
{
name: "firefox",
use: { ...devices["Desktop Firefox"] },
},
{
name: "webkit",
use: { ...devices["Desktop Safari"] },
},
/* Test against branded browsers. */
{
name: "Google Chrome",
use: { ...devices["Desktop Chrome"], channel: "chrome" }, // or 'chrome-beta'
},
{
name: "Microsoft Edge",
use: { ...devices["Desktop Edge"], channel: "msedge" }, // or 'msedge-dev'
},
],
webServer: {
command: "npm run start",
url: "https://127.0.0.1:1234/_ready",
timeout: 120 * 1000,
ignoreHTTPSErrors: true,
reuseExistingServer: !process.env.CI,
},
});

View File

@@ -1,55 +0,0 @@
import React, { FunctionComponent, MutableRefObject, useEffect, useRef } from "react";
import arrowLeftImg from "../../images/imgarrowlefticon.svg";
import { getApiShortDisplayName } from "../Utils/APITypeUtils";
import { NormalizedEventKey } from "./Constants";
export interface CollapsedResourceTreeProps {
toggleLeftPaneExpanded: () => void;
isLeftPaneExpanded: boolean;
}
export const CollapsedResourceTree: FunctionComponent<CollapsedResourceTreeProps> = ({
toggleLeftPaneExpanded,
isLeftPaneExpanded,
}: CollapsedResourceTreeProps): JSX.Element => {
const focusButton = useRef<HTMLLIElement>() as MutableRefObject<HTMLLIElement>;
useEffect(() => {
if (focusButton.current) {
focusButton.current.focus();
}
});
const onKeyPressToggleLeftPaneExpanded = (event: React.KeyboardEvent) => {
if (event.key === NormalizedEventKey.Space || event.key === NormalizedEventKey.Enter) {
toggleLeftPaneExpanded();
event.stopPropagation();
}
};
return (
<div id="mini" className={!isLeftPaneExpanded ? "mini toggle-mini" : "hiddenMain"}>
<div className="main-nav nav">
<ul className="nav">
<li
className="resourceTreeCollapse"
id="collapseToggleLeftPaneButton"
role="button"
tabIndex={0}
aria-label={getApiShortDisplayName() + `Expand tree`}
onClick={toggleLeftPaneExpanded}
onKeyPress={onKeyPressToggleLeftPaneExpanded}
ref={focusButton}
>
<span className="leftarrowCollapsed">
<img className="arrowCollapsed" src={arrowLeftImg} alt="Expand" />
</span>
<span className="collectionCollapsed">
<span>{getApiShortDisplayName()}</span>
</span>
</li>
</ul>
</div>
</div>
);
};

View File

@@ -88,6 +88,12 @@ export class CapabilityNames {
public static readonly EnableStorageAnalytics: string = "EnableStorageAnalytics"; public static readonly EnableStorageAnalytics: string = "EnableStorageAnalytics";
public static readonly EnableMongo: string = "EnableMongo"; public static readonly EnableMongo: string = "EnableMongo";
public static readonly EnableServerless: string = "EnableServerless"; public static readonly EnableServerless: string = "EnableServerless";
public static readonly EnableNoSQLVectorSearch: string = "EnableNoSQLVectorSearch";
}
export enum CapacityMode {
Provisioned = "Provisioned",
Serverless = "Serverless",
} }
// flight names returned from the portal are always lowercase // flight names returned from the portal are always lowercase
@@ -127,6 +133,7 @@ export enum MongoBackendEndpointType {
export class BackendApi { export class BackendApi {
public static readonly GenerateToken: string = "GenerateToken"; public static readonly GenerateToken: string = "GenerateToken";
public static readonly PortalSettings: string = "PortalSettings"; public static readonly PortalSettings: string = "PortalSettings";
public static readonly AccountRestrictions: string = "AccountRestrictions";
} }
export class PortalBackendEndpoints { export class PortalBackendEndpoints {
@@ -138,7 +145,7 @@ export class PortalBackendEndpoints {
} }
export class MongoProxyEndpoints { export class MongoProxyEndpoints {
public static readonly Development: string = "https://localhost:7238"; public static readonly Local: string = "https://localhost:7238";
public static readonly Mpac: string = "https://cdb-ms-mpac-mp.cosmos.azure.com"; public static readonly Mpac: string = "https://cdb-ms-mpac-mp.cosmos.azure.com";
public static readonly Prod: string = "https://cdb-ms-prod-mp.cosmos.azure.com"; public static readonly Prod: string = "https://cdb-ms-prod-mp.cosmos.azure.com";
public static readonly Fairfax: string = "https://cdb-ff-prod-mp.cosmos.azure.us"; public static readonly Fairfax: string = "https://cdb-ff-prod-mp.cosmos.azure.us";
@@ -190,6 +197,12 @@ export class Queries {
public static readonly DefaultMaxWaitTimeInSeconds = 30; public static readonly DefaultMaxWaitTimeInSeconds = 30;
} }
export class RBACOptions {
public static setAutomaticRBACOption: string = "Automatic";
public static setTrueRBACOption: string = "True";
public static setFalseRBACOption: string = "False";
}
export class SavedQueries { export class SavedQueries {
public static readonly CollectionName: string = "___Query"; public static readonly CollectionName: string = "___Query";
public static readonly DatabaseName: string = "___Cosmos"; public static readonly DatabaseName: string = "___Cosmos";
@@ -249,6 +262,7 @@ export class HttpHeaders {
public static partitionKey: string = "x-ms-documentdb-partitionkey"; public static partitionKey: string = "x-ms-documentdb-partitionkey";
public static migrateOfferToManualThroughput: string = "x-ms-cosmos-migrate-offer-to-manual-throughput"; public static migrateOfferToManualThroughput: string = "x-ms-cosmos-migrate-offer-to-manual-throughput";
public static migrateOfferToAutopilot: string = "x-ms-cosmos-migrate-offer-to-autopilot"; public static migrateOfferToAutopilot: string = "x-ms-cosmos-migrate-offer-to-autopilot";
public static xAPIKey: string = "X-API-Key";
} }
export class ContentType { export class ContentType {

View File

@@ -1,47 +1,6 @@
import { ResourceType } from "@azure/cosmos";
import { Platform, resetConfigContext, updateConfigContext } from "../ConfigContext"; import { Platform, resetConfigContext, updateConfigContext } from "../ConfigContext";
import { updateUserContext } from "../UserContext"; import { updateUserContext } from "../UserContext";
import { endpoint, getTokenFromAuthService, requestPlugin, tokenProvider } from "./CosmosClient"; import { endpoint, getTokenFromAuthService, requestPlugin } from "./CosmosClient";
describe("tokenProvider", () => {
const options = {
verb: "GET" as any,
path: "/",
resourceId: "",
resourceType: "dbs" as ResourceType,
headers: {},
getAuthorizationTokenUsingMasterKey: () => "",
};
beforeEach(() => {
updateConfigContext({
BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com",
});
window.fetch = jest.fn().mockImplementation(() => {
return {
json: () => "{}",
headers: new Map(),
};
});
});
afterEach(() => {
jest.restoreAllMocks();
});
it("calls the auth token service if no master key is set", async () => {
await tokenProvider(options);
expect((window.fetch as any).mock.calls.length).toBe(1);
});
it("does not call the auth service if a master key is set", async () => {
updateUserContext({
masterKey: "foo",
});
await tokenProvider(options);
expect((window.fetch as any).mock.calls.length).toBe(0);
});
});
describe("getTokenFromAuthService", () => { describe("getTokenFromAuthService", () => {
beforeEach(() => { beforeEach(() => {

View File

@@ -1,6 +1,6 @@
import * as Cosmos from "@azure/cosmos"; import * as Cosmos from "@azure/cosmos";
import { getAuthorizationTokenUsingResourceTokens } from "Common/getAuthorizationTokenUsingResourceTokens"; import { getAuthorizationTokenUsingResourceTokens } from "Common/getAuthorizationTokenUsingResourceTokens";
import { AuthorizationToken } from "Contracts/MessageTypes"; import { AuthorizationToken } from "Contracts/FabricMessageTypes";
import { checkDatabaseResourceTokensValidity } from "Platform/Fabric/FabricUtil"; import { checkDatabaseResourceTokensValidity } from "Platform/Fabric/FabricUtil";
import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility"; import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
import { AuthType } from "../AuthType"; import { AuthType } from "../AuthType";
@@ -11,13 +11,25 @@ import { logConsoleError } from "../Utils/NotificationConsoleUtils";
import * as PriorityBasedExecutionUtils from "../Utils/PriorityBasedExecutionUtils"; import * as PriorityBasedExecutionUtils from "../Utils/PriorityBasedExecutionUtils";
import { EmulatorMasterKey, HttpHeaders } from "./Constants"; import { EmulatorMasterKey, HttpHeaders } from "./Constants";
import { getErrorMessage } from "./ErrorHandlingUtils"; import { getErrorMessage } from "./ErrorHandlingUtils";
import * as Logger from "../Common/Logger";
const _global = typeof self === "undefined" ? window : self; const _global = typeof self === "undefined" ? window : self;
export const tokenProvider = async (requestInfo: Cosmos.RequestInfo) => { export const tokenProvider = async (requestInfo: Cosmos.RequestInfo) => {
const { verb, resourceId, resourceType, headers } = requestInfo; const { verb, resourceId, resourceType, headers } = requestInfo;
if (userContext.features.enableAadDataPlane && userContext.aadToken) { const dataPlaneRBACOptionEnabled = userContext.dataPlaneRbacEnabled && userContext.apiType === "SQL";
if (userContext.features.enableAadDataPlane || dataPlaneRBACOptionEnabled) {
Logger.logInfo(
`AAD Data Plane Feature flag set to ${userContext.features.enableAadDataPlane} for account with disable local auth ${userContext.databaseAccount.properties.disableLocalAuth} `,
"Explorer/tokenProvider",
);
if (!userContext.aadToken) {
logConsoleError(
`AAD token does not exist. Please click on "Login for Entra ID" button prior to performing Entra ID RBAC operations`,
);
return null;
}
const AUTH_PREFIX = `type=aad&ver=1.0&sig=`; const AUTH_PREFIX = `type=aad&ver=1.0&sig=`;
const authorizationToken = `${AUTH_PREFIX}${userContext.aadToken}`; const authorizationToken = `${AUTH_PREFIX}${userContext.aadToken}`;
return authorizationToken; return authorizationToken;
@@ -59,7 +71,7 @@ export const tokenProvider = async (requestInfo: Cosmos.RequestInfo) => {
/* ************** TODO: Uncomment this code if we need to support these operations ************** /* ************** TODO: Uncomment this code if we need to support these operations **************
// User master tokens // User master tokens
const authorizationToken = await sendCachedDataMessage<AuthorizationToken>( const authorizationToken = await sendCachedDataMessage<AuthorizationToken>(
MessageTypes.GetAuthorizationToken, FabricMessageTypes.GetAuthorizationToken,
[requestInfo], [requestInfo],
userContext.fabricContext.connectionId, userContext.fabricContext.connectionId,
); );
@@ -71,8 +83,15 @@ export const tokenProvider = async (requestInfo: Cosmos.RequestInfo) => {
} }
if (userContext.masterKey) { if (userContext.masterKey) {
Logger.logInfo(`Master Key exists`, "Explorer/tokenProvider");
// TODO This SDK method mutates the headers object. Find a better one or fix the SDK. // TODO This SDK method mutates the headers object. Find a better one or fix the SDK.
await Cosmos.setAuthorizationTokenHeaderUsingMasterKey(verb, resourceId, resourceType, headers, EmulatorMasterKey); await Cosmos.setAuthorizationTokenHeaderUsingMasterKey(
verb,
resourceId,
resourceType,
headers,
userContext.masterKey,
);
return decodeURIComponent(headers.authorization); return decodeURIComponent(headers.authorization);
} }
@@ -137,8 +156,11 @@ enum SDKSupportedCapabilities {
let _client: Cosmos.CosmosClient; let _client: Cosmos.CosmosClient;
export function client(): Cosmos.CosmosClient { export function client(): Cosmos.CosmosClient {
if (_client) return _client; if (_client) {
if (!userContext.hasDataPlaneRbacSettingChanged) {
return _client;
}
}
let _defaultHeaders: Cosmos.CosmosHeaders = {}; let _defaultHeaders: Cosmos.CosmosHeaders = {};
_defaultHeaders["x-ms-cosmos-sdk-supportedcapabilities"] = _defaultHeaders["x-ms-cosmos-sdk-supportedcapabilities"] =
SDKSupportedCapabilities.None | SDKSupportedCapabilities.PartitionMerge; SDKSupportedCapabilities.None | SDKSupportedCapabilities.PartitionMerge;
@@ -157,7 +179,7 @@ export function client(): Cosmos.CosmosClient {
const options: Cosmos.CosmosClientOptions = { const options: Cosmos.CosmosClientOptions = {
endpoint: endpoint() || "https://cosmos.azure.com", // CosmosClient gets upset if we pass a bad URL. This should never actually get called endpoint: endpoint() || "https://cosmos.azure.com", // CosmosClient gets upset if we pass a bad URL. This should never actually get called
key: userContext.masterKey, key: userContext.dataPlaneRbacEnabled ? "" : userContext.masterKey,
tokenProvider, tokenProvider,
userAgentSuffix: "Azure Portal", userAgentSuffix: "Azure Portal",
defaultHeaders: _defaultHeaders, defaultHeaders: _defaultHeaders,

View File

@@ -1,9 +1,9 @@
import { userContext } from "../UserContext"; import { userContext } from "../UserContext";
export const getEntityName = (): string => { export const getEntityName = (multiple?: boolean): string => {
if (userContext.apiType === "Mongo") { if (userContext.apiType === "Mongo") {
return "document"; return multiple ? "documents" : "document";
} }
return "item"; return multiple ? "items" : "item";
}; };

View File

@@ -1,5 +1,7 @@
import { FabricMessageTypes } from "Contracts/FabricMessageTypes";
import Q from "q"; import Q from "q";
import * as _ from "underscore"; import * as _ from "underscore";
import * as Logger from "../Common/Logger";
import { MessageTypes } from "../Contracts/ExplorerContracts"; import { MessageTypes } from "../Contracts/ExplorerContracts";
import { getDataExplorerWindow } from "../Utils/WindowUtils"; import { getDataExplorerWindow } from "../Utils/WindowUtils";
import * as Constants from "./Constants"; import * as Constants from "./Constants";
@@ -36,7 +38,7 @@ export function handleCachedDataMessage(message: any): void {
* @returns * @returns
*/ */
export function sendCachedDataMessage<TResponseDataModel>( export function sendCachedDataMessage<TResponseDataModel>(
messageType: MessageTypes, messageType: MessageTypes | FabricMessageTypes,
params: Object[], params: Object[],
scope?: string, scope?: string,
timeoutInMs?: number, timeoutInMs?: number,
@@ -95,10 +97,18 @@ const _sendMessage = (message: any): void => {
const portalChildWindow = getDataExplorerWindow(window) || window; const portalChildWindow = getDataExplorerWindow(window) || window;
if (portalChildWindow === window) { if (portalChildWindow === window) {
// Current window is a child of portal, send message to portal window // Current window is a child of portal, send message to portal window
portalChildWindow.parent.postMessage(message, portalChildWindow.document.referrer || "*"); if (portalChildWindow.document.referrer) {
portalChildWindow.parent.postMessage(message, portalChildWindow.document.referrer);
} else {
Logger.logError("Iframe failed to send message to portal", "MessageHandler");
}
} else { } else {
// Current window is not a child of portal, send message to the child window instead (which is data explorer) // Current window is not a child of portal, send message to the child window instead (which is data explorer)
portalChildWindow.postMessage(message, portalChildWindow.location.origin || "*"); if (portalChildWindow.location.origin) {
portalChildWindow.postMessage(message, portalChildWindow.location.origin);
} else {
Logger.logError("Iframe failed to send message to data explorer", "MessageHandler");
}
} }
} }
}; };

View File

@@ -672,6 +672,28 @@ export function getEndpoint(endpoint: string): string {
return url; return url;
} }
export function useMongoProxyEndpoint(api: string): boolean {
const activeMongoProxyEndpoints: string[] = [
MongoProxyEndpoints.Local,
MongoProxyEndpoints.Mpac,
MongoProxyEndpoints.Prod,
// MongoProxyEndpoints.Fairfax,
];
let canAccessMongoProxy: boolean = userContext.databaseAccount.properties.publicNetworkAccess === "Enabled";
if (
configContext.MONGO_PROXY_ENDPOINT !== MongoProxyEndpoints.Local &&
userContext.databaseAccount.properties.ipRules?.length > 0
) {
canAccessMongoProxy = canAccessMongoProxy && configContext.MONGO_PROXY_OUTBOUND_IPS_ALLOWLISTED;
}
return (
canAccessMongoProxy &&
configContext.NEW_MONGO_APIS?.includes(api) &&
activeMongoProxyEndpoints.includes(configContext.MONGO_PROXY_ENDPOINT)
);
}
// TODO: This function throws most of the time except on Forbidden which is a bit strange // TODO: This function throws most of the time except on Forbidden which is a bit strange
// It causes problems for TypeScript understanding the types // It causes problems for TypeScript understanding the types
async function errorHandling(response: Response, action: string, params: unknown): Promise<void> { async function errorHandling(response: Response, action: string, params: unknown): Promise<void> {
@@ -688,24 +710,3 @@ async function errorHandling(response: Response, action: string, params: unknown
export function getARMCreateCollectionEndpoint(params: DataModels.MongoParameters): string { export function getARMCreateCollectionEndpoint(params: DataModels.MongoParameters): string {
return `subscriptions/${params.sid}/resourceGroups/${params.rg}/providers/Microsoft.DocumentDB/databaseAccounts/${userContext.databaseAccount.name}/mongodbDatabases/${params.db}/collections/${params.coll}`; return `subscriptions/${params.sid}/resourceGroups/${params.rg}/providers/Microsoft.DocumentDB/databaseAccounts/${userContext.databaseAccount.name}/mongodbDatabases/${params.db}/collections/${params.coll}`;
} }
function useMongoProxyEndpoint(api: string): boolean {
const activeMongoProxyEndpoints: string[] = [
MongoProxyEndpoints.Development,
MongoProxyEndpoints.Mpac,
MongoProxyEndpoints.Prod,
];
let canAccessMongoProxy: boolean = userContext.databaseAccount.properties.publicNetworkAccess === "Enabled";
if (
configContext.MONGO_PROXY_ENDPOINT !== MongoProxyEndpoints.Development &&
userContext.databaseAccount.properties.ipRules?.length > 0
) {
canAccessMongoProxy = canAccessMongoProxy && configContext.MONGO_PROXY_OUTBOUND_IPS_ALLOWLISTED;
}
return (
canAccessMongoProxy &&
configContext.NEW_MONGO_APIS?.includes(api) &&
activeMongoProxyEndpoints.includes(configContext.MONGO_PROXY_ENDPOINT)
);
}

View File

@@ -1,39 +0,0 @@
import { configContext, Platform } from "../ConfigContext";
import * as DataModels from "../Contracts/DataModels";
import * as ViewModels from "../Contracts/ViewModels";
import { userContext } from "../UserContext";
import { getAuthorizationHeader } from "../Utils/AuthorizationUtils";
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 || configContext.platform === Platform.Hosted) {
return [];
}
const { databaseAccount, resourceGroup, subscriptionId } = userContext;
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

@@ -3,8 +3,7 @@ import * as _ from "underscore";
import * as DataModels from "../Contracts/DataModels"; import * as DataModels from "../Contracts/DataModels";
import * as ViewModels from "../Contracts/ViewModels"; import * as ViewModels from "../Contracts/ViewModels";
import Explorer from "../Explorer/Explorer"; import Explorer from "../Explorer/Explorer";
import DocumentsTab from "../Explorer/Tabs/DocumentsTab"; import DocumentId, { IDocumentIdContainer } from "../Explorer/Tree/DocumentId";
import DocumentId from "../Explorer/Tree/DocumentId";
import { useDatabases } from "../Explorer/useDatabases"; import { useDatabases } from "../Explorer/useDatabases";
import { userContext } from "../UserContext"; import { userContext } from "../UserContext";
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils"; import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
@@ -162,10 +161,10 @@ export class QueriesClient {
{ {
partitionKey: QueriesClient.PartitionKey, partitionKey: QueriesClient.PartitionKey,
partitionKeyProperties: ["id"], partitionKeyProperties: ["id"],
} as DocumentsTab, } as IDocumentIdContainer,
query, query,
[query.queryName], [query.queryName],
); // TODO: Remove DocumentId's dependency on DocumentsTab );
const options: any = { partitionKey: query.resourceId }; const options: any = { partitionKey: query.resourceId };
return deleteDocument(queriesCollection, documentId) return deleteDocument(queriesCollection, documentId)
.then( .then(

View File

@@ -1,90 +0,0 @@
import { ResourceTree } from "Explorer/Tree/ResourceTree";
import React, { FunctionComponent, MutableRefObject, useEffect, useRef } from "react";
import arrowLeftImg from "../../images/imgarrowlefticon.svg";
import refreshImg from "../../images/refresh-cosmos.svg";
import { AuthType } from "../AuthType";
import Explorer from "../Explorer/Explorer";
import { ResourceTokenTree } from "../Explorer/Tree/ResourceTokenTree";
import { ResourceTree2 } from "../Explorer/Tree2/ResourceTree";
import { userContext } from "../UserContext";
import { getApiShortDisplayName } from "../Utils/APITypeUtils";
import { Platform, configContext } from "./../ConfigContext";
import { NormalizedEventKey } from "./Constants";
export interface ResourceTreeContainerProps {
toggleLeftPaneExpanded: () => void;
isLeftPaneExpanded: boolean;
container: Explorer;
}
export const ResourceTreeContainer: FunctionComponent<ResourceTreeContainerProps> = ({
toggleLeftPaneExpanded,
isLeftPaneExpanded,
container,
}: ResourceTreeContainerProps): JSX.Element => {
const focusButton = useRef<HTMLLIElement>() as MutableRefObject<HTMLLIElement>;
useEffect(() => {
if (isLeftPaneExpanded) {
if (focusButton.current) {
focusButton.current.focus();
}
}
});
const onKeyPressToggleLeftPaneExpanded = (event: React.KeyboardEvent) => {
if (event.key === NormalizedEventKey.Space || event.key === NormalizedEventKey.Enter) {
toggleLeftPaneExpanded();
event.stopPropagation();
}
};
return (
<div id="main" className={isLeftPaneExpanded ? "main" : "hiddenMain"}>
{/* Collections Window - - Start */}
<div id="mainslide" className="flexContainer">
{/* Collections Window Title/Command Bar - Start */}
<div className="collectiontitle">
<div className="coltitle">
<span className="titlepadcol">{getApiShortDisplayName()}</span>
<div className="float-right">
<span
className="padimgcolrefresh"
data-test="refreshTree"
role="button"
data-bind="click: onRefreshResourcesClick, clickBubble: false, event: { keypress: onRefreshDatabasesKeyPress }"
tabIndex={0}
aria-label={getApiShortDisplayName() + `Refresh tree`}
title="Refresh tree"
>
<img className="refreshcol" src={refreshImg} alt="Refresh Tree" />
</span>
<span
className="padimgcolrefresh1"
id="expandToggleLeftPaneButton"
role="button"
onClick={toggleLeftPaneExpanded}
onKeyPress={onKeyPressToggleLeftPaneExpanded}
tabIndex={0}
aria-label={getApiShortDisplayName() + `Collapse Tree`}
title="Collapse Tree"
ref={focusButton}
>
<img className="refreshcol1" src={arrowLeftImg} alt="Hide" />
</span>
</div>
</div>
</div>
{userContext.authType === AuthType.ResourceToken ? (
<ResourceTokenTree />
) : userContext.features.enableKoResourceTree ? (
<div style={{ overflowY: "auto" }} data-bind="react:resourceTree" />
) : configContext.platform === Platform.Fabric ? (
<ResourceTree2 container={container} />
) : (
<ResourceTree container={container} />
)}
</div>
{/* Collections Window - End */}
</div>
);
};

View File

@@ -142,7 +142,7 @@ export const TableEntity: FunctionComponent<TableEntityProps> = ({
<Image <Image
{...imageProps} {...imageProps}
src={EditIcon} src={EditIcon}
alt="editEntity" alt={`Edit ${entityProperty} entity`}
onClick={onEditEntity} onClick={onEditEntity}
tabIndex={0} tabIndex={0}
onKeyPress={handleKeyPress} onKeyPress={handleKeyPress}
@@ -156,7 +156,7 @@ export const TableEntity: FunctionComponent<TableEntityProps> = ({
<Image <Image
{...imageProps} {...imageProps}
src={DeleteIcon} src={DeleteIcon}
alt="delete entity" alt={`Delete ${entityProperty} entity`}
id="deleteEntity" id="deleteEntity"
onClick={onDeleteEntity} onClick={onDeleteEntity}
tabIndex={0} tabIndex={0}

View File

@@ -1,9 +1,9 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`requestPlugin Emulator builds a url for emulator proxy via webpack 1`] = ` exports[`requestPlugin Emulator builds a url for emulator proxy via webpack 1`] = `
Object { {
"endpoint": "http://localhost/proxy", "endpoint": "http://localhost/proxy",
"headers": Object { "headers": {
"x-ms-proxy-target": "http://localhost", "x-ms-proxy-target": "http://localhost",
}, },
"path": "/dbs/foo", "path": "/dbs/foo",
@@ -11,9 +11,9 @@ Object {
`; `;
exports[`requestPlugin Hosted builds a proxy URL in development 1`] = ` exports[`requestPlugin Hosted builds a proxy URL in development 1`] = `
Object { {
"endpoint": "http://localhost/proxy", "endpoint": "http://localhost/proxy",
"headers": Object { "headers": {
"x-ms-proxy-target": "baz", "x-ms-proxy-target": "baz",
}, },
"path": "/dbs/foo", "path": "/dbs/foo",

View File

@@ -1,12 +1,12 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`nextPage returns results for the next page 1`] = ` exports[`nextPage returns results for the next page 1`] = `
Object { {
"activityId": "foo", "activityId": "foo",
"documents": Array [], "documents": [],
"firstItemIndex": 11, "firstItemIndex": 11,
"hasMoreResults": false, "hasMoreResults": false,
"headers": Object {}, "headers": {},
"itemCount": 0, "itemCount": 0,
"lastItemIndex": 10, "lastItemIndex": 10,
"requestCharge": 1, "requestCharge": 1,

View File

@@ -1,7 +1,8 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`getCommonQueryOptions builds the correct default options objects 1`] = ` exports[`getCommonQueryOptions builds the correct default options objects 1`] = `
Object { {
"disableNonStreamingOrderByQuery": true,
"enableScanInQuery": true, "enableScanInQuery": true,
"forceQueryPlan": true, "forceQueryPlan": true,
"maxDegreeOfParallelism": 0, "maxDegreeOfParallelism": 0,
@@ -11,7 +12,8 @@ Object {
`; `;
exports[`getCommonQueryOptions reads from localStorage 1`] = ` exports[`getCommonQueryOptions reads from localStorage 1`] = `
Object { {
"disableNonStreamingOrderByQuery": true,
"enableScanInQuery": true, "enableScanInQuery": true,
"forceQueryPlan": true, "forceQueryPlan": true,
"maxDegreeOfParallelism": 17, "maxDegreeOfParallelism": 17,

View File

@@ -6,13 +6,13 @@ import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstan
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
import { getCollectionName } from "../../Utils/APITypeUtils"; import { getCollectionName } from "../../Utils/APITypeUtils";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { createUpdateCassandraTable } from "../../Utils/arm/generatedClients/cosmos/cassandraResources"; import { createUpdateCassandraTable } from "../../Utils/arm/generatedClients/cosmos/cassandraResources";
import { createUpdateGremlinGraph } from "../../Utils/arm/generatedClients/cosmos/gremlinResources"; import { createUpdateGremlinGraph } from "../../Utils/arm/generatedClients/cosmos/gremlinResources";
import { createUpdateMongoDBCollection } from "../../Utils/arm/generatedClients/cosmos/mongoDBResources"; import { createUpdateMongoDBCollection } from "../../Utils/arm/generatedClients/cosmos/mongoDBResources";
import { createUpdateSqlContainer } from "../../Utils/arm/generatedClients/cosmos/sqlResources"; import { createUpdateSqlContainer } from "../../Utils/arm/generatedClients/cosmos/sqlResources";
import { createUpdateTable } from "../../Utils/arm/generatedClients/cosmos/tableResources"; import { createUpdateTable } from "../../Utils/arm/generatedClients/cosmos/tableResources";
import * as ARMTypes from "../../Utils/arm/generatedClients/cosmos/types"; import * as ARMTypes from "../../Utils/arm/generatedClients/cosmos/types";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils"; import { handleError } from "../ErrorHandlingUtils";
import { createMongoCollectionWithProxy } from "../MongoProxyClient"; import { createMongoCollectionWithProxy } from "../MongoProxyClient";
@@ -96,6 +96,9 @@ const createSqlContainer = async (params: DataModels.CreateCollectionParams): Pr
if (params.uniqueKeyPolicy) { if (params.uniqueKeyPolicy) {
resource.uniqueKeyPolicy = params.uniqueKeyPolicy; resource.uniqueKeyPolicy = params.uniqueKeyPolicy;
} }
if (params.vectorEmbeddingPolicy) {
resource.vectorEmbeddingPolicy = params.vectorEmbeddingPolicy;
}
const rpPayload: ARMTypes.SqlDatabaseCreateUpdateParameters = { const rpPayload: ARMTypes.SqlDatabaseCreateUpdateParameters = {
properties: { properties: {
@@ -266,6 +269,7 @@ const createCollectionWithSDK = async (params: DataModels.CreateCollectionParams
indexingPolicy: params.indexingPolicy || undefined, indexingPolicy: params.indexingPolicy || undefined,
uniqueKeyPolicy: params.uniqueKeyPolicy || undefined, uniqueKeyPolicy: params.uniqueKeyPolicy || undefined,
analyticalStorageTtl: params.analyticalStorageTtl, analyticalStorageTtl: params.analyticalStorageTtl,
vectorEmbeddingPolicy: params.vectorEmbeddingPolicy,
} as ContainerRequest; // TODO: remove cast when https://github.com/Azure/azure-cosmos-js/issues/423 is fixed } as ContainerRequest; // TODO: remove cast when https://github.com/Azure/azure-cosmos-js/issues/423 is fixed
const collectionOptions: RequestOptions = {}; const collectionOptions: RequestOptions = {};
const createDatabaseBody: DatabaseRequest = { id: params.databaseId }; const createDatabaseBody: DatabaseRequest = { id: params.databaseId };

View File

@@ -4,6 +4,7 @@ import * as DataModels from "../../Contracts/DataModels";
import { useDatabases } from "../../Explorer/useDatabases"; import { useDatabases } from "../../Explorer/useDatabases";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
import { getDatabaseName } from "../../Utils/APITypeUtils"; import { getDatabaseName } from "../../Utils/APITypeUtils";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { createUpdateCassandraKeyspace } from "../../Utils/arm/generatedClients/cosmos/cassandraResources"; import { createUpdateCassandraKeyspace } from "../../Utils/arm/generatedClients/cosmos/cassandraResources";
import { createUpdateGremlinDatabase } from "../../Utils/arm/generatedClients/cosmos/gremlinResources"; import { createUpdateGremlinDatabase } from "../../Utils/arm/generatedClients/cosmos/gremlinResources";
import { createUpdateMongoDBDatabase } from "../../Utils/arm/generatedClients/cosmos/mongoDBResources"; import { createUpdateMongoDBDatabase } from "../../Utils/arm/generatedClients/cosmos/mongoDBResources";
@@ -15,7 +16,6 @@ import {
MongoDBDatabaseCreateUpdateParameters, MongoDBDatabaseCreateUpdateParameters,
SqlDatabaseCreateUpdateParameters, SqlDatabaseCreateUpdateParameters,
} from "../../Utils/arm/generatedClients/cosmos/types"; } from "../../Utils/arm/generatedClients/cosmos/types";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils"; import { handleError } from "../ErrorHandlingUtils";
@@ -152,8 +152,18 @@ async function createDatabaseWithSDK(params: DataModels.CreateDatabaseParams): P
createBody.throughput = params.offerThroughput; createBody.throughput = params.offerThroughput;
} }
} }
let response: DatabaseResponse;
const response: DatabaseResponse = await client().databases.create(createBody); try {
response = await client().databases.create(createBody);
} catch (error) {
if (error.message.includes("Shared throughput database creation is not supported for serverless accounts")) {
createBody.maxThroughput = undefined;
createBody.throughput = undefined;
response = await client().databases.create(createBody);
} else {
throw error;
}
}
return response.resource; return response.resource;
} }

View File

@@ -1,3 +1,4 @@
import { BulkOperationType, OperationInput } from "@azure/cosmos";
import { CollectionBase } from "../../Contracts/ViewModels"; import { CollectionBase } from "../../Contracts/ViewModels";
import DocumentId from "../../Explorer/Tree/DocumentId"; import DocumentId from "../../Explorer/Tree/DocumentId";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils"; import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
@@ -24,3 +25,58 @@ export const deleteDocument = async (collection: CollectionBase, documentId: Doc
clearMessage(); clearMessage();
} }
}; };
/**
* Bulk delete documents
* @param collection
* @param documentId
* @returns array of ids that were successfully deleted
*/
export const deleteDocuments = async (collection: CollectionBase, documentIds: DocumentId[]): Promise<DocumentId[]> => {
const nbDocuments = documentIds.length;
const clearMessage = logConsoleProgress(`Deleting ${documentIds.length} ${getEntityName(true)}`);
try {
const v2Container = await client().database(collection.databaseId).container(collection.id());
// Bulk can only delete 100 documents at a time
const BULK_DELETE_LIMIT = 100;
const promiseArray = [];
while (documentIds.length > 0) {
const documentIdsChunk = documentIds.splice(0, BULK_DELETE_LIMIT);
const operations: OperationInput[] = documentIdsChunk.map((documentId) => ({
id: documentId.id(),
// bulk delete: if not partition key is specified, do not pass empty array, but undefined
partitionKey:
documentId.partitionKeyValue &&
Array.isArray(documentId.partitionKeyValue) &&
documentId.partitionKeyValue.length === 0
? undefined
: documentId.partitionKeyValue,
operationType: BulkOperationType.Delete,
}));
const promise = v2Container.items.bulk(operations).then((bulkResult) => {
return documentIdsChunk.filter((_, index) => bulkResult[index].statusCode === 204);
});
promiseArray.push(promise);
}
const allResult = await Promise.all(promiseArray);
const flatAllResult = Array.prototype.concat.apply([], allResult);
logConsoleInfo(
`Successfully deleted ${getEntityName(flatAllResult.length > 1)}: ${flatAllResult.length} out of ${nbDocuments}`,
);
// TODO: handle case result.length != nbDocuments
return flatAllResult;
} catch (error) {
handleError(
error,
"DeleteDocuments",
`Error while deleting ${documentIds.length} ${getEntityName(documentIds.length > 1)}`,
);
throw error;
} finally {
clearMessage();
}
};

View File

@@ -1,4 +1,5 @@
import { FeedOptions, ItemDefinition, QueryIterator, Resource } from "@azure/cosmos"; import { FeedOptions, ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
import { isVectorSearchEnabled } from "Utils/CapabilityUtils";
import { LocalStorageUtility, StorageKey } from "../../Shared/StorageUtility"; import { LocalStorageUtility, StorageKey } from "../../Shared/StorageUtility";
import { Queries } from "../Constants"; import { Queries } from "../Constants";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
@@ -26,5 +27,6 @@ export const getCommonQueryOptions = (options: FeedOptions): FeedOptions => {
(storedItemPerPageSetting !== undefined && storedItemPerPageSetting) || (storedItemPerPageSetting !== undefined && storedItemPerPageSetting) ||
Queries.itemsPerPage; Queries.itemsPerPage;
options.maxDegreeOfParallelism = LocalStorageUtility.getEntryNumber(StorageKey.MaxDegreeOfParellism); options.maxDegreeOfParallelism = LocalStorageUtility.getEntryNumber(StorageKey.MaxDegreeOfParellism);
options.disableNonStreamingOrderByQuery = !isVectorSearchEnabled();
return options; return options;
}; };

View File

@@ -2,7 +2,6 @@ import { CosmosClient } from "@azure/cosmos";
import { sampleDataClient } from "Common/SampleDataClient"; import { sampleDataClient } from "Common/SampleDataClient";
import { userContext } from "UserContext"; import { userContext } from "UserContext";
import * as DataModels from "../../Contracts/DataModels"; import * as DataModels from "../../Contracts/DataModels";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient"; import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils"; import { handleError } from "../ErrorHandlingUtils";
@@ -31,7 +30,6 @@ export async function readCollectionInternal(
collectionId: string, collectionId: string,
): Promise<DataModels.Collection> { ): Promise<DataModels.Collection> {
let collection: DataModels.Collection; let collection: DataModels.Collection;
const clearMessage = logConsoleProgress(`Querying container ${collectionId}`);
try { try {
const response = await cosmosClient.database(databaseId).container(collectionId).read(); const response = await cosmosClient.database(databaseId).container(collectionId).read();
collection = response.resource as DataModels.Collection; collection = response.resource as DataModels.Collection;
@@ -39,6 +37,5 @@ export async function readCollectionInternal(
handleError(error, "ReadCollection", `Error while querying container ${collectionId}`); handleError(error, "ReadCollection", `Error while querying container ${collectionId}`);
throw error; throw error;
} }
clearMessage();
return collection; return collection;
} }

View File

@@ -42,6 +42,10 @@ export interface ConfigContext {
ARM_API_VERSION: string; ARM_API_VERSION: string;
GRAPH_ENDPOINT: string; GRAPH_ENDPOINT: string;
GRAPH_API_VERSION: string; GRAPH_API_VERSION: string;
// This is the endpoint to get offering Ids to be used to fetch prices. Refer to this doc: https://learn.microsoft.com/en-us/rest/api/marketplacecatalog/dataplane/skus/list?view=rest-marketplacecatalog-dataplane-2023-05-01-preview&tabs=HTTP
CATALOG_ENDPOINT: string;
CATALOG_API_VERSION: string;
CATALOG_API_KEY: string;
ARCADIA_ENDPOINT: string; ARCADIA_ENDPOINT: string;
ARCADIA_LIVY_ENDPOINT_DNS_ZONE: string; ARCADIA_LIVY_ENDPOINT_DNS_ZONE: string;
BACKEND_ENDPOINT?: string; BACKEND_ENDPOINT?: string;
@@ -83,6 +87,7 @@ let configContext: Readonly<ConfigContext> = {
`^https:\\/\\/.*\\.analysis-df\\.net$`, `^https:\\/\\/.*\\.analysis-df\\.net$`,
`^https:\\/\\/.*\\.analysis-df\\.windows\\.net$`, `^https:\\/\\/.*\\.analysis-df\\.windows\\.net$`,
`^https:\\/\\/.*\\.azure-test\\.net$`, `^https:\\/\\/.*\\.azure-test\\.net$`,
`^https:\\/\\/cosmos-explorer-preview\\.azurewebsites\\.net`,
], // Webpack injects this at build time ], // Webpack injects this at build time
gitSha: process.env.GIT_SHA, gitSha: process.env.GIT_SHA,
hostedExplorerURL: "https://cosmos.azure.com/", hostedExplorerURL: "https://cosmos.azure.com/",
@@ -92,6 +97,9 @@ let configContext: Readonly<ConfigContext> = {
ARM_API_VERSION: "2016-06-01", ARM_API_VERSION: "2016-06-01",
GRAPH_ENDPOINT: "https://graph.microsoft.com", GRAPH_ENDPOINT: "https://graph.microsoft.com",
GRAPH_API_VERSION: "1.6", GRAPH_API_VERSION: "1.6",
CATALOG_ENDPOINT: "https://catalogapi.azure.com/",
CATALOG_API_VERSION: "2023-05-01-preview",
CATALOG_API_KEY: "",
ARCADIA_ENDPOINT: "https://workspaceartifacts.projectarcadia.net", ARCADIA_ENDPOINT: "https://workspaceartifacts.projectarcadia.net",
ARCADIA_LIVY_ENDPOINT_DNS_ZONE: "dev.azuresynapse.net", ARCADIA_LIVY_ENDPOINT_DNS_ZONE: "dev.azuresynapse.net",
GITHUB_CLIENT_ID: "6cb2f63cf6f7b5cbdeca", // Registered OAuth app: https://github.com/organizations/AzureCosmosDBNotebooks/settings/applications/1189306 GITHUB_CLIENT_ID: "6cb2f63cf6f7b5cbdeca", // Registered OAuth app: https://github.com/organizations/AzureCosmosDBNotebooks/settings/applications/1189306
@@ -108,6 +116,7 @@ let configContext: Readonly<ConfigContext> = {
"updateDocument", "updateDocument",
"deleteDocument", "deleteDocument",
"createCollectionWithProxy", "createCollectionWithProxy",
"legacyMongoShell",
], ],
MONGO_PROXY_OUTBOUND_IPS_ALLOWLISTED: false, MONGO_PROXY_OUTBOUND_IPS_ALLOWLISTED: false,
CASSANDRA_PROXY_ENDPOINT: CassandraProxyEndpoints.Prod, CASSANDRA_PROXY_ENDPOINT: CassandraProxyEndpoints.Prod,
@@ -190,6 +199,9 @@ if (process.env.NODE_ENV === "development") {
updateConfigContext({ updateConfigContext({
PROXY_PATH: "/proxy", PROXY_PATH: "/proxy",
EMULATOR_ENDPOINT: "https://localhost:8081", EMULATOR_ENDPOINT: "https://localhost:8081",
PORTAL_BACKEND_ENDPOINT: PortalBackendEndpoints.Mpac,
MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Mpac,
CASSANDRA_PROXY_ENDPOINT: CassandraProxyEndpoints.Mpac,
}); });
} }

View File

@@ -1,7 +1,8 @@
export interface QueryRequestOptions { export interface QueryRequestOptions {
$skipToken?: string; $skipToken?: string;
$top?: number; $top?: number;
subscriptions: string[]; $allowPartialScopes: boolean;
subscriptions?: string[];
} }
export interface QueryResponse { export interface QueryResponse {

View File

@@ -1,37 +1,22 @@
import { MessageTypes } from "./MessageTypes"; import { FabricMessageTypes } from "./FabricMessageTypes";
// This is the current version of these messages // This is the current version of these messages
export const DATA_EXPLORER_RPC_VERSION = "2"; export const DATA_EXPLORER_RPC_VERSION = "3";
// Data Explorer to Fabric // Data Explorer to Fabric
export type DataExploreMessageV3 =
// TODO Remove when upgrading to Fabric v2
export type DataExploreMessageV1 =
| "ready"
| { | {
type: MessageTypes.GetAuthorizationToken; type: FabricMessageTypes.Ready;
id: string;
params: GetCosmosTokenMessageOptions[];
}
| {
type: MessageTypes.GetAllResourceTokens;
id: string;
};
// -----------------------------
export type DataExploreMessageV2 =
| {
type: MessageTypes.Ready;
id: string; id: string;
params: [string]; // version params: [string]; // version
} }
| { | {
type: MessageTypes.GetAuthorizationToken; type: FabricMessageTypes.GetAuthorizationToken;
id: string; id: string;
params: GetCosmosTokenMessageOptions[]; params: GetCosmosTokenMessageOptions[];
} }
| { | {
type: MessageTypes.GetAllResourceTokens; type: FabricMessageTypes.GetAllResourceTokens;
id: string; id: string;
}; };

View File

@@ -1,4 +1,4 @@
import { ConnectionStatusType, ContainerStatusType } from "../Common/Constants"; import { CapacityMode, ConnectionStatusType, ContainerStatusType } from "../Common/Constants";
export interface ArmEntity { export interface ArmEntity {
id: string; id: string;
@@ -35,6 +35,7 @@ export interface DatabaseAccountExtendedProperties {
ipRules?: IpRule[]; ipRules?: IpRule[];
privateEndpointConnections?: unknown[]; privateEndpointConnections?: unknown[];
capacity?: { totalThroughputLimit: number }; capacity?: { totalThroughputLimit: number };
capacityMode?: CapacityMode;
locations?: DatabaseAccountResponseLocation[]; locations?: DatabaseAccountResponseLocation[];
postgresqlEndpoint?: string; postgresqlEndpoint?: string;
publicNetworkAccess?: string; publicNetworkAccess?: string;
@@ -157,6 +158,7 @@ export interface Collection extends Resource {
changeFeedPolicy?: ChangeFeedPolicy; changeFeedPolicy?: ChangeFeedPolicy;
analyticalStorageTtl?: number; analyticalStorageTtl?: number;
geospatialConfig?: GeospatialConfig; geospatialConfig?: GeospatialConfig;
vectorEmbeddingPolicy?: VectorEmbeddingPolicy;
schema?: ISchema; schema?: ISchema;
requestSchema?: () => void; requestSchema?: () => void;
computedProperties?: ComputedProperties; computedProperties?: ComputedProperties;
@@ -194,8 +196,14 @@ export interface IndexingPolicy {
indexingMode: "consistent" | "lazy" | "none"; indexingMode: "consistent" | "lazy" | "none";
includedPaths: any; includedPaths: any;
excludedPaths: any; excludedPaths: any;
compositeIndexes?: any; compositeIndexes?: any[];
spatialIndexes?: any; spatialIndexes?: any[];
vectorIndexes?: VectorIndex[];
}
export interface VectorIndex {
path: string;
type: "flat" | "diskANN" | "quantizedFlat";
} }
export interface ComputedProperty { export interface ComputedProperty {
@@ -333,6 +341,18 @@ export interface CreateCollectionParams {
partitionKey?: PartitionKey; partitionKey?: PartitionKey;
uniqueKeyPolicy?: UniqueKeyPolicy; uniqueKeyPolicy?: UniqueKeyPolicy;
createMongoWildcardIndex?: boolean; createMongoWildcardIndex?: boolean;
vectorEmbeddingPolicy?: VectorEmbeddingPolicy;
}
export interface VectorEmbeddingPolicy {
vectorEmbeddings: VectorEmbedding[];
}
export interface VectorEmbedding {
dataType: "float16" | "float32" | "uint8" | "int8";
dimensions: number;
distanceFunction: "euclidean" | "cosine" | "dotproduct";
path: string;
} }
export interface ReadDatabaseOfferParams { export interface ReadDatabaseOfferParams {

View File

@@ -0,0 +1,13 @@
/**
* Data Explorer -> Fabric communication.
*/
export enum FabricMessageTypes {
GetAuthorizationToken = "GetAuthorizationToken",
GetAllResourceTokens = "GetAllResourceTokens",
Ready = "Ready",
}
export interface AuthorizationToken {
XDate: string;
PrimaryReadWriteToken: string;
}

View File

@@ -1,4 +1,4 @@
import { AuthorizationToken } from "./MessageTypes"; import { AuthorizationToken } from "Contracts/FabricMessageTypes";
// This is the version of these messages // This is the version of these messages
export const FABRIC_RPC_VERSION = "2"; export const FABRIC_RPC_VERSION = "2";

View File

@@ -1,12 +1,13 @@
/** /**
* Messaging types used with Data Explorer <-> Portal communication, * Messaging types used with Data Explorer <-> Portal communication,
* Hosted <-> Explorer communication and Data Explorer -> Fabric communication. * Hosted <-> Explorer communication
* *
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
* WARNING: !!!!!!! YOU CAN ONLY ADD NEW TYPES TO THE END OF THIS ENUM !!!!!!! * WARNING: !!!!!!! YOU CAN ONLY ADD NEW TYPES TO THE END OF THIS ENUM !!!!!!!
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
* *
* Enum are integers, so inserting or deleting a type will break the communication. * Enum are integers, so inserting or deleting a type will break the communication.
*
*/ */
export enum MessageTypes { export enum MessageTypes {
TelemetryInfo, TelemetryInfo,
@@ -43,14 +44,9 @@ export enum MessageTypes {
DisplayNPSSurvey, DisplayNPSSurvey,
OpenVCoreMongoNetworkingBlade, OpenVCoreMongoNetworkingBlade,
OpenVCoreMongoConnectionStringsBlade, OpenVCoreMongoConnectionStringsBlade,
GetAuthorizationToken, // Data Explorer -> Fabric GetAuthorizationToken, // unused. Can be removed if the portal uses the same list of enums.
GetAllResourceTokens, // Data Explorer -> Fabric GetAllResourceTokens, // unused. Can be removed if the portal uses the same list of enums.
Ready, // Data Explorer -> Fabric Ready, // unused. Can be removed if the portal uses the same list of enums.
OpenCESCVAFeedbackBlade, OpenCESCVAFeedbackBlade,
ActivateTab, ActivateTab,
} }
export interface AuthorizationToken {
XDate: string;
PrimaryReadWriteToken: string;
}

View File

@@ -98,7 +98,6 @@ export interface Database extends TreeNode {
openAddCollection(database: Database, event: MouseEvent): void; openAddCollection(database: Database, event: MouseEvent): void;
onSettingsClick: () => void; onSettingsClick: () => void;
loadOffer(): Promise<void>; loadOffer(): Promise<void>;
getPendingThroughputSplitNotification(): Promise<DataModels.Notification>;
} }
export interface CollectionBase extends TreeNode { export interface CollectionBase extends TreeNode {
@@ -176,6 +175,11 @@ export interface Collection extends CollectionBase {
loadTriggers(): Promise<any>; loadTriggers(): Promise<any>;
loadOffer(): Promise<void>; loadOffer(): Promise<void>;
showStoredProcedures: ko.Observable<boolean>;
showTriggers: ko.Observable<boolean>;
showUserDefinedFunctions: ko.Observable<boolean>;
showConflicts: ko.Observable<boolean>;
createStoredProcedureNode(data: StoredProcedureDefinition & Resource): StoredProcedure; createStoredProcedureNode(data: StoredProcedureDefinition & Resource): StoredProcedure;
createUserDefinedFunctionNode(data: UserDefinedFunctionDefinition & Resource): UserDefinedFunction; createUserDefinedFunctionNode(data: UserDefinedFunctionDefinition & Resource): UserDefinedFunction;
createTriggerNode(data: TriggerDefinition | SqlTriggerResource): Trigger; createTriggerNode(data: TriggerDefinition | SqlTriggerResource): Trigger;
@@ -186,8 +190,6 @@ export interface Collection extends CollectionBase {
onDragOver(source: Collection, event: { originalEvent: DragEvent }): void; onDragOver(source: Collection, event: { originalEvent: DragEvent }): void;
onDrop(source: Collection, event: { originalEvent: DragEvent }): void; onDrop(source: Collection, event: { originalEvent: DragEvent }): void;
uploadFiles(fileList: FileList): Promise<{ data: UploadDetailsRecord[] }>; uploadFiles(fileList: FileList): Promise<{ data: UploadDetailsRecord[] }>;
getPendingThroughputSplitNotification(): Promise<DataModels.Notification>;
} }
/** /**
@@ -324,9 +326,9 @@ export enum DocumentExplorerState {
noDocumentSelected, noDocumentSelected,
newDocumentValid, newDocumentValid,
newDocumentInvalid, newDocumentInvalid,
exisitingDocumentNoEdits, existingDocumentNoEdits,
exisitingDocumentDirtyValid, existingDocumentDirtyValid,
exisitingDocumentDirtyInvalid, existingDocumentDirtyInvalid,
} }
export enum IndexingPolicyEditorState { export enum IndexingPolicyEditorState {
@@ -339,9 +341,9 @@ export enum IndexingPolicyEditorState {
export enum ScriptEditorState { export enum ScriptEditorState {
newInvalid, newInvalid,
newValid, newValid,
exisitingNoEdits, existingNoEdits,
exisitingDirtyValid, existingDirtyValid,
exisitingDirtyInvalid, existingDirtyInvalid,
} }
export enum CollectionTabKind { export enum CollectionTabKind {
@@ -420,6 +422,7 @@ export interface SelfServeFrameInputs {
authorizationToken: string; authorizationToken: string;
csmEndpoint: string; csmEndpoint: string;
flights?: readonly string[]; flights?: readonly string[];
catalogAPIKey: string;
} }
export class MonacoEditorSettings { export class MonacoEditorSettings {

View File

@@ -36,21 +36,21 @@ describe("The Heatmap Control", () => {
}); });
it("should call _getChartSettings when drawHeatmap is invoked", () => { it("should call _getChartSettings when drawHeatmap is invoked", () => {
const _getChartSettings = spyOn<any>(heatmap, "_getChartSettings"); const _getChartSettings = jest.spyOn(heatmap, "_getChartSettings");
heatmap.drawHeatmap(); heatmap.drawHeatmap();
expect(_getChartSettings.calls.any()).toBe(true); expect(_getChartSettings).toHaveBeenCalled();
}); });
it("should call _getLayoutSettings when drawHeatmap is invoked", () => { it("should call _getLayoutSettings when drawHeatmap is invoked", () => {
const _getLayoutSettings = spyOn<any>(heatmap, "_getLayoutSettings"); const _getLayoutSettings = jest.spyOn(heatmap, "_getLayoutSettings");
heatmap.drawHeatmap(); heatmap.drawHeatmap();
expect(_getLayoutSettings.calls.any()).toBe(true); expect(_getLayoutSettings).toHaveBeenCalled();
}); });
it("should call _getChartDisplaySettings when drawHeatmap is invoked", () => { it("should call _getChartDisplaySettings when drawHeatmap is invoked", () => {
const _getChartDisplaySettings = spyOn<any>(heatmap, "_getChartDisplaySettings"); const _getChartDisplaySettings = jest.spyOn(heatmap, "_getChartDisplaySettings");
heatmap.drawHeatmap(); heatmap.drawHeatmap();
expect(_getChartDisplaySettings.calls.any()).toBe(true); expect(_getChartDisplaySettings).toHaveBeenCalled();
}); });
it("drawHeatmap should render a Heatmap inside the div element", () => { it("drawHeatmap should render a Heatmap inside the div element", () => {

View File

@@ -96,7 +96,8 @@ export class Heatmap {
return output; return output;
} }
private _getChartSettings(): ChartSettings[] { // public for testing purposes
public _getChartSettings(): ChartSettings[] {
return [ return [
{ {
z: this._chartData.dataPoints, z: this._chartData.dataPoints,
@@ -131,7 +132,8 @@ export class Heatmap {
]; ];
} }
private _getLayoutSettings(): LayoutSettings { // public for testing purposes
public _getLayoutSettings(): LayoutSettings {
return { return {
margin: { margin: {
l: 40, l: 40,
@@ -177,7 +179,8 @@ export class Heatmap {
}; };
} }
private _getChartDisplaySettings(): DisplaySettings { // public for testing purposes
public _getChartDisplaySettings(): DisplaySettings {
return { return {
/* heatmap can be fully responsive however the min-height needed in that case is greater than the iframe portal height, hence explicit width + height have been set in _getLayoutSettings /* heatmap can be fully responsive however the min-height needed in that case is greater than the iframe portal height, hence explicit width + height have been set in _getLayoutSettings
responsive: true,*/ responsive: true,*/

View File

@@ -1,3 +1,4 @@
import { TreeNodeMenuItem } from "Explorer/Controls/TreeComponent/TreeNodeComponent";
import { useDatabases } from "Explorer/useDatabases"; import { useDatabases } from "Explorer/useDatabases";
import { Action } from "Shared/Telemetry/TelemetryConstants"; import { Action } from "Shared/Telemetry/TelemetryConstants";
import { traceOpen } from "Shared/Telemetry/TelemetryProcessor"; import { traceOpen } from "Shared/Telemetry/TelemetryProcessor";
@@ -19,7 +20,6 @@ import { userContext } from "../UserContext";
import { getCollectionName, getDatabaseName } from "../Utils/APITypeUtils"; import { getCollectionName, getDatabaseName } from "../Utils/APITypeUtils";
import { useSidePanel } from "../hooks/useSidePanel"; import { useSidePanel } from "../hooks/useSidePanel";
import { Platform, configContext } from "./../ConfigContext"; import { Platform, configContext } from "./../ConfigContext";
import { TreeNodeMenuItem } from "./Controls/TreeComponent/TreeComponent";
import Explorer from "./Explorer"; import Explorer from "./Explorer";
import { useNotebook } from "./Notebook/useNotebook"; import { useNotebook } from "./Notebook/useNotebook";
import { DeleteCollectionConfirmationPane } from "./Panes/DeleteCollectionConfirmationPane/DeleteCollectionConfirmationPane"; import { DeleteCollectionConfirmationPane } from "./Panes/DeleteCollectionConfirmationPane/DeleteCollectionConfirmationPane";
@@ -100,6 +100,16 @@ export const createCollectionContextMenuButton = (
}); });
} }
if (useNotebook.getState().isShellEnabled && userContext.apiType === "Cassandra") {
items.push({
iconSrc: HostedTerminalIcon,
onClick: () => {
container.openNotebookTerminal(ViewModels.TerminalKind.Cassandra);
},
label: "Open Cassandra Shell",
});
}
if ( if (
configContext.platform !== Platform.Fabric && configContext.platform !== Platform.Fabric &&
(userContext.apiType === "SQL" || userContext.apiType === "Gremlin") (userContext.apiType === "SQL" || userContext.apiType === "Gremlin")

View File

@@ -1,4 +1,4 @@
import { Icon, Label, Stack } from "@fluentui/react"; import { DirectionalHint, Icon, Label, Stack, TooltipHost } from "@fluentui/react";
import * as React from "react"; import * as React from "react";
import { NormalizedEventKey } from "../../../Common/Constants"; import { NormalizedEventKey } from "../../../Common/Constants";
import { accordionStackTokens } from "../Settings/SettingsRenderUtils"; import { accordionStackTokens } from "../Settings/SettingsRenderUtils";
@@ -8,6 +8,7 @@ export interface CollapsibleSectionProps {
isExpandedByDefault: boolean; isExpandedByDefault: boolean;
onExpand?: () => void; onExpand?: () => void;
children: JSX.Element; children: JSX.Element;
tooltipContent?: string | JSX.Element | JSX.Element[];
} }
export interface CollapsibleSectionState { export interface CollapsibleSectionState {
@@ -26,8 +27,8 @@ export class CollapsibleSectionComponent extends React.Component<CollapsibleSect
this.setState({ isExpanded: !this.state.isExpanded }); this.setState({ isExpanded: !this.state.isExpanded });
}; };
public componentDidUpdate(): void { public componentDidUpdate(_prevProps: CollapsibleSectionProps, prevState: CollapsibleSectionState): void {
if (this.state.isExpanded && this.props.onExpand) { if (!prevState.isExpanded && this.state.isExpanded && this.props.onExpand) {
this.props.onExpand(); this.props.onExpand();
} }
} }
@@ -43,7 +44,7 @@ export class CollapsibleSectionComponent extends React.Component<CollapsibleSect
return ( return (
<> <>
<Stack <Stack
className="collapsibleSection" className={"collapsibleSection"}
horizontal horizontal
verticalAlign="center" verticalAlign="center"
tokens={accordionStackTokens} tokens={accordionStackTokens}
@@ -55,6 +56,19 @@ export class CollapsibleSectionComponent extends React.Component<CollapsibleSect
> >
<Icon iconName={this.state.isExpanded ? "ChevronDown" : "ChevronRight"} /> <Icon iconName={this.state.isExpanded ? "ChevronDown" : "ChevronRight"} />
<Label>{this.props.title}</Label> <Label>{this.props.title}</Label>
{this.props.tooltipContent && (
<TooltipHost
directionalHint={DirectionalHint.bottomLeftEdge}
content={this.props.tooltipContent}
styles={{
root: {
marginLeft: "0 !important",
},
}}
>
<Icon iconName="Info" className="panelInfoIcon" tabIndex={0} />
</TooltipHost>
)}
</Stack> </Stack>
{this.state.isExpanded && this.props.children} {this.state.isExpanded && this.props.children}
</> </>

View File

@@ -11,7 +11,7 @@ exports[`CollapsibleSectionComponent renders 1`] = `
role="button" role="button"
tabIndex={0} tabIndex={0}
tokens={ tokens={
Object { {
"childrenGap": 10, "childrenGap": 10,
} }
} }

View File

@@ -1,6 +1,7 @@
/** /**
* React component for Command button component. * React component for Command button component.
*/ */
import { KeyboardAction } from "KeyboardShortcuts";
import * as React from "react"; import * as React from "react";
import CollapseChevronDownIcon from "../../../../images/QueryBuilder/CollapseChevronDown_16x.png"; import CollapseChevronDownIcon from "../../../../images/QueryBuilder/CollapseChevronDown_16x.png";
import { KeyCodes } from "../../../Common/Constants"; import { KeyCodes } from "../../../Common/Constants";
@@ -30,7 +31,7 @@ export interface CommandButtonComponentProps {
/** /**
* Click handler for command button click * Click handler for command button click
*/ */
onCommandClick: (e: React.SyntheticEvent) => void; onCommandClick?: (e: React.SyntheticEvent | KeyboardEvent) => void;
/** /**
* Label for the button * Label for the button
@@ -107,10 +108,17 @@ export interface CommandButtonComponentProps {
* Vertical bar to divide buttons * Vertical bar to divide buttons
*/ */
isDivider?: boolean; isDivider?: boolean;
/** /**
* Aria-label for the button * Aria-label for the button
*/ */
ariaLabel: string; ariaLabel: string;
/**
* If specified, a keyboard action that should trigger this button's onCommandClick handler when activated.
* If not specified, the button will not be triggerable by keyboard shortcuts.
*/
keyboardAction?: KeyboardAction;
} }
export class CommandButtonComponent extends React.Component<CommandButtonComponentProps> { export class CommandButtonComponent extends React.Component<CommandButtonComponentProps> {

View File

@@ -20,14 +20,23 @@ export interface EditorReactProps {
lineDecorationsWidth?: monaco.editor.IEditorOptions["lineDecorationsWidth"]; lineDecorationsWidth?: monaco.editor.IEditorOptions["lineDecorationsWidth"];
minimap?: monaco.editor.IEditorOptions["minimap"]; minimap?: monaco.editor.IEditorOptions["minimap"];
scrollBeyondLastLine?: monaco.editor.IEditorOptions["scrollBeyondLastLine"]; scrollBeyondLastLine?: monaco.editor.IEditorOptions["scrollBeyondLastLine"];
fontSize?: monaco.editor.IEditorOptions["fontSize"];
monacoContainerStyles?: React.CSSProperties; monacoContainerStyles?: React.CSSProperties;
className?: string;
spinnerClassName?: string;
enableWordWrapContextMenuItem?: boolean; // Enable/Disable "Word Wrap" context menu item
onWordWrapChanged?: (wordWrap: "on" | "off") => void; // Called when word wrap is changed
} }
export class EditorReact extends React.Component<EditorReactProps, EditorReactStates> { export class EditorReact extends React.Component<EditorReactProps, EditorReactStates> {
private static readonly VIEWING_OPTIONS_GROUP_ID = "viewingoptions"; // Group ID for the context menu group
private rootNode: HTMLElement; private rootNode: HTMLElement;
private editor: monaco.editor.IStandaloneCodeEditor; private editor: monaco.editor.IStandaloneCodeEditor;
private selectionListener: monaco.IDisposable; private selectionListener: monaco.IDisposable;
private monacoEditorOptionsWordWrap: monaco.editor.EditorOption;
public constructor(props: EditorReactProps) { public constructor(props: EditorReactProps) {
super(props); super(props);
this.state = { this.state = {
@@ -75,9 +84,11 @@ export class EditorReact extends React.Component<EditorReactProps, EditorReactSt
public render(): JSX.Element { public render(): JSX.Element {
return ( return (
<React.Fragment> <React.Fragment>
{!this.state.showEditor && <Spinner size={SpinnerSize.large} className="spinner" />} {!this.state.showEditor && (
<Spinner size={SpinnerSize.large} className={this.props.spinnerClassName || "spinner"} />
)}
<div <div
className="jsonEditor" className={this.props.className || "jsonEditor"}
style={this.props.monacoContainerStyles} style={this.props.monacoContainerStyles}
ref={(elt: HTMLElement) => this.setRef(elt)} ref={(elt: HTMLElement) => this.setRef(elt)}
/> />
@@ -108,6 +119,23 @@ export class EditorReact extends React.Component<EditorReactProps, EditorReactSt
}, },
); );
} }
if (this.props.enableWordWrapContextMenuItem) {
editor.addAction({
// An unique identifier of the contributed action.
id: "wordwrap",
label: "Toggle Word Wrap",
contextMenuGroupId: EditorReact.VIEWING_OPTIONS_GROUP_ID,
contextMenuOrder: 1,
// Method that will be executed when the action is triggered.
// @param editor The editor instance is passed in as a convenience
run: (ed) => {
const newOption = ed.getOption(this.monacoEditorOptionsWordWrap) === "on" ? "off" : "on";
ed.updateOptions({ wordWrap: newOption });
this.props.onWordWrapChanged(newOption);
},
});
}
} }
/** /**
@@ -119,7 +147,7 @@ export class EditorReact extends React.Component<EditorReactProps, EditorReactSt
value: this.props.content, value: this.props.content,
readOnly: this.props.isReadOnly, readOnly: this.props.isReadOnly,
ariaLabel: this.props.ariaLabel, ariaLabel: this.props.ariaLabel,
fontSize: 12, fontSize: this.props.fontSize || 12,
automaticLayout: true, automaticLayout: true,
theme: this.props.theme, theme: this.props.theme,
wordWrap: this.props.wordWrap || "off", wordWrap: this.props.wordWrap || "off",
@@ -131,8 +159,18 @@ export class EditorReact extends React.Component<EditorReactProps, EditorReactSt
}; };
this.rootNode.innerHTML = ""; this.rootNode.innerHTML = "";
const monaco = await loadMonaco(); const lazymonaco = await loadMonaco();
createCallback(monaco?.editor?.create(this.rootNode, options));
// We can only get this constant after loading monaco lazily
this.monacoEditorOptionsWordWrap = lazymonaco.editor.EditorOption.wordWrap;
try {
createCallback(lazymonaco?.editor?.create(this.rootNode, options));
} catch (error) {
// This could happen if the parent node suddenly disappears during create()
console.error("Unable to create EditorReact", error);
return;
}
if (this.rootNode.innerHTML) { if (this.rootNode.innerHTML) {
this.setState({ this.setState({

View File

@@ -18,7 +18,7 @@ exports[`Feature panel renders all flags 1`] = `
<Stack <Stack
className="options" className="options"
tokens={ tokens={
Object { {
"childrenGap": 10, "childrenGap": 10,
} }
} }
@@ -27,7 +27,7 @@ exports[`Feature panel renders all flags 1`] = `
horizontal={true} horizontal={true}
horizontalAlign="space-between" horizontalAlign="space-between"
tokens={ tokens={
Object { {
"childrenGap": 10, "childrenGap": 10,
} }
} }
@@ -52,7 +52,7 @@ exports[`Feature panel renders all flags 1`] = `
horizontal={true} horizontal={true}
horizontalAlign="start" horizontalAlign="start"
tokens={ tokens={
Object { {
"childrenGap": 10, "childrenGap": 10,
} }
} }
@@ -61,16 +61,16 @@ exports[`Feature panel renders all flags 1`] = `
label="Base Url" label="Base Url"
onChange={[Function]} onChange={[Function]}
options={ options={
Array [ [
Object { {
"key": "https://localhost:1234/explorer.html", "key": "https://localhost:1234/explorer.html",
"text": "localhost:1234", "text": "localhost:1234",
}, },
Object { {
"key": "https://cosmos.azure.com/explorer.html", "key": "https://cosmos.azure.com/explorer.html",
"text": "cosmos.azure.com", "text": "cosmos.azure.com",
}, },
Object { {
"key": "https://portal.azure.com", "key": "https://portal.azure.com",
"text": "portal", "text": "portal",
}, },
@@ -78,8 +78,8 @@ exports[`Feature panel renders all flags 1`] = `
} }
selectedKey="https://localhost:1234/explorer.html" selectedKey="https://localhost:1234/explorer.html"
styles={ styles={
Object { {
"dropdown": Object { "dropdown": {
"width": 200, "width": 200,
}, },
} }
@@ -89,20 +89,20 @@ exports[`Feature panel renders all flags 1`] = `
label="Platform" label="Platform"
onChange={[Function]} onChange={[Function]}
options={ options={
Array [ [
Object { {
"key": "Hosted", "key": "Hosted",
"text": "Hosted", "text": "Hosted",
}, },
Object { {
"key": "Portal", "key": "Portal",
"text": "Portal", "text": "Portal",
}, },
Object { {
"key": "Emulator", "key": "Emulator",
"text": "Emulator", "text": "Emulator",
}, },
Object { {
"key": "", "key": "",
"text": "None", "text": "None",
}, },
@@ -110,8 +110,8 @@ exports[`Feature panel renders all flags 1`] = `
} }
selectedKey="Hosted" selectedKey="Hosted"
styles={ styles={
Object { {
"dropdown": Object { "dropdown": {
"width": 200, "width": 200,
}, },
} }
@@ -208,7 +208,7 @@ exports[`Feature panel renders all flags 1`] = `
<Stack <Stack
horizontal={true} horizontal={true}
tokens={ tokens={
Object { {
"childrenGap": 10, "childrenGap": 10,
} }
} }
@@ -222,8 +222,8 @@ exports[`Feature panel renders all flags 1`] = `
onChange={[Function]} onChange={[Function]}
placeholder="https://notebookserver" placeholder="https://notebookserver"
styles={ styles={
Object { {
"fieldGroup": Object { "fieldGroup": {
"width": 300, "width": 300,
}, },
} }
@@ -235,8 +235,8 @@ exports[`Feature panel renders all flags 1`] = `
onChange={[Function]} onChange={[Function]}
placeholder="" placeholder=""
styles={ styles={
Object { {
"fieldGroup": Object { "fieldGroup": {
"width": 300, "width": 300,
}, },
} }
@@ -248,8 +248,8 @@ exports[`Feature panel renders all flags 1`] = `
onChange={[Function]} onChange={[Function]}
placeholder="" placeholder=""
styles={ styles={
Object { {
"fieldGroup": Object { "fieldGroup": {
"width": 300, "width": 300,
}, },
} }
@@ -265,8 +265,8 @@ exports[`Feature panel renders all flags 1`] = `
onChange={[Function]} onChange={[Function]}
placeholder="" placeholder=""
styles={ styles={
Object { {
"fieldGroup": Object { "fieldGroup": {
"width": 300, "width": 300,
}, },
} }
@@ -279,8 +279,8 @@ exports[`Feature panel renders all flags 1`] = `
onChange={[Function]} onChange={[Function]}
placeholder="https://localhost:1234/explorer.html" placeholder="https://localhost:1234/explorer.html"
styles={ styles={
Object { {
"fieldGroup": Object { "fieldGroup": {
"width": 300, "width": 300,
}, },
} }
@@ -292,8 +292,8 @@ exports[`Feature panel renders all flags 1`] = `
onChange={[Function]} onChange={[Function]}
placeholder="" placeholder=""
styles={ styles={
Object { {
"fieldGroup": Object { "fieldGroup": {
"width": 300, "width": 300,
}, },
} }

View File

@@ -4,8 +4,8 @@ exports[`GalleryCardComponent renders 1`] = `
<StyledDocumentCardBase <StyledDocumentCardBase
aria-label="name" aria-label="name"
styles={ styles={
Object { {
"root": Object { "root": {
"display": "inline-block", "display": "inline-block",
"marginRight": 20, "marginRight": 20,
"width": 256, "width": 256,
@@ -16,8 +16,8 @@ exports[`GalleryCardComponent renders 1`] = `
<StyledDocumentCardActivityBase <StyledDocumentCardActivityBase
activity="Invalid Date" activity="Invalid Date"
people={ people={
Array [ [
Object { {
"name": "author", "name": "author",
"profileImageSrc": false, "profileImageSrc": false,
}, },
@@ -26,8 +26,8 @@ exports[`GalleryCardComponent renders 1`] = `
/> />
<StyledDocumentCardPreviewBase <StyledDocumentCardPreviewBase
previewImages={ previewImages={
Array [ [
Object { {
"height": 144, "height": 144,
"imageFit": 2, "imageFit": 2,
"previewImageSrc": "thumbnailUrl", "previewImageSrc": "thumbnailUrl",
@@ -40,8 +40,8 @@ exports[`GalleryCardComponent renders 1`] = `
<Text <Text
nowrap={true} nowrap={true}
styles={ styles={
Object { {
"root": Object { "root": {
"height": 18, "height": 18,
"padding": "2px 16px", "padding": "2px 16px",
}, },
@@ -69,15 +69,15 @@ exports[`GalleryCardComponent renders 1`] = `
/> />
<span <span
style={ style={
Object { {
"padding": "8px 16px", "padding": "8px 16px",
} }
} }
> >
<Text <Text
styles={ styles={
Object { {
"root": Object { "root": {
"color": "#605E5C", "color": "#605E5C",
"paddingRight": 8, "paddingRight": 8,
}, },
@@ -88,8 +88,8 @@ exports[`GalleryCardComponent renders 1`] = `
<Icon <Icon
iconName="RedEye" iconName="RedEye"
styles={ styles={
Object { {
"root": Object { "root": {
"verticalAlign": "middle", "verticalAlign": "middle",
}, },
} }
@@ -100,8 +100,8 @@ exports[`GalleryCardComponent renders 1`] = `
</Text> </Text>
<Text <Text
styles={ styles={
Object { {
"root": Object { "root": {
"color": "#605E5C", "color": "#605E5C",
"paddingRight": 8, "paddingRight": 8,
}, },
@@ -112,8 +112,8 @@ exports[`GalleryCardComponent renders 1`] = `
<Icon <Icon
iconName="Download" iconName="Download"
styles={ styles={
Object { {
"root": Object { "root": {
"verticalAlign": "middle", "verticalAlign": "middle",
}, },
} }
@@ -124,8 +124,8 @@ exports[`GalleryCardComponent renders 1`] = `
</Text> </Text>
<Text <Text
styles={ styles={
Object { {
"root": Object { "root": {
"color": "#605E5C", "color": "#605E5C",
"paddingRight": 8, "paddingRight": 8,
}, },
@@ -136,8 +136,8 @@ exports[`GalleryCardComponent renders 1`] = `
<Icon <Icon
iconName="Heart" iconName="Heart"
styles={ styles={
Object { {
"root": Object { "root": {
"verticalAlign": "middle", "verticalAlign": "middle",
}, },
} }
@@ -151,8 +151,8 @@ exports[`GalleryCardComponent renders 1`] = `
<StyledDocumentCardDetailsBase> <StyledDocumentCardDetailsBase>
<Separator <Separator
styles={ styles={
Object { {
"root": Object { "root": {
"height": 1, "height": 1,
"padding": 0, "padding": 0,
}, },
@@ -161,22 +161,22 @@ exports[`GalleryCardComponent renders 1`] = `
/> />
<span <span
style={ style={
Object { {
"padding": "0px 16px", "padding": "0px 16px",
} }
} }
> >
<StyledTooltipHostBase <StyledTooltipHostBase
calloutProps={ calloutProps={
Object { {
"gapSpace": 0, "gapSpace": 0,
} }
} }
content="Favorite" content="Favorite"
id="TooltipHost-IconButton-Heart" id="TooltipHost-IconButton-Heart"
styles={ styles={
Object { {
"root": Object { "root": {
"display": "inline-block", "display": "inline-block",
"float": "left", "float": "left",
}, },
@@ -186,7 +186,7 @@ exports[`GalleryCardComponent renders 1`] = `
<CustomizedIconButton <CustomizedIconButton
ariaLabel="Favorite" ariaLabel="Favorite"
iconProps={ iconProps={
Object { {
"iconName": "Heart", "iconName": "Heart",
} }
} }
@@ -196,15 +196,15 @@ exports[`GalleryCardComponent renders 1`] = `
</StyledTooltipHostBase> </StyledTooltipHostBase>
<StyledTooltipHostBase <StyledTooltipHostBase
calloutProps={ calloutProps={
Object { {
"gapSpace": 0, "gapSpace": 0,
} }
} }
content="Download" content="Download"
id="TooltipHost-IconButton-Download" id="TooltipHost-IconButton-Download"
styles={ styles={
Object { {
"root": Object { "root": {
"display": "inline-block", "display": "inline-block",
"float": "left", "float": "left",
}, },
@@ -214,7 +214,7 @@ exports[`GalleryCardComponent renders 1`] = `
<CustomizedIconButton <CustomizedIconButton
ariaLabel="Download" ariaLabel="Download"
iconProps={ iconProps={
Object { {
"iconName": "Download", "iconName": "Download",
} }
} }
@@ -224,15 +224,15 @@ exports[`GalleryCardComponent renders 1`] = `
</StyledTooltipHostBase> </StyledTooltipHostBase>
<StyledTooltipHostBase <StyledTooltipHostBase
calloutProps={ calloutProps={
Object { {
"gapSpace": 0, "gapSpace": 0,
} }
} }
content="Remove" content="Remove"
id="TooltipHost-IconButton-Delete" id="TooltipHost-IconButton-Delete"
styles={ styles={
Object { {
"root": Object { "root": {
"display": "inline-block", "display": "inline-block",
"float": "right", "float": "right",
}, },
@@ -242,7 +242,7 @@ exports[`GalleryCardComponent renders 1`] = `
<CustomizedIconButton <CustomizedIconButton
ariaLabel="Remove" ariaLabel="Remove"
iconProps={ iconProps={
Object { {
"iconName": "Delete", "iconName": "Delete",
} }
} }

View File

@@ -3,7 +3,7 @@
exports[`CodeOfConduct renders 1`] = ` exports[`CodeOfConduct renders 1`] = `
<Stack <Stack
tokens={ tokens={
Object { {
"childrenGap": 20, "childrenGap": 20,
} }
} }
@@ -11,7 +11,7 @@ exports[`CodeOfConduct renders 1`] = `
<StackItem> <StackItem>
<Text <Text
style={ style={
Object { {
"fontSize": "20px", "fontSize": "20px",
"fontWeight": 500, "fontWeight": 500,
} }
@@ -41,12 +41,12 @@ exports[`CodeOfConduct renders 1`] = `
label="I have read and accept the code of conduct." label="I have read and accept the code of conduct."
onChange={[Function]} onChange={[Function]}
styles={ styles={
Object { {
"label": Object { "label": {
"margin": 0, "margin": 0,
"padding": "2 0 2 0", "padding": "2 0 2 0",
}, },
"text": Object { "text": {
"fontSize": 12, "fontSize": 12,
}, },
} }

View File

@@ -4,7 +4,7 @@ exports[`InfoComponent renders 1`] = `
<StyledHoverCardBase <StyledHoverCardBase
instantOpenOnClick={true} instantOpenOnClick={true}
plainCardProps={ plainCardProps={
Object { {
"onRenderPlainCard": [Function], "onRenderPlainCard": [Function],
} }
} }
@@ -18,8 +18,8 @@ exports[`InfoComponent renders 1`] = `
className="infoIconMain" className="infoIconMain"
iconName="Help" iconName="Help"
styles={ styles={
Object { {
"root": Object { "root": {
"verticalAlign": "middle", "verticalAlign": "middle",
}, },
} }

View File

@@ -13,14 +13,14 @@ exports[`GalleryViewerComponent renders 1`] = `
itemKey="OfficialSamples" itemKey="OfficialSamples"
key="OfficialSamples" key="OfficialSamples"
style={ style={
Object { {
"marginTop": 20, "marginTop": 20,
} }
} }
> >
<Stack <Stack
tokens={ tokens={
Object { {
"childrenGap": 10, "childrenGap": 10,
} }
} }
@@ -28,7 +28,7 @@ exports[`GalleryViewerComponent renders 1`] = `
<Stack <Stack
horizontal={true} horizontal={true}
tokens={ tokens={
Object { {
"childrenGap": 20, "childrenGap": 20,
"padding": 10, "padding": 10,
} }
@@ -50,8 +50,8 @@ exports[`GalleryViewerComponent renders 1`] = `
</StackItem> </StackItem>
<StackItem <StackItem
styles={ styles={
Object { {
"root": Object { "root": {
"minWidth": 200, "minWidth": 200,
}, },
} }
@@ -60,20 +60,20 @@ exports[`GalleryViewerComponent renders 1`] = `
<Dropdown <Dropdown
onChange={[Function]} onChange={[Function]}
options={ options={
Array [ [
Object { {
"key": 0, "key": 0,
"text": "Most viewed", "text": "Most viewed",
}, },
Object { {
"key": 1, "key": 1,
"text": "Most downloaded", "text": "Most downloaded",
}, },
Object { {
"key": 3, "key": 3,
"text": "Most recent", "text": "Most recent",
}, },
Object { {
"key": 2, "key": 2,
"text": "Most favorited", "text": "Most favorited",
}, },

View File

@@ -3,7 +3,7 @@
exports[`NotebookMetadataComponent renders liked notebook 1`] = ` exports[`NotebookMetadataComponent renders liked notebook 1`] = `
<Stack <Stack
tokens={ tokens={
Object { {
"childrenGap": 10, "childrenGap": 10,
} }
} }
@@ -11,7 +11,7 @@ exports[`NotebookMetadataComponent renders liked notebook 1`] = `
<Stack <Stack
horizontal={true} horizontal={true}
tokens={ tokens={
Object { {
"childrenGap": 30, "childrenGap": 30,
} }
} }
@@ -29,7 +29,7 @@ exports[`NotebookMetadataComponent renders liked notebook 1`] = `
<Text> <Text>
<CustomizedIconButton <CustomizedIconButton
iconProps={ iconProps={
Object { {
"iconName": "HeartFill", "iconName": "HeartFill",
} }
} }
@@ -53,7 +53,7 @@ exports[`NotebookMetadataComponent renders liked notebook 1`] = `
<Stack <Stack
horizontal={true} horizontal={true}
tokens={ tokens={
Object { {
"childrenGap": 10, "childrenGap": 10,
} }
} }
@@ -96,8 +96,8 @@ exports[`NotebookMetadataComponent renders liked notebook 1`] = `
</Text> </Text>
<Text <Text
styles={ styles={
Object { {
"root": Object { "root": {
"fontWeight": 600, "fontWeight": 600,
}, },
} }
@@ -115,7 +115,7 @@ exports[`NotebookMetadataComponent renders liked notebook 1`] = `
exports[`NotebookMetadataComponent renders un-liked notebook 1`] = ` exports[`NotebookMetadataComponent renders un-liked notebook 1`] = `
<Stack <Stack
tokens={ tokens={
Object { {
"childrenGap": 10, "childrenGap": 10,
} }
} }
@@ -123,7 +123,7 @@ exports[`NotebookMetadataComponent renders un-liked notebook 1`] = `
<Stack <Stack
horizontal={true} horizontal={true}
tokens={ tokens={
Object { {
"childrenGap": 30, "childrenGap": 30,
} }
} }
@@ -141,7 +141,7 @@ exports[`NotebookMetadataComponent renders un-liked notebook 1`] = `
<Text> <Text>
<CustomizedIconButton <CustomizedIconButton
iconProps={ iconProps={
Object { {
"iconName": "Heart", "iconName": "Heart",
} }
} }
@@ -165,7 +165,7 @@ exports[`NotebookMetadataComponent renders un-liked notebook 1`] = `
<Stack <Stack
horizontal={true} horizontal={true}
tokens={ tokens={
Object { {
"childrenGap": 10, "childrenGap": 10,
} }
} }
@@ -208,8 +208,8 @@ exports[`NotebookMetadataComponent renders un-liked notebook 1`] = `
</Text> </Text>
<Text <Text
styles={ styles={
Object { {
"root": Object { "root": {
"fontWeight": 600, "fontWeight": 600,
}, },
} }

View File

@@ -7,14 +7,14 @@
} }
.settingsV2ToolTip { .settingsV2ToolTip {
padding: 10px; padding: 10px;
font: 12px @DataExplorerFont; font: 12px @DataExplorerFont;
max-width: 300px; max-width: 300px;
} }
.autoPilotSelector span { .autoPilotSelector span {
height: 25px; height: 25px;
font: 14px @DataExplorerFont; font: 14px @DataExplorerFont;
} }
.settingsV2TabsContainer { .settingsV2TabsContainer {
@@ -25,7 +25,14 @@
font-family: @DataExplorerFont; font-family: @DataExplorerFont;
} }
.settingsV2IndexingPolicyEditor { .settingsV2Editor {
width: 100%; width: 100%;
height: 60vh; height: 60vh;
} }
.settingsV2EditorSpinner {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}

View File

@@ -3,7 +3,12 @@ import {
ComputedPropertiesComponent, ComputedPropertiesComponent,
ComputedPropertiesComponentProps, ComputedPropertiesComponentProps,
} from "Explorer/Controls/Settings/SettingsSubComponents/ComputedPropertiesComponent"; } from "Explorer/Controls/Settings/SettingsSubComponents/ComputedPropertiesComponent";
import {
ContainerVectorPolicyComponent,
ContainerVectorPolicyComponentProps,
} from "Explorer/Controls/Settings/SettingsSubComponents/ContainerVectorPolicyComponent";
import { useDatabases } from "Explorer/useDatabases"; import { useDatabases } from "Explorer/useDatabases";
import { isVectorSearchEnabled } from "Utils/CapabilityUtils";
import { isRunningOnPublicCloud } from "Utils/CloudUtils"; import { isRunningOnPublicCloud } from "Utils/CloudUtils";
import * as React from "react"; import * as React from "react";
import DiscardIcon from "../../../../images/discard.svg"; import DiscardIcon from "../../../../images/discard.svg";
@@ -125,7 +130,6 @@ export interface SettingsComponentState {
conflictResolutionPolicyProcedureBaseline: string; conflictResolutionPolicyProcedureBaseline: string;
isConflictResolutionDirty: boolean; isConflictResolutionDirty: boolean;
initialNotification: DataModels.Notification;
selectedTab: SettingsV2TabTypes; selectedTab: SettingsV2TabTypes;
} }
@@ -144,6 +148,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
private shouldShowComputedPropertiesEditor: boolean; private shouldShowComputedPropertiesEditor: boolean;
private shouldShowIndexingPolicyEditor: boolean; private shouldShowIndexingPolicyEditor: boolean;
private shouldShowPartitionKeyEditor: boolean; private shouldShowPartitionKeyEditor: boolean;
private isVectorSearchEnabled: boolean;
private totalThroughputUsed: number; private totalThroughputUsed: number;
public mongoDBCollectionResource: MongoDBCollectionResource; public mongoDBCollectionResource: MongoDBCollectionResource;
@@ -158,6 +163,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
this.shouldShowComputedPropertiesEditor = userContext.apiType === "SQL"; this.shouldShowComputedPropertiesEditor = userContext.apiType === "SQL";
this.shouldShowIndexingPolicyEditor = userContext.apiType !== "Cassandra" && userContext.apiType !== "Mongo"; this.shouldShowIndexingPolicyEditor = userContext.apiType !== "Cassandra" && userContext.apiType !== "Mongo";
this.shouldShowPartitionKeyEditor = userContext.apiType === "SQL" && isRunningOnPublicCloud(); this.shouldShowPartitionKeyEditor = userContext.apiType === "SQL" && isRunningOnPublicCloud();
this.isVectorSearchEnabled = isVectorSearchEnabled() && !hasDatabaseSharedThroughput(this.collection);
this.changeFeedPolicyVisible = userContext.features.enableChangeFeedPolicy; this.changeFeedPolicyVisible = userContext.features.enableChangeFeedPolicy;
@@ -222,7 +228,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
conflictResolutionPolicyProcedureBaseline: undefined, conflictResolutionPolicyProcedureBaseline: undefined,
isConflictResolutionDirty: false, isConflictResolutionDirty: false,
initialNotification: undefined,
selectedTab: SettingsV2TabTypes.ScaleTab, selectedTab: SettingsV2TabTypes.ScaleTab,
}; };
@@ -1045,7 +1050,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
onMaxAutoPilotThroughputChange: this.onMaxAutoPilotThroughputChange, onMaxAutoPilotThroughputChange: this.onMaxAutoPilotThroughputChange,
onScaleSaveableChange: this.onScaleSaveableChange, onScaleSaveableChange: this.onScaleSaveableChange,
onScaleDiscardableChange: this.onScaleDiscardableChange, onScaleDiscardableChange: this.onScaleDiscardableChange,
initialNotification: this.props.settingsTab.pendingNotification(),
throughputError: this.state.throughputError, throughputError: this.state.throughputError,
}; };
@@ -1097,6 +1101,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
indexTransformationProgress: this.state.indexTransformationProgress, indexTransformationProgress: this.state.indexTransformationProgress,
refreshIndexTransformationProgress: this.refreshIndexTransformationProgress, refreshIndexTransformationProgress: this.refreshIndexTransformationProgress,
onIndexingPolicyDirtyChange: this.onIndexingPolicyDirtyChange, onIndexingPolicyDirtyChange: this.onIndexingPolicyDirtyChange,
isVectorSearchEnabled: this.isVectorSearchEnabled,
}; };
const mongoIndexingPolicyComponentProps: MongoIndexingPolicyComponentProps = { const mongoIndexingPolicyComponentProps: MongoIndexingPolicyComponentProps = {
@@ -1143,6 +1148,10 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
explorer: this.props.settingsTab.getContainer(), explorer: this.props.settingsTab.getContainer(),
}; };
const containerVectorPolicyProps: ContainerVectorPolicyComponentProps = {
vectorEmbeddingPolicy: this.collection.rawDataModel?.vectorEmbeddingPolicy,
};
const tabs: SettingsV2TabInfo[] = []; const tabs: SettingsV2TabInfo[] = [];
if (!hasDatabaseSharedThroughput(this.collection) && this.offer) { if (!hasDatabaseSharedThroughput(this.collection) && this.offer) {
tabs.push({ tabs.push({
@@ -1156,6 +1165,13 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
content: <SubSettingsComponent {...subSettingsComponentProps} />, content: <SubSettingsComponent {...subSettingsComponentProps} />,
}); });
if (this.isVectorSearchEnabled) {
tabs.push({
tab: SettingsV2TabTypes.ContainerVectorPolicyTab,
content: <ContainerVectorPolicyComponent {...containerVectorPolicyProps} />,
});
}
if (this.shouldShowIndexingPolicyEditor) { if (this.shouldShowIndexingPolicyEditor) {
tabs.push({ tabs.push({
tab: SettingsV2TabTypes.IndexingPolicyTab, tab: SettingsV2TabTypes.IndexingPolicyTab,

View File

@@ -121,7 +121,7 @@ export class ComputedPropertiesComponent extends React.Component<
</Link> </Link>
&#160; about how to define computed properties and how to use them. &#160; about how to define computed properties and how to use them.
</Text> </Text>
<div className="settingsV2IndexingPolicyEditor" tabIndex={0} ref={this.computedPropertiesDiv}></div> <div className="settingsV2Editor" tabIndex={0} ref={this.computedPropertiesDiv}></div>
</Stack> </Stack>
); );
} }

View File

@@ -0,0 +1,30 @@
import { Stack } from "@fluentui/react";
import { VectorEmbeddingPolicy } from "Contracts/DataModels";
import { EditorReact } from "Explorer/Controls/Editor/EditorReact";
import { titleAndInputStackProps } from "Explorer/Controls/Settings/SettingsRenderUtils";
import React from "react";
export interface ContainerVectorPolicyComponentProps {
vectorEmbeddingPolicy: VectorEmbeddingPolicy;
}
export const ContainerVectorPolicyComponent: React.FC<ContainerVectorPolicyComponentProps> = ({
vectorEmbeddingPolicy,
}) => {
return (
<Stack {...titleAndInputStackProps} styles={{ root: { position: "relative" } }}>
<EditorReact
language={"json"}
content={JSON.stringify(vectorEmbeddingPolicy || {}, null, 4)}
isReadOnly={true}
wordWrap={"on"}
ariaLabel={"Container vector policy"}
lineNumbers={"on"}
scrollBeyondLastLine={false}
className={"settingsV2Editor"}
spinnerClassName={"settingsV2EditorSpinner"}
fontSize={14}
/>
</Stack>
);
};

View File

@@ -16,6 +16,7 @@ export interface IndexingPolicyComponentProps {
logIndexingPolicySuccessMessage: () => void; logIndexingPolicySuccessMessage: () => void;
indexTransformationProgress: number; indexTransformationProgress: number;
refreshIndexTransformationProgress: () => Promise<void>; refreshIndexTransformationProgress: () => Promise<void>;
isVectorSearchEnabled?: boolean;
onIndexingPolicyDirtyChange: (isIndexingPolicyDirty: boolean) => void; onIndexingPolicyDirtyChange: (isIndexingPolicyDirty: boolean) => void;
} }
@@ -119,10 +120,15 @@ export class IndexingPolicyComponent extends React.Component<
indexTransformationProgress={this.props.indexTransformationProgress} indexTransformationProgress={this.props.indexTransformationProgress}
refreshIndexTransformationProgress={this.props.refreshIndexTransformationProgress} refreshIndexTransformationProgress={this.props.refreshIndexTransformationProgress}
/> />
{this.props.isVectorSearchEnabled && (
<MessageBar messageBarType={MessageBarType.severeWarning}>
Container vector policies and vector indexes are not modifiable after container creation
</MessageBar>
)}
{isDirty(this.props.indexingPolicyContent, this.props.indexingPolicyContentBaseline) && ( {isDirty(this.props.indexingPolicyContent, this.props.indexingPolicyContentBaseline) && (
<MessageBar messageBarType={MessageBarType.warning}>{unsavedEditorWarningMessage("indexPolicy")}</MessageBar> <MessageBar messageBarType={MessageBarType.warning}>{unsavedEditorWarningMessage("indexPolicy")}</MessageBar>
)} )}
<div className="settingsV2IndexingPolicyEditor" tabIndex={0} ref={this.indexingPolicyDiv}></div> <div className="settingsV2Editor" tabIndex={0} ref={this.indexingPolicyDiv}></div>
</Stack> </Stack>
); );
} }

View File

@@ -6,8 +6,8 @@ exports[`IndexingPolicyRefreshComponent renders 1`] = `
> >
<Text <Text
styles={ styles={
Object { {
"root": Object { "root": {
"color": "windowtext", "color": "windowtext",
"fontSize": 14, "fontSize": 14,
}, },

View File

@@ -3,7 +3,7 @@
exports[`AddMongoIndexComponent renders 1`] = ` exports[`AddMongoIndexComponent renders 1`] = `
<Stack <Stack
tokens={ tokens={
Object { {
"childrenGap": 5, "childrenGap": 5,
} }
} }
@@ -11,7 +11,7 @@ exports[`AddMongoIndexComponent renders 1`] = `
<Stack <Stack
horizontal={true} horizontal={true}
tokens={ tokens={
Object { {
"childrenGap": 20, "childrenGap": 20,
} }
} }
@@ -21,8 +21,8 @@ exports[`AddMongoIndexComponent renders 1`] = `
componentRef={[Function]} componentRef={[Function]}
onChange={[Function]} onChange={[Function]}
styles={ styles={
Object { {
"root": Object { "root": {
"paddingLeft": 10, "paddingLeft": 10,
"width": 210, "width": 210,
}, },
@@ -34,12 +34,12 @@ exports[`AddMongoIndexComponent renders 1`] = `
ariaLabel="Index Type 1" ariaLabel="Index Type 1"
onChange={[Function]} onChange={[Function]}
options={ options={
Array [ [
Object { {
"key": "Single", "key": "Single",
"text": "Single Field", "text": "Single Field",
}, },
Object { {
"key": "Wildcard", "key": "Wildcard",
"text": "Wildcard", "text": "Wildcard",
}, },
@@ -48,8 +48,8 @@ exports[`AddMongoIndexComponent renders 1`] = `
placeholder="Select an index type" placeholder="Select an index type"
selectedKey="Single" selectedKey="Single"
styles={ styles={
Object { {
"dropdown": Object { "dropdown": {
"paddingleft": 10, "paddingleft": 10,
"width": 202, "width": 202,
}, },
@@ -60,7 +60,7 @@ exports[`AddMongoIndexComponent renders 1`] = `
ariaLabel="Undo Button 1" ariaLabel="Undo Button 1"
disabled={false} disabled={false}
iconProps={ iconProps={
Object { {
"iconName": "Undo", "iconName": "Undo",
} }
} }
@@ -70,8 +70,8 @@ exports[`AddMongoIndexComponent renders 1`] = `
<StyledMessageBar <StyledMessageBar
messageBarType={1} messageBarType={1}
styles={ styles={
Object { {
"root": Object { "root": {
"marginLeft": 10, "marginLeft": 10,
}, },
} }

View File

@@ -9,7 +9,7 @@ exports[`MongoIndexingPolicyComponent error shown for collection with compound i
exports[`MongoIndexingPolicyComponent renders 1`] = ` exports[`MongoIndexingPolicyComponent renders 1`] = `
<Stack <Stack
tokens={ tokens={
Object { {
"childrenGap": 20, "childrenGap": 20,
} }
} }
@@ -29,14 +29,14 @@ exports[`MongoIndexingPolicyComponent renders 1`] = `
</Text> </Text>
<Stack <Stack
styles={ styles={
Object { {
"root": Object { "root": {
"width": 600, "width": 600,
}, },
} }
} }
tokens={ tokens={
Object { {
"childrenGap": 5, "childrenGap": 5,
} }
} }
@@ -47,8 +47,8 @@ exports[`MongoIndexingPolicyComponent renders 1`] = `
> >
<StyledWithViewportComponent <StyledWithViewportComponent
columns={ columns={
Array [ [
Object { {
"fieldName": "definition", "fieldName": "definition",
"isResizable": true, "isResizable": true,
"key": "definition", "key": "definition",
@@ -56,7 +56,7 @@ exports[`MongoIndexingPolicyComponent renders 1`] = `
"minWidth": 100, "minWidth": 100,
"name": "Definition", "name": "Definition",
}, },
Object { {
"fieldName": "type", "fieldName": "type",
"isResizable": true, "isResizable": true,
"key": "type", "key": "type",
@@ -64,7 +64,7 @@ exports[`MongoIndexingPolicyComponent renders 1`] = `
"minWidth": 100, "minWidth": 100,
"name": "Type", "name": "Type",
}, },
Object { {
"fieldName": "actionButton", "fieldName": "actionButton",
"isResizable": true, "isResizable": true,
"key": "actionButton", "key": "actionButton",
@@ -75,15 +75,15 @@ exports[`MongoIndexingPolicyComponent renders 1`] = `
] ]
} }
disableSelectionZone={true} disableSelectionZone={true}
items={Array []} items={[]}
layoutMode={1} layoutMode={1}
onRenderRow={[Function]} onRenderRow={[Function]}
selectionMode={0} selectionMode={0}
styles={ styles={
Object { {
"root": Object { "root": {
"selectors": Object { "selectors": {
".ms-FocusZone": Object { ".ms-FocusZone": {
"paddingTop": 0, "paddingTop": 0,
}, },
}, },
@@ -93,14 +93,14 @@ exports[`MongoIndexingPolicyComponent renders 1`] = `
/> />
<Stack <Stack
styles={ styles={
Object { {
"root": Object { "root": {
"width": 600, "width": 600,
}, },
} }
} }
tokens={ tokens={
Object { {
"childrenGap": 10, "childrenGap": 10,
} }
} }
@@ -117,11 +117,11 @@ exports[`MongoIndexingPolicyComponent renders 1`] = `
</Stack> </Stack>
<Separator <Separator
styles={ styles={
Object { {
"root": Array [ "root": [
Object { {
"selectors": Object { "selectors": {
"::before": Object { "::before": {
"background": undefined, "background": undefined,
}, },
}, },
@@ -132,8 +132,8 @@ exports[`MongoIndexingPolicyComponent renders 1`] = `
/> />
<Stack <Stack
styles={ styles={
Object { {
"root": Object { "root": {
"width": 600, "width": 600,
}, },
} }

View File

@@ -1,18 +1,10 @@
import { shallow } from "enzyme";
import ko from "knockout";
import React from "react";
import * as Constants from "../../../../Common/Constants"; import * as Constants from "../../../../Common/Constants";
import * as DataModels from "../../../../Contracts/DataModels";
import { updateUserContext } from "../../../../UserContext"; import { updateUserContext } from "../../../../UserContext";
import Explorer from "../../../Explorer"; import Explorer from "../../../Explorer";
import { throughputUnit } from "../SettingsRenderUtils";
import { collection } from "../TestUtils"; import { collection } from "../TestUtils";
import { ScaleComponent, ScaleComponentProps } from "./ScaleComponent"; import { ScaleComponent, ScaleComponentProps } from "./ScaleComponent";
import { ThroughputInputAutoPilotV3Component } from "./ThroughputInputComponents/ThroughputInputAutoPilotV3Component";
describe("ScaleComponent", () => { describe("ScaleComponent", () => {
const targetThroughput = 6000;
const baseProps: ScaleComponentProps = { const baseProps: ScaleComponentProps = {
collection: collection, collection: collection,
database: undefined, database: undefined,
@@ -36,39 +28,8 @@ describe("ScaleComponent", () => {
onScaleDiscardableChange: () => { onScaleDiscardableChange: () => {
return; return;
}, },
initialNotification: {
description: `Throughput update for ${targetThroughput} ${throughputUnit}`,
} as DataModels.Notification,
}; };
it("renders with correct initial notification", () => {
let wrapper = shallow(<ScaleComponent {...baseProps} />);
expect(wrapper).toMatchSnapshot();
expect(wrapper.exists(ThroughputInputAutoPilotV3Component)).toEqual(true);
expect(wrapper.exists("#throughputApplyLongDelayMessage")).toEqual(true);
expect(wrapper.exists("#throughputApplyShortDelayMessage")).toEqual(false);
expect(wrapper.find("#throughputApplyLongDelayMessage").html()).toContain(targetThroughput);
const newCollection = { ...collection };
const maxThroughput = 5000;
newCollection.offer = ko.observable({
manualThroughput: undefined,
autoscaleMaxThroughput: maxThroughput,
minimumThroughput: 400,
id: "offer",
offerReplacePending: true,
});
const newProps = {
...baseProps,
initialNotification: undefined as DataModels.Notification,
collection: newCollection,
};
wrapper = shallow(<ScaleComponent {...newProps} />);
expect(wrapper.exists("#throughputApplyShortDelayMessage")).toEqual(true);
expect(wrapper.exists("#throughputApplyLongDelayMessage")).toEqual(false);
expect(wrapper.find("#throughputApplyShortDelayMessage").html()).toContain(maxThroughput);
});
it("autoScale disabled", () => { it("autoScale disabled", () => {
const scaleComponent = new ScaleComponent(baseProps); const scaleComponent = new ScaleComponent(baseProps);
expect(scaleComponent.isAutoScaleEnabled()).toEqual(false); expect(scaleComponent.isAutoScaleEnabled()).toEqual(false);

View File

@@ -10,7 +10,6 @@ import * as AutoPilotUtils from "../../../../Utils/AutoPilotUtils";
import { isRunningOnNationalCloud } from "../../../../Utils/CloudUtils"; import { isRunningOnNationalCloud } from "../../../../Utils/CloudUtils";
import { import {
getTextFieldStyles, getTextFieldStyles,
getThroughputApplyLongDelayMessage,
getThroughputApplyShortDelayMessage, getThroughputApplyShortDelayMessage,
subComponentStackProps, subComponentStackProps,
throughputUnit, throughputUnit,
@@ -34,7 +33,6 @@ export interface ScaleComponentProps {
onMaxAutoPilotThroughputChange: (newThroughput: number) => void; onMaxAutoPilotThroughputChange: (newThroughput: number) => void;
onScaleSaveableChange: (isScaleSaveable: boolean) => void; onScaleSaveableChange: (isScaleSaveable: boolean) => void;
onScaleDiscardableChange: (isScaleDiscardable: boolean) => void; onScaleDiscardableChange: (isScaleDiscardable: boolean) => void;
initialNotification: DataModels.Notification;
throughputError?: string; throughputError?: string;
} }
@@ -102,10 +100,6 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
}; };
public getInitialNotificationElement = (): JSX.Element => { public getInitialNotificationElement = (): JSX.Element => {
if (this.props.initialNotification) {
return this.getLongDelayMessage();
}
if (this.offer?.offerReplacePending) { if (this.offer?.offerReplacePending) {
const throughput = this.offer.manualThroughput || this.offer.autoscaleMaxThroughput; const throughput = this.offer.manualThroughput || this.offer.autoscaleMaxThroughput;
return getThroughputApplyShortDelayMessage( return getThroughputApplyShortDelayMessage(
@@ -120,26 +114,6 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
return undefined; return undefined;
}; };
public getLongDelayMessage = (): JSX.Element => {
const matches: string[] = this.props.initialNotification?.description.match(
`Throughput update for (.*) ${throughputUnit}`,
);
const throughput = this.props.throughputBaseline;
const targetThroughput: number = matches.length > 1 && Number(matches[1]);
if (targetThroughput) {
return getThroughputApplyLongDelayMessage(
this.props.wasAutopilotOriginallySet,
throughput,
throughputUnit,
this.databaseId,
this.collectionId,
targetThroughput,
);
}
return <></>;
};
private getThroughputInputComponent = (): JSX.Element => ( private getThroughputInputComponent = (): JSX.Element => (
<ThroughputInputAutoPilotV3Component <ThroughputInputAutoPilotV3Component
databaseAccount={userContext?.databaseAccount} databaseAccount={userContext?.databaseAccount}

View File

@@ -3,14 +3,14 @@
exports[`ComputedPropertiesComponent renders 1`] = ` exports[`ComputedPropertiesComponent renders 1`] = `
<Stack <Stack
tokens={ tokens={
Object { {
"childrenGap": 5, "childrenGap": 5,
} }
} }
> >
<Text <Text
style={ style={
Object { {
"marginBottom": "10px", "marginBottom": "10px",
"marginLeft": "30px", "marginLeft": "30px",
} }
@@ -29,7 +29,7 @@ exports[`ComputedPropertiesComponent renders 1`] = `
  about how to define computed properties and how to use them.   about how to define computed properties and how to use them.
</Text> </Text>
<div <div
className="settingsV2IndexingPolicyEditor" className="settingsV2Editor"
tabIndex={0} tabIndex={0}
/> />
</Stack> </Stack>

View File

@@ -3,7 +3,7 @@
exports[`ConflictResolutionComponent Path text field displayed 1`] = ` exports[`ConflictResolutionComponent Path text field displayed 1`] = `
<Stack <Stack
tokens={ tokens={
Object { {
"childrenGap": 20, "childrenGap": 20,
} }
} }
@@ -12,12 +12,12 @@ exports[`ConflictResolutionComponent Path text field displayed 1`] = `
label="Mode" label="Mode"
onChange={[Function]} onChange={[Function]}
options={ options={
Array [ [
Object { {
"key": "LastWriterWins", "key": "LastWriterWins",
"text": "Last Write Wins (default)", "text": "Last Write Wins (default)",
}, },
Object { {
"key": "Custom", "key": "Custom",
"text": "Merge Procedure (custom)", "text": "Merge Procedure (custom)",
}, },
@@ -25,19 +25,19 @@ exports[`ConflictResolutionComponent Path text field displayed 1`] = `
} }
selectedKey="LastWriterWins" selectedKey="LastWriterWins"
styles={ styles={
Object { {
"flexContainer": Array [ "flexContainer": [
Object { {
"columnGap": "default", "columnGap": "default",
"display": "default", "display": "default",
"selectors": Object { "selectors": {
".ms-ChoiceField-field.is-checked::after": Object { ".ms-ChoiceField-field.is-checked::after": {
"borderColor": undefined, "borderColor": undefined,
}, },
".ms-ChoiceField-field.is-checked::before": Object { ".ms-ChoiceField-field.is-checked::before": {
"borderColor": undefined, "borderColor": undefined,
}, },
".ms-ChoiceField-wrapper label": Object { ".ms-ChoiceField-wrapper label": {
"fontFamily": undefined, "fontFamily": undefined,
"fontSize": 14, "fontSize": 14,
"padding": "2px 5px", "padding": "2px 5px",
@@ -55,12 +55,12 @@ exports[`ConflictResolutionComponent Path text field displayed 1`] = `
onChange={[Function]} onChange={[Function]}
onRenderLabel={[Function]} onRenderLabel={[Function]}
styles={ styles={
Object { {
"fieldGroup": Object { "fieldGroup": {
"borderColor": "", "borderColor": "",
"height": 25, "height": 25,
"selectors": Object { "selectors": {
":disabled": Object { ":disabled": {
"backgroundColor": undefined, "backgroundColor": undefined,
"borderColor": undefined, "borderColor": undefined,
}, },
@@ -77,7 +77,7 @@ exports[`ConflictResolutionComponent Path text field displayed 1`] = `
exports[`ConflictResolutionComponent Sproc text field displayed 1`] = ` exports[`ConflictResolutionComponent Sproc text field displayed 1`] = `
<Stack <Stack
tokens={ tokens={
Object { {
"childrenGap": 20, "childrenGap": 20,
} }
} }
@@ -86,12 +86,12 @@ exports[`ConflictResolutionComponent Sproc text field displayed 1`] = `
label="Mode" label="Mode"
onChange={[Function]} onChange={[Function]}
options={ options={
Array [ [
Object { {
"key": "LastWriterWins", "key": "LastWriterWins",
"text": "Last Write Wins (default)", "text": "Last Write Wins (default)",
}, },
Object { {
"key": "Custom", "key": "Custom",
"text": "Merge Procedure (custom)", "text": "Merge Procedure (custom)",
}, },
@@ -99,19 +99,19 @@ exports[`ConflictResolutionComponent Sproc text field displayed 1`] = `
} }
selectedKey="Custom" selectedKey="Custom"
styles={ styles={
Object { {
"flexContainer": Array [ "flexContainer": [
Object { {
"columnGap": "default", "columnGap": "default",
"display": "default", "display": "default",
"selectors": Object { "selectors": {
".ms-ChoiceField-field.is-checked::after": Object { ".ms-ChoiceField-field.is-checked::after": {
"borderColor": "", "borderColor": "",
}, },
".ms-ChoiceField-field.is-checked::before": Object { ".ms-ChoiceField-field.is-checked::before": {
"borderColor": "", "borderColor": "",
}, },
".ms-ChoiceField-wrapper label": Object { ".ms-ChoiceField-wrapper label": {
"fontFamily": undefined, "fontFamily": undefined,
"fontSize": 14, "fontSize": 14,
"padding": "2px 5px", "padding": "2px 5px",
@@ -129,12 +129,12 @@ exports[`ConflictResolutionComponent Sproc text field displayed 1`] = `
onChange={[Function]} onChange={[Function]}
onRenderLabel={[Function]} onRenderLabel={[Function]}
styles={ styles={
Object { {
"fieldGroup": Object { "fieldGroup": {
"borderColor": "", "borderColor": "",
"height": 25, "height": 25,
"selectors": Object { "selectors": {
":disabled": Object { ":disabled": {
"backgroundColor": undefined, "backgroundColor": undefined,
"borderColor": undefined, "borderColor": undefined,
}, },

View File

@@ -3,7 +3,7 @@
exports[`IndexingPolicyComponent renders 1`] = ` exports[`IndexingPolicyComponent renders 1`] = `
<Stack <Stack
tokens={ tokens={
Object { {
"childrenGap": 5, "childrenGap": 5,
} }
} }
@@ -12,7 +12,7 @@ exports[`IndexingPolicyComponent renders 1`] = `
refreshIndexTransformationProgress={[Function]} refreshIndexTransformationProgress={[Function]}
/> />
<div <div
className="settingsV2IndexingPolicyEditor" className="settingsV2Editor"
tabIndex={0} tabIndex={0}
/> />
</Stack> </Stack>

View File

@@ -1,64 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ScaleComponent renders with correct initial notification 1`] = `
<Stack
tokens={
Object {
"childrenGap": 20,
}
}
>
<StyledMessageBar
messageBarType={5}
>
<Text
id="throughputApplyLongDelayMessage"
styles={
Object {
"root": Object {
"color": "windowtext",
"fontSize": 14,
},
}
}
>
A request to increase the throughput is currently in progress. This operation will take 1-3 business days to complete. View the latest status in Notifications.
<br />
Database: test, Container: test
, Current autoscale throughput: 100 - 1000 RU/s, Target autoscale throughput: 600 - 6000 RU/s
</Text>
</StyledMessageBar>
<Stack
tokens={
Object {
"childrenGap": 20,
}
}
>
<ThroughputInputAutoPilotV3Component
canExceedMaximumValue={true}
collectionName="test"
databaseName="test"
isAutoPilotSelected={false}
isEmulator={false}
isEnabled={true}
isFixed={false}
label="Throughput (6,000 - unlimited RU/s)"
maxAutoPilotThroughput={4000}
maxAutoPilotThroughputBaseline={4000}
maximum={1000000}
minimum={6000}
onAutoPilotSelected={[Function]}
onMaxAutoPilotThroughputChange={[Function]}
onScaleDiscardableChange={[Function]}
onScaleSaveableChange={[Function]}
onThroughputChange={[Function]}
spendAckChecked={false}
throughput={1000}
throughputBaseline={1000}
usageSizeInKB={100}
wasAutopilotOriginallySet={true}
/>
</Stack>
</Stack>
`;

View File

@@ -5,7 +5,7 @@ exports[`ToolTipLabelComponent renders 1`] = `
<Stack <Stack
horizontal={true} horizontal={true}
tokens={ tokens={
Object { {
"childrenGap": 6, "childrenGap": 6,
} }
} }
@@ -13,7 +13,7 @@ exports[`ToolTipLabelComponent renders 1`] = `
> >
<Text <Text
style={ style={
Object { {
"fontWeight": 600, "fontWeight": 600,
} }
} }
@@ -22,7 +22,7 @@ exports[`ToolTipLabelComponent renders 1`] = `
</Text> </Text>
<StyledTooltipHostBase <StyledTooltipHostBase
calloutProps={ calloutProps={
Object { {
"gapSpace": 0, "gapSpace": 0,
} }
} }
@@ -33,8 +33,8 @@ exports[`ToolTipLabelComponent renders 1`] = `
} }
directionalHint={12} directionalHint={12}
styles={ styles={
Object { {
"root": Object { "root": {
"display": "inline-block", "display": "inline-block",
"float": "right", "float": "right",
}, },
@@ -45,8 +45,8 @@ exports[`ToolTipLabelComponent renders 1`] = `
ariaLabel="Info" ariaLabel="Info"
iconName="Info" iconName="Info"
styles={ styles={
Object { {
"root": Object { "root": {
"marginBottom": -3, "marginBottom": -3,
}, },
} }

View File

@@ -47,6 +47,7 @@ export enum SettingsV2TabTypes {
IndexingPolicyTab, IndexingPolicyTab,
PartitionKeyTab, PartitionKeyTab,
ComputedPropertiesTab, ComputedPropertiesTab,
ContainerVectorPolicyTab,
} }
export interface IsComponentDirtyResult { export interface IsComponentDirtyResult {
@@ -66,7 +67,6 @@ export interface MongoNotificationMessage {
export const hasDatabaseSharedThroughput = (collection: ViewModels.Collection): boolean => { export const hasDatabaseSharedThroughput = (collection: ViewModels.Collection): boolean => {
const database: ViewModels.Database = collection.getDatabase(); const database: ViewModels.Database = collection.getDatabase();
console.log(database?.isDatabaseShared(), collection.offer());
return database?.isDatabaseShared() && !collection.offer(); return database?.isDatabaseShared() && !collection.offer();
}; };
@@ -152,7 +152,9 @@ export const getTabTitle = (tab: SettingsV2TabTypes): string => {
case SettingsV2TabTypes.PartitionKeyTab: case SettingsV2TabTypes.PartitionKeyTab:
return "Partition Keys (preview)"; return "Partition Keys (preview)";
case SettingsV2TabTypes.ComputedPropertiesTab: case SettingsV2TabTypes.ComputedPropertiesTab:
return "Computed Properties (preview)"; return "Computed Properties";
case SettingsV2TabTypes.ContainerVectorPolicyTab:
return "Container Vector Policy (preview)";
default: default:
throw new Error(`Unknown tab ${tab}`); throw new Error(`Unknown tab ${tab}`);
} }

View File

@@ -16,28 +16,27 @@ exports[`SettingsComponent renders 1`] = `
itemKey="ScaleTab" itemKey="ScaleTab"
key="ScaleTab" key="ScaleTab"
style={ style={
Object { {
"marginTop": 20, "marginTop": 20,
} }
} }
> >
<ScaleComponent <ScaleComponent
collection={ collection={
Object { {
"analyticalStorageTtl": [Function], "analyticalStorageTtl": [Function],
"changeFeedPolicy": [Function], "changeFeedPolicy": [Function],
"computedProperties": [Function], "computedProperties": [Function],
"conflictResolutionPolicy": [Function], "conflictResolutionPolicy": [Function],
"container": Explorer { "container": Explorer {
"_isInitializingNotebooks": false, "_isInitializingNotebooks": false,
"_resetNotebookWorkspace": [Function],
"isFixedCollectionWithSharedThroughputSupported": [Function], "isFixedCollectionWithSharedThroughputSupported": [Function],
"isTabsContentExpanded": [Function], "isTabsContentExpanded": [Function],
"onRefreshDatabasesKeyPress": [Function], "onRefreshDatabasesKeyPress": [Function],
"onRefreshResourcesClick": [Function], "onRefreshResourcesClick": [Function],
"phoenixClient": PhoenixClient { "phoenixClient": PhoenixClient {
"armResourceId": undefined, "armResourceId": undefined,
"retryOptions": Object { "retryOptions": {
"maxTimeout": 5000, "maxTimeout": 5000,
"minTimeout": 5000, "minTimeout": 5000,
"retries": 3, "retries": 3,
@@ -61,16 +60,16 @@ exports[`SettingsComponent renders 1`] = `
"id": [Function], "id": [Function],
"indexingPolicy": [Function], "indexingPolicy": [Function],
"offer": [Function], "offer": [Function],
"partitionKey": Object { "partitionKey": {
"kind": "hash", "kind": "hash",
"paths": Array [], "paths": [],
"version": 2, "version": 2,
}, },
"partitionKeyProperties": Array [ "partitionKeyProperties": [
"partitionKey", "partitionKey",
], ],
"readSettings": [Function], "readSettings": [Function],
"uniqueKeyPolicy": Object {}, "uniqueKeyPolicy": {},
"usageSizeInKB": [Function], "usageSizeInKB": [Function],
} }
} }
@@ -91,7 +90,7 @@ exports[`SettingsComponent renders 1`] = `
itemKey="SubSettingsTab" itemKey="SubSettingsTab"
key="SubSettingsTab" key="SubSettingsTab"
style={ style={
Object { {
"marginTop": 20, "marginTop": 20,
} }
} }
@@ -101,21 +100,20 @@ exports[`SettingsComponent renders 1`] = `
changeFeedPolicyBaseline="Off" changeFeedPolicyBaseline="Off"
changeFeedPolicyVisible={false} changeFeedPolicyVisible={false}
collection={ collection={
Object { {
"analyticalStorageTtl": [Function], "analyticalStorageTtl": [Function],
"changeFeedPolicy": [Function], "changeFeedPolicy": [Function],
"computedProperties": [Function], "computedProperties": [Function],
"conflictResolutionPolicy": [Function], "conflictResolutionPolicy": [Function],
"container": Explorer { "container": Explorer {
"_isInitializingNotebooks": false, "_isInitializingNotebooks": false,
"_resetNotebookWorkspace": [Function],
"isFixedCollectionWithSharedThroughputSupported": [Function], "isFixedCollectionWithSharedThroughputSupported": [Function],
"isTabsContentExpanded": [Function], "isTabsContentExpanded": [Function],
"onRefreshDatabasesKeyPress": [Function], "onRefreshDatabasesKeyPress": [Function],
"onRefreshResourcesClick": [Function], "onRefreshResourcesClick": [Function],
"phoenixClient": PhoenixClient { "phoenixClient": PhoenixClient {
"armResourceId": undefined, "armResourceId": undefined,
"retryOptions": Object { "retryOptions": {
"maxTimeout": 5000, "maxTimeout": 5000,
"minTimeout": 5000, "minTimeout": 5000,
"retries": 3, "retries": 3,
@@ -139,16 +137,16 @@ exports[`SettingsComponent renders 1`] = `
"id": [Function], "id": [Function],
"indexingPolicy": [Function], "indexingPolicy": [Function],
"offer": [Function], "offer": [Function],
"partitionKey": Object { "partitionKey": {
"kind": "hash", "kind": "hash",
"paths": Array [], "paths": [],
"version": 2, "version": 2,
}, },
"partitionKeyProperties": Array [ "partitionKeyProperties": [
"partitionKey", "partitionKey",
], ],
"readSettings": [Function], "readSettings": [Function],
"uniqueKeyPolicy": Object {}, "uniqueKeyPolicy": {},
"usageSizeInKB": [Function], "usageSizeInKB": [Function],
} }
} }
@@ -176,28 +174,29 @@ exports[`SettingsComponent renders 1`] = `
itemKey="IndexingPolicyTab" itemKey="IndexingPolicyTab"
key="IndexingPolicyTab" key="IndexingPolicyTab"
style={ style={
Object { {
"marginTop": 20, "marginTop": 20,
} }
} }
> >
<IndexingPolicyComponent <IndexingPolicyComponent
indexingPolicyContent={ indexingPolicyContent={
Object { {
"automatic": true, "automatic": true,
"excludedPaths": Array [], "excludedPaths": [],
"includedPaths": Array [], "includedPaths": [],
"indexingMode": "consistent", "indexingMode": "consistent",
} }
} }
indexingPolicyContentBaseline={ indexingPolicyContentBaseline={
Object { {
"automatic": true, "automatic": true,
"excludedPaths": Array [], "excludedPaths": [],
"includedPaths": Array [], "includedPaths": [],
"indexingMode": "consistent", "indexingMode": "consistent",
} }
} }
isVectorSearchEnabled={false}
logIndexingPolicySuccessMessage={[Function]} logIndexingPolicySuccessMessage={[Function]}
onIndexingPolicyContentChange={[Function]} onIndexingPolicyContentChange={[Function]}
onIndexingPolicyDirtyChange={[Function]} onIndexingPolicyDirtyChange={[Function]}
@@ -211,28 +210,27 @@ exports[`SettingsComponent renders 1`] = `
itemKey="PartitionKeyTab" itemKey="PartitionKeyTab"
key="PartitionKeyTab" key="PartitionKeyTab"
style={ style={
Object { {
"marginTop": 20, "marginTop": 20,
} }
} }
> >
<PartitionKeyComponent <PartitionKeyComponent
collection={ collection={
Object { {
"analyticalStorageTtl": [Function], "analyticalStorageTtl": [Function],
"changeFeedPolicy": [Function], "changeFeedPolicy": [Function],
"computedProperties": [Function], "computedProperties": [Function],
"conflictResolutionPolicy": [Function], "conflictResolutionPolicy": [Function],
"container": Explorer { "container": Explorer {
"_isInitializingNotebooks": false, "_isInitializingNotebooks": false,
"_resetNotebookWorkspace": [Function],
"isFixedCollectionWithSharedThroughputSupported": [Function], "isFixedCollectionWithSharedThroughputSupported": [Function],
"isTabsContentExpanded": [Function], "isTabsContentExpanded": [Function],
"onRefreshDatabasesKeyPress": [Function], "onRefreshDatabasesKeyPress": [Function],
"onRefreshResourcesClick": [Function], "onRefreshResourcesClick": [Function],
"phoenixClient": PhoenixClient { "phoenixClient": PhoenixClient {
"armResourceId": undefined, "armResourceId": undefined,
"retryOptions": Object { "retryOptions": {
"maxTimeout": 5000, "maxTimeout": 5000,
"minTimeout": 5000, "minTimeout": 5000,
"retries": 3, "retries": 3,
@@ -256,30 +254,29 @@ exports[`SettingsComponent renders 1`] = `
"id": [Function], "id": [Function],
"indexingPolicy": [Function], "indexingPolicy": [Function],
"offer": [Function], "offer": [Function],
"partitionKey": Object { "partitionKey": {
"kind": "hash", "kind": "hash",
"paths": Array [], "paths": [],
"version": 2, "version": 2,
}, },
"partitionKeyProperties": Array [ "partitionKeyProperties": [
"partitionKey", "partitionKey",
], ],
"readSettings": [Function], "readSettings": [Function],
"uniqueKeyPolicy": Object {}, "uniqueKeyPolicy": {},
"usageSizeInKB": [Function], "usageSizeInKB": [Function],
} }
} }
explorer={ explorer={
Explorer { Explorer {
"_isInitializingNotebooks": false, "_isInitializingNotebooks": false,
"_resetNotebookWorkspace": [Function],
"isFixedCollectionWithSharedThroughputSupported": [Function], "isFixedCollectionWithSharedThroughputSupported": [Function],
"isTabsContentExpanded": [Function], "isTabsContentExpanded": [Function],
"onRefreshDatabasesKeyPress": [Function], "onRefreshDatabasesKeyPress": [Function],
"onRefreshResourcesClick": [Function], "onRefreshResourcesClick": [Function],
"phoenixClient": PhoenixClient { "phoenixClient": PhoenixClient {
"armResourceId": undefined, "armResourceId": undefined,
"retryOptions": Object { "retryOptions": {
"maxTimeout": 5000, "maxTimeout": 5000,
"minTimeout": 5000, "minTimeout": 5000,
"retries": 3, "retries": 3,
@@ -300,27 +297,27 @@ exports[`SettingsComponent renders 1`] = `
/> />
</PivotItem> </PivotItem>
<PivotItem <PivotItem
headerText="Computed Properties (preview)" headerText="Computed Properties"
itemKey="ComputedPropertiesTab" itemKey="ComputedPropertiesTab"
key="ComputedPropertiesTab" key="ComputedPropertiesTab"
style={ style={
Object { {
"marginTop": 20, "marginTop": 20,
} }
} }
> >
<ComputedPropertiesComponent <ComputedPropertiesComponent
computedPropertiesContent={ computedPropertiesContent={
Array [ [
Object { {
"name": "queryName", "name": "queryName",
"query": "query", "query": "query",
}, },
] ]
} }
computedPropertiesContentBaseline={ computedPropertiesContentBaseline={
Array [ [
Object { {
"name": "queryName", "name": "queryName",
"query": "query", "query": "query",
}, },

View File

@@ -5,7 +5,7 @@ exports[`SettingsUtils functions render 1`] = `
<Stack> <Stack>
<Text <Text
style={ style={
Object { {
"fontWeight": 600, "fontWeight": 600,
} }
} }
@@ -14,7 +14,7 @@ exports[`SettingsUtils functions render 1`] = `
</Text> </Text>
<Text <Text
style={ style={
Object { {
"fontWeight": 600, "fontWeight": 600,
"marginTop": 15, "marginTop": 15,
} }
@@ -25,7 +25,7 @@ exports[`SettingsUtils functions render 1`] = `
<Stack <Stack
id="throughputSpendElement" id="throughputSpendElement"
style={ style={
Object { {
"marginTop": 5, "marginTop": 5,
} }
} }
@@ -49,7 +49,7 @@ exports[`SettingsUtils functions render 1`] = `
</Stack> </Stack>
<Text <Text
style={ style={
Object { {
"marginTop": 15, "marginTop": 15,
} }
} }
@@ -63,8 +63,8 @@ exports[`SettingsUtils functions render 1`] = `
<Text <Text
id="manualToAutoscaleDisclaimerElement" id="manualToAutoscaleDisclaimerElement"
styles={ styles={
Object { {
"root": Object { "root": {
"color": "windowtext", "color": "windowtext",
"fontSize": 14, "fontSize": 14,
}, },
@@ -81,8 +81,8 @@ exports[`SettingsUtils functions render 1`] = `
</Text> </Text>
<Text <Text
styles={ styles={
Object { {
"root": Object { "root": {
"color": "windowtext", "color": "windowtext",
"fontSize": 14, "fontSize": 14,
}, },
@@ -102,8 +102,8 @@ exports[`SettingsUtils functions render 1`] = `
<Text <Text
id="updateThroughputDelayedApplyWarningMessage" id="updateThroughputDelayedApplyWarningMessage"
styles={ styles={
Object { {
"root": Object { "root": {
"color": "windowtext", "color": "windowtext",
"fontSize": 14, "fontSize": 14,
}, },
@@ -114,8 +114,8 @@ exports[`SettingsUtils functions render 1`] = `
</Text> </Text>
<Text <Text
styles={ styles={
Object { {
"root": Object { "root": {
"color": "windowtext", "color": "windowtext",
"fontSize": 14, "fontSize": 14,
}, },
@@ -134,8 +134,8 @@ exports[`SettingsUtils functions render 1`] = `
<Text <Text
id="throughputApplyShortDelayMessage" id="throughputApplyShortDelayMessage"
styles={ styles={
Object { {
"root": Object { "root": {
"color": "windowtext", "color": "windowtext",
"fontSize": 14, "fontSize": 14,
}, },
@@ -150,8 +150,8 @@ exports[`SettingsUtils functions render 1`] = `
<Text <Text
id="throughputApplyLongDelayMessage" id="throughputApplyLongDelayMessage"
styles={ styles={
Object { {
"root": Object { "root": {
"color": "windowtext", "color": "windowtext",
"fontSize": 14, "fontSize": 14,
}, },
@@ -165,8 +165,8 @@ exports[`SettingsUtils functions render 1`] = `
</Text> </Text>
<Text <Text
styles={ styles={
Object { {
"root": Object { "root": {
"color": "windowtext", "color": "windowtext",
"fontSize": 14, "fontSize": 14,
}, },
@@ -179,8 +179,8 @@ exports[`SettingsUtils functions render 1`] = `
</Text> </Text>
<Text <Text
styles={ styles={
Object { {
"root": Object { "root": {
"color": "windowtext", "color": "windowtext",
"fontSize": 14, "fontSize": 14,
}, },
@@ -191,8 +191,8 @@ exports[`SettingsUtils functions render 1`] = `
</Text> </Text>
<Text <Text
styles={ styles={
Object { {
"root": Object { "root": {
"color": "windowtext", "color": "windowtext",
"fontSize": 14, "fontSize": 14,
}, },
@@ -211,8 +211,8 @@ exports[`SettingsUtils functions render 1`] = `
</Text> </Text>
<Text <Text
styles={ styles={
Object { {
"root": Object { "root": {
"color": "windowtext", "color": "windowtext",
"fontSize": 14, "fontSize": 14,
}, },
@@ -247,15 +247,15 @@ exports[`SettingsUtils functions render 1`] = `
<Stack <Stack
horizontal={true} horizontal={true}
tokens={ tokens={
Object { {
"childrenGap": 5, "childrenGap": 5,
} }
} }
> >
<Text <Text
styles={ styles={
Object { {
"root": Object { "root": {
"color": "windowtext", "color": "windowtext",
"fontSize": 14, "fontSize": 14,
}, },
@@ -270,8 +270,8 @@ exports[`SettingsUtils functions render 1`] = `
</Stack> </Stack>
<Text <Text
styles={ styles={
Object { {
"root": Object { "root": {
"color": "windowtext", "color": "windowtext",
"fontSize": 14, "fontSize": 14,
}, },
@@ -287,8 +287,8 @@ exports[`SettingsUtils functions render 1`] = `
</Text> </Text>
<Text <Text
styles={ styles={
Object { {
"root": Object { "root": {
"color": "windowtext", "color": "windowtext",
"fontSize": 14, "fontSize": 14,
}, },

View File

@@ -14,7 +14,6 @@ import {
TextField, TextField,
Toggle, Toggle,
} from "@fluentui/react"; } from "@fluentui/react";
import { TFunction } from "i18next";
import * as React from "react"; import * as React from "react";
import { import {
ChoiceItem, ChoiceItem,
@@ -100,7 +99,7 @@ export interface SmartUiComponentProps {
onInputChange: (input: AnyDisplay, newValue: InputType) => void; onInputChange: (input: AnyDisplay, newValue: InputType) => void;
onError: (hasError: boolean) => void; onError: (hasError: boolean) => void;
disabled: boolean; disabled: boolean;
getTranslation: TFunction; getTranslation: (messageKey: string, namespace?: string) => string;
} }
interface SmartUiComponentState { interface SmartUiComponentState {

View File

@@ -4,7 +4,7 @@ exports[`SmartUiComponent disable all inputs 1`] = `
<Stack <Stack
className="widgetRendererContainer" className="widgetRendererContainer"
tokens={ tokens={
Object { {
"childrenGap": 10, "childrenGap": 10,
} }
} }
@@ -16,7 +16,7 @@ exports[`SmartUiComponent disable all inputs 1`] = `
<Stack <Stack
className="widgetRendererContainer" className="widgetRendererContainer"
tokens={ tokens={
Object { {
"childrenGap": 10, "childrenGap": 10,
} }
} }
@@ -28,7 +28,7 @@ exports[`SmartUiComponent disable all inputs 1`] = `
aria-labelledby="description-label" aria-labelledby="description-label"
id="description-text-display" id="description-text-display"
style={ style={
Object { {
"whiteSpace": "pre-line", "whiteSpace": "pre-line",
} }
} }
@@ -53,7 +53,7 @@ exports[`SmartUiComponent disable all inputs 1`] = `
<Stack <Stack
className="widgetRendererContainer" className="widgetRendererContainer"
tokens={ tokens={
Object { {
"childrenGap": 10, "childrenGap": 10,
} }
} }
@@ -70,14 +70,14 @@ exports[`SmartUiComponent disable all inputs 1`] = `
</StyledLabelBase> </StyledLabelBase>
<Stack <Stack
styles={ styles={
Object { {
"root": Object { "root": {
"width": 400, "width": 400,
}, },
} }
} }
tokens={ tokens={
Object { {
"childrenGap": 2, "childrenGap": 2,
} }
} }
@@ -107,7 +107,7 @@ exports[`SmartUiComponent disable all inputs 1`] = `
<Stack <Stack
className="widgetRendererContainer" className="widgetRendererContainer"
tokens={ tokens={
Object { {
"childrenGap": 10, "childrenGap": 10,
} }
} }
@@ -133,11 +133,11 @@ exports[`SmartUiComponent disable all inputs 1`] = `
onChange={[Function]} onChange={[Function]}
step={10} step={10}
styles={ styles={
Object { {
"root": Object { "root": {
"width": 400, "width": 400,
}, },
"valueLabel": Object { "valueLabel": {
"color": "#393939", "color": "#393939",
"fontFamily": "wf_segoe-ui_normal, 'Segoe UI', 'Segoe WP', Tahoma, Arial, sans-serif", "fontFamily": "wf_segoe-ui_normal, 'Segoe UI', 'Segoe WP', Tahoma, Arial, sans-serif",
"fontSize": 12, "fontSize": 12,
@@ -157,7 +157,7 @@ exports[`SmartUiComponent disable all inputs 1`] = `
<Stack <Stack
className="widgetRendererContainer" className="widgetRendererContainer"
tokens={ tokens={
Object { {
"childrenGap": 10, "childrenGap": 10,
} }
} }
@@ -178,7 +178,7 @@ exports[`SmartUiComponent disable all inputs 1`] = `
<Stack <Stack
className="widgetRendererContainer" className="widgetRendererContainer"
tokens={ tokens={
Object { {
"childrenGap": 10, "childrenGap": 10,
} }
} }
@@ -199,8 +199,8 @@ exports[`SmartUiComponent disable all inputs 1`] = `
id="containerId-textField-input" id="containerId-textField-input"
onChange={[Function]} onChange={[Function]}
styles={ styles={
Object { {
"root": Object { "root": {
"width": 400, "width": 400,
}, },
} }
@@ -219,7 +219,7 @@ exports[`SmartUiComponent disable all inputs 1`] = `
<Stack <Stack
className="widgetRendererContainer" className="widgetRendererContainer"
tokens={ tokens={
Object { {
"childrenGap": 10, "childrenGap": 10,
} }
} }
@@ -243,8 +243,8 @@ exports[`SmartUiComponent disable all inputs 1`] = `
onChange={[Function]} onChange={[Function]}
onText="Enabled" onText="Enabled"
styles={ styles={
Object { {
"root": Object { "root": {
"width": 400, "width": 400,
}, },
} }
@@ -261,7 +261,7 @@ exports[`SmartUiComponent disable all inputs 1`] = `
<Stack <Stack
className="widgetRendererContainer" className="widgetRendererContainer"
tokens={ tokens={
Object { {
"childrenGap": 10, "childrenGap": 10,
} }
} }
@@ -282,16 +282,16 @@ exports[`SmartUiComponent disable all inputs 1`] = `
id="database-dropdown-input" id="database-dropdown-input"
onChange={[Function]} onChange={[Function]}
options={ options={
Array [ [
Object { {
"key": "db1", "key": "db1",
"text": "Database 1", "text": "Database 1",
}, },
Object { {
"key": "db2", "key": "db2",
"text": "Database 2", "text": "Database 2",
}, },
Object { {
"key": "db3", "key": "db3",
"text": "Database 3", "text": "Database 3",
}, },
@@ -299,13 +299,13 @@ exports[`SmartUiComponent disable all inputs 1`] = `
} }
selectedKey="db2" selectedKey="db2"
styles={ styles={
Object { {
"dropdown": Object { "dropdown": {
"color": "#393939", "color": "#393939",
"fontFamily": "wf_segoe-ui_normal, 'Segoe UI', 'Segoe WP', Tahoma, Arial, sans-serif", "fontFamily": "wf_segoe-ui_normal, 'Segoe UI', 'Segoe WP', Tahoma, Arial, sans-serif",
"fontSize": 12, "fontSize": 12,
}, },
"root": Object { "root": {
"width": 400, "width": 400,
}, },
} }
@@ -323,7 +323,7 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
<Stack <Stack
className="widgetRendererContainer" className="widgetRendererContainer"
tokens={ tokens={
Object { {
"childrenGap": 10, "childrenGap": 10,
} }
} }
@@ -335,7 +335,7 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
<Stack <Stack
className="widgetRendererContainer" className="widgetRendererContainer"
tokens={ tokens={
Object { {
"childrenGap": 10, "childrenGap": 10,
} }
} }
@@ -347,7 +347,7 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
aria-labelledby="description-label" aria-labelledby="description-label"
id="description-text-display" id="description-text-display"
style={ style={
Object { {
"whiteSpace": "pre-line", "whiteSpace": "pre-line",
} }
} }
@@ -372,7 +372,7 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
<Stack <Stack
className="widgetRendererContainer" className="widgetRendererContainer"
tokens={ tokens={
Object { {
"childrenGap": 10, "childrenGap": 10,
} }
} }
@@ -389,14 +389,14 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
</StyledLabelBase> </StyledLabelBase>
<Stack <Stack
styles={ styles={
Object { {
"root": Object { "root": {
"width": 400, "width": 400,
}, },
} }
} }
tokens={ tokens={
Object { {
"childrenGap": 2, "childrenGap": 2,
} }
} }
@@ -425,7 +425,7 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
<Stack <Stack
className="widgetRendererContainer" className="widgetRendererContainer"
tokens={ tokens={
Object { {
"childrenGap": 10, "childrenGap": 10,
} }
} }
@@ -450,11 +450,11 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
onChange={[Function]} onChange={[Function]}
step={10} step={10}
styles={ styles={
Object { {
"root": Object { "root": {
"width": 400, "width": 400,
}, },
"valueLabel": Object { "valueLabel": {
"color": "#393939", "color": "#393939",
"fontFamily": "wf_segoe-ui_normal, 'Segoe UI', 'Segoe WP', Tahoma, Arial, sans-serif", "fontFamily": "wf_segoe-ui_normal, 'Segoe UI', 'Segoe WP', Tahoma, Arial, sans-serif",
"fontSize": 12, "fontSize": 12,
@@ -474,7 +474,7 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
<Stack <Stack
className="widgetRendererContainer" className="widgetRendererContainer"
tokens={ tokens={
Object { {
"childrenGap": 10, "childrenGap": 10,
} }
} }
@@ -495,7 +495,7 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
<Stack <Stack
className="widgetRendererContainer" className="widgetRendererContainer"
tokens={ tokens={
Object { {
"childrenGap": 10, "childrenGap": 10,
} }
} }
@@ -515,8 +515,8 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
id="containerId-textField-input" id="containerId-textField-input"
onChange={[Function]} onChange={[Function]}
styles={ styles={
Object { {
"root": Object { "root": {
"width": 400, "width": 400,
}, },
} }
@@ -535,7 +535,7 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
<Stack <Stack
className="widgetRendererContainer" className="widgetRendererContainer"
tokens={ tokens={
Object { {
"childrenGap": 10, "childrenGap": 10,
} }
} }
@@ -558,8 +558,8 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
onChange={[Function]} onChange={[Function]}
onText="Enabled" onText="Enabled"
styles={ styles={
Object { {
"root": Object { "root": {
"width": 400, "width": 400,
}, },
} }
@@ -576,7 +576,7 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
<Stack <Stack
className="widgetRendererContainer" className="widgetRendererContainer"
tokens={ tokens={
Object { {
"childrenGap": 10, "childrenGap": 10,
} }
} }
@@ -596,16 +596,16 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
id="database-dropdown-input" id="database-dropdown-input"
onChange={[Function]} onChange={[Function]}
options={ options={
Array [ [
Object { {
"key": "db1", "key": "db1",
"text": "Database 1", "text": "Database 1",
}, },
Object { {
"key": "db2", "key": "db2",
"text": "Database 2", "text": "Database 2",
}, },
Object { {
"key": "db3", "key": "db3",
"text": "Database 3", "text": "Database 3",
}, },
@@ -613,13 +613,13 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
} }
selectedKey="db2" selectedKey="db2"
styles={ styles={
Object { {
"dropdown": Object { "dropdown": {
"color": "#393939", "color": "#393939",
"fontFamily": "wf_segoe-ui_normal, 'Segoe UI', 'Segoe WP', Tahoma, Arial, sans-serif", "fontFamily": "wf_segoe-ui_normal, 'Segoe UI', 'Segoe WP', Tahoma, Arial, sans-serif",
"fontSize": 12, "fontSize": 12,
}, },
"root": Object { "root": {
"width": 400, "width": 400,
}, },
} }

View File

@@ -1,28 +1,16 @@
@import "../../../../less/Common/Constants"; @import "../../../../less/Common/Constants";
.tabComponentContainer { .tabComponentContainer {
height: 100%; height: 100%;
.flex-display(); .flex-display();
.flex-direction(); .flex-direction();
.tabSwitch { .tabSwitch {
margin-left: @LargeSpace; margin-left: @LargeSpace;
margin-bottom: 20px; margin-bottom: 20px;
.tab { .tab {
margin-right: @MediumSpace; margin-right: @MediumSpace;
} }
}
.toggleSwitch {
.toggleSwitch();
}
.selectedToggle {
.selectedToggle();
}
.unselectedToggle {
.unselectedToggle();
}
}
} }

View File

@@ -1,5 +1,5 @@
import * as React from "react"; import * as React from "react";
import { AccessibleElement } from "../../Controls/AccessibleElement/AccessibleElement"; import { Pivot, PivotItem } from "@fluentui/react";
import "./TabComponent.less"; import "./TabComponent.less";
export interface TabContent { export interface TabContent {
@@ -35,58 +35,36 @@ export class TabComponent extends React.Component<TabComponentProps> {
} }
private setActiveTab(index: number): void { private setActiveTab(index: number): void {
this.setState({ activeTabIndex: index });
this.props.onTabIndexChange(index); this.props.onTabIndexChange(index);
} }
private renderTabTitles(): JSX.Element[] {
return this.props.tabs.map((tab: Tab, index: number) => {
if (!tab.isVisible()) {
return <React.Fragment key={index} />;
}
let className = "toggleSwitch";
let ariaselected;
if (index === this.props.currentTabIndex) {
className += " selectedToggle";
ariaselected = true;
} else {
className += " unselectedToggle";
ariaselected = false;
}
return (
<div className="tab" key={index}>
<AccessibleElement
as="span"
className={className}
role="tab"
onActivated={() => this.setActiveTab(index)}
aria-label={`Select tab: ${tab.title}`}
aria-selected={ariaselected}
>
{tab.title}
</AccessibleElement>
</div>
);
});
}
public render(): JSX.Element { public render(): JSX.Element {
const currentTabContent = this.props.tabs[this.props.currentTabIndex].content; const { tabs, currentTabIndex, hideHeader } = this.props;
const currentTabContent = tabs[currentTabIndex].content;
let className = "tabComponentContent"; let className = "tabComponentContent";
if (currentTabContent.className) { if (currentTabContent.className) {
className += ` ${currentTabContent.className}`; className += ` ${currentTabContent.className}`;
} }
return ( return (
<div className="tabComponentContainer"> <div className="tabComponentContainer">
{!this.props.hideHeader && ( <div className="tabs tabSwitch">
<div className="tabs tabSwitch" role="tablist"> {!hideHeader && (
{this.renderTabTitles()} <Pivot
</div> aria-label="Tab navigation"
)} selectedKey={currentTabIndex.toString()}
<div className={className}>{currentTabContent.render()}</div> linkSize="normal"
onLinkClick={(item) => this.setActiveTab(parseInt(item?.props.itemKey || ""))}
>
{tabs.map((tab: Tab, index: number) => {
if (!tab.isVisible()) {
return null; // Skip rendering invisible tabs
}
return <PivotItem key={index} headerText={tab.title} itemKey={index.toString()} />;
})}
</Pivot>
)}
</div>
<div className={className}>{tabs[currentTabIndex].content.render()}</div>
</div> </div>
); );
} }

View File

@@ -20,6 +20,10 @@
outline-offset: 2px; outline-offset: 2px;
} }
.outlineNone{
outline: none !important;
}
.copyQuery:focus::after, .copyQuery:focus::after,
.deleteQuery:focus::after { .deleteQuery:focus::after {
outline: none !important; outline: none !important;

View File

@@ -223,6 +223,7 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
<Text variant="small" aria-label="capacity calculator of azure cosmos db"> <Text variant="small" aria-label="capacity calculator of azure cosmos db">
Estimate your required RU/s with{" "} Estimate your required RU/s with{" "}
<Link <Link
className="underlinedLink outlineNone"
target="_blank" target="_blank"
href="https://cosmos.azure.com/capacitycalculator/" href="https://cosmos.azure.com/capacitycalculator/"
aria-label="capacity calculator of azure cosmos db" aria-label="capacity calculator of azure cosmos db"
@@ -271,7 +272,12 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
<Stack className="throughputInputSpacing"> <Stack className="throughputInputSpacing">
<Text variant="small" aria-label="ruDescription"> <Text variant="small" aria-label="ruDescription">
Estimate your required RU/s with&nbsp; Estimate your required RU/s with&nbsp;
<Link target="_blank" href="https://cosmos.azure.com/capacitycalculator/" aria-label="Capacity calculator"> <Link
className="underlinedLink"
target="_blank"
href="https://cosmos.azure.com/capacitycalculator/"
aria-label="Capacity calculator"
>
capacity calculator capacity calculator
</Link> </Link>
. .

View File

@@ -30,7 +30,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
aria-label="Throughput header" aria-label="Throughput header"
key=".0:$.$.1" key=".0:$.$.1"
style={ style={
Object { {
"fontWeight": 600, "fontWeight": 600,
"lineHeight": "20px", "lineHeight": "20px",
} }
@@ -41,7 +41,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
aria-label="Throughput header" aria-label="Throughput header"
className="css-110" className="css-110"
style={ style={
Object { {
"fontWeight": 600, "fontWeight": 600,
"lineHeight": "20px", "lineHeight": "20px",
} }
@@ -62,9 +62,9 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
delay={1} delay={1}
styles={[Function]} styles={[Function]}
theme={ theme={
Object { {
"disableGlobalClassNames": false, "disableGlobalClassNames": false,
"effects": Object { "effects": {
"elevation16": "0 6.4px 14.4px 0 rgba(0, 0, 0, 0.132), 0 1.2px 3.6px 0 rgba(0, 0, 0, 0.108)", "elevation16": "0 6.4px 14.4px 0 rgba(0, 0, 0, 0.132), 0 1.2px 3.6px 0 rgba(0, 0, 0, 0.108)",
"elevation4": "0 1.6px 3.6px 0 rgba(0, 0, 0, 0.132), 0 0.3px 0.9px 0 rgba(0, 0, 0, 0.108)", "elevation4": "0 1.6px 3.6px 0 rgba(0, 0, 0, 0.132), 0 0.3px 0.9px 0 rgba(0, 0, 0, 0.108)",
"elevation64": "0 25.6px 57.6px 0 rgba(0, 0, 0, 0.22), 0 4.8px 14.4px 0 rgba(0, 0, 0, 0.18)", "elevation64": "0 25.6px 57.6px 0 rgba(0, 0, 0, 0.22), 0 4.8px 14.4px 0 rgba(0, 0, 0, 0.18)",
@@ -73,92 +73,92 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
"roundedCorner4": "4px", "roundedCorner4": "4px",
"roundedCorner6": "6px", "roundedCorner6": "6px",
}, },
"fonts": Object { "fonts": {
"large": Object { "large": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "18px", "fontSize": "18px",
"fontWeight": 400, "fontWeight": 400,
}, },
"medium": Object { "medium": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "14px", "fontSize": "14px",
"fontWeight": 400, "fontWeight": 400,
}, },
"mediumPlus": Object { "mediumPlus": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "16px", "fontSize": "16px",
"fontWeight": 400, "fontWeight": 400,
}, },
"mega": Object { "mega": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "68px", "fontSize": "68px",
"fontWeight": 600, "fontWeight": 600,
}, },
"small": Object { "small": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "12px", "fontSize": "12px",
"fontWeight": 400, "fontWeight": 400,
}, },
"smallPlus": Object { "smallPlus": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "12px", "fontSize": "12px",
"fontWeight": 400, "fontWeight": 400,
}, },
"superLarge": Object { "superLarge": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "42px", "fontSize": "42px",
"fontWeight": 600, "fontWeight": 600,
}, },
"tiny": Object { "tiny": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "10px", "fontSize": "10px",
"fontWeight": 400, "fontWeight": 400,
}, },
"xLarge": Object { "xLarge": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "20px", "fontSize": "20px",
"fontWeight": 600, "fontWeight": 600,
}, },
"xLargePlus": Object { "xLargePlus": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "24px", "fontSize": "24px",
"fontWeight": 600, "fontWeight": 600,
}, },
"xSmall": Object { "xSmall": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "10px", "fontSize": "10px",
"fontWeight": 400, "fontWeight": 400,
}, },
"xxLarge": Object { "xxLarge": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "28px", "fontSize": "28px",
"fontWeight": 600, "fontWeight": 600,
}, },
"xxLargePlus": Object { "xxLargePlus": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
@@ -167,7 +167,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
}, },
}, },
"isInverted": false, "isInverted": false,
"palette": Object { "palette": {
"accent": "#0078d4", "accent": "#0078d4",
"black": "#000000", "black": "#000000",
"blackTranslucent40": "rgba(0,0,0,.4)", "blackTranslucent40": "rgba(0,0,0,.4)",
@@ -220,7 +220,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
"yellowLight": "#fff100", "yellowLight": "#fff100",
}, },
"rtl": undefined, "rtl": undefined,
"semanticColors": Object { "semanticColors": {
"accentButtonBackground": "#0078d4", "accentButtonBackground": "#0078d4",
"accentButtonText": "#ffffff", "accentButtonText": "#ffffff",
"actionLink": "#323130", "actionLink": "#323130",
@@ -325,7 +325,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
"warningIcon": "#797775", "warningIcon": "#797775",
"warningText": "#323130", "warningText": "#323130",
}, },
"spacing": Object { "spacing": {
"l1": "20px", "l1": "20px",
"l2": "32px", "l2": "32px",
"m": "16px", "m": "16px",
@@ -357,9 +357,9 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
styles={[Function]} styles={[Function]}
tabIndex={0} tabIndex={0}
theme={ theme={
Object { {
"disableGlobalClassNames": false, "disableGlobalClassNames": false,
"effects": Object { "effects": {
"elevation16": "0 6.4px 14.4px 0 rgba(0, 0, 0, 0.132), 0 1.2px 3.6px 0 rgba(0, 0, 0, 0.108)", "elevation16": "0 6.4px 14.4px 0 rgba(0, 0, 0, 0.132), 0 1.2px 3.6px 0 rgba(0, 0, 0, 0.108)",
"elevation4": "0 1.6px 3.6px 0 rgba(0, 0, 0, 0.132), 0 0.3px 0.9px 0 rgba(0, 0, 0, 0.108)", "elevation4": "0 1.6px 3.6px 0 rgba(0, 0, 0, 0.132), 0 0.3px 0.9px 0 rgba(0, 0, 0, 0.108)",
"elevation64": "0 25.6px 57.6px 0 rgba(0, 0, 0, 0.22), 0 4.8px 14.4px 0 rgba(0, 0, 0, 0.18)", "elevation64": "0 25.6px 57.6px 0 rgba(0, 0, 0, 0.22), 0 4.8px 14.4px 0 rgba(0, 0, 0, 0.18)",
@@ -368,92 +368,92 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
"roundedCorner4": "4px", "roundedCorner4": "4px",
"roundedCorner6": "6px", "roundedCorner6": "6px",
}, },
"fonts": Object { "fonts": {
"large": Object { "large": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "18px", "fontSize": "18px",
"fontWeight": 400, "fontWeight": 400,
}, },
"medium": Object { "medium": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "14px", "fontSize": "14px",
"fontWeight": 400, "fontWeight": 400,
}, },
"mediumPlus": Object { "mediumPlus": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "16px", "fontSize": "16px",
"fontWeight": 400, "fontWeight": 400,
}, },
"mega": Object { "mega": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "68px", "fontSize": "68px",
"fontWeight": 600, "fontWeight": 600,
}, },
"small": Object { "small": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "12px", "fontSize": "12px",
"fontWeight": 400, "fontWeight": 400,
}, },
"smallPlus": Object { "smallPlus": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "12px", "fontSize": "12px",
"fontWeight": 400, "fontWeight": 400,
}, },
"superLarge": Object { "superLarge": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "42px", "fontSize": "42px",
"fontWeight": 600, "fontWeight": 600,
}, },
"tiny": Object { "tiny": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "10px", "fontSize": "10px",
"fontWeight": 400, "fontWeight": 400,
}, },
"xLarge": Object { "xLarge": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "20px", "fontSize": "20px",
"fontWeight": 600, "fontWeight": 600,
}, },
"xLargePlus": Object { "xLargePlus": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "24px", "fontSize": "24px",
"fontWeight": 600, "fontWeight": 600,
}, },
"xSmall": Object { "xSmall": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "10px", "fontSize": "10px",
"fontWeight": 400, "fontWeight": 400,
}, },
"xxLarge": Object { "xxLarge": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "28px", "fontSize": "28px",
"fontWeight": 600, "fontWeight": 600,
}, },
"xxLargePlus": Object { "xxLargePlus": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
@@ -462,7 +462,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
}, },
}, },
"isInverted": false, "isInverted": false,
"palette": Object { "palette": {
"accent": "#0078d4", "accent": "#0078d4",
"black": "#000000", "black": "#000000",
"blackTranslucent40": "rgba(0,0,0,.4)", "blackTranslucent40": "rgba(0,0,0,.4)",
@@ -515,7 +515,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
"yellowLight": "#fff100", "yellowLight": "#fff100",
}, },
"rtl": undefined, "rtl": undefined,
"semanticColors": Object { "semanticColors": {
"accentButtonBackground": "#0078d4", "accentButtonBackground": "#0078d4",
"accentButtonText": "#ffffff", "accentButtonText": "#ffffff",
"actionLink": "#323130", "actionLink": "#323130",
@@ -620,7 +620,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
"warningIcon": "#797775", "warningIcon": "#797775",
"warningText": "#323130", "warningText": "#323130",
}, },
"spacing": Object { "spacing": {
"l1": "20px", "l1": "20px",
"l2": "32px", "l2": "32px",
"m": "16px", "m": "16px",
@@ -645,7 +645,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
hidden={true} hidden={true}
id="tooltip0" id="tooltip0"
style={ style={
Object { {
"border": 0, "border": 0,
"height": 1, "height": 1,
"margin": -1, "margin": -1,
@@ -733,18 +733,20 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
<StyledLinkBase <StyledLinkBase
aria-label="capacity calculator of azure cosmos db" aria-label="capacity calculator of azure cosmos db"
className="underlinedLink outlineNone"
href="https://cosmos.azure.com/capacitycalculator/" href="https://cosmos.azure.com/capacitycalculator/"
target="_blank" target="_blank"
> >
<LinkBase <LinkBase
aria-label="capacity calculator of azure cosmos db" aria-label="capacity calculator of azure cosmos db"
className="underlinedLink outlineNone"
href="https://cosmos.azure.com/capacitycalculator/" href="https://cosmos.azure.com/capacitycalculator/"
styles={[Function]} styles={[Function]}
target="_blank" target="_blank"
theme={ theme={
Object { {
"disableGlobalClassNames": false, "disableGlobalClassNames": false,
"effects": Object { "effects": {
"elevation16": "0 6.4px 14.4px 0 rgba(0, 0, 0, 0.132), 0 1.2px 3.6px 0 rgba(0, 0, 0, 0.108)", "elevation16": "0 6.4px 14.4px 0 rgba(0, 0, 0, 0.132), 0 1.2px 3.6px 0 rgba(0, 0, 0, 0.108)",
"elevation4": "0 1.6px 3.6px 0 rgba(0, 0, 0, 0.132), 0 0.3px 0.9px 0 rgba(0, 0, 0, 0.108)", "elevation4": "0 1.6px 3.6px 0 rgba(0, 0, 0, 0.132), 0 0.3px 0.9px 0 rgba(0, 0, 0, 0.108)",
"elevation64": "0 25.6px 57.6px 0 rgba(0, 0, 0, 0.22), 0 4.8px 14.4px 0 rgba(0, 0, 0, 0.18)", "elevation64": "0 25.6px 57.6px 0 rgba(0, 0, 0, 0.22), 0 4.8px 14.4px 0 rgba(0, 0, 0, 0.18)",
@@ -753,92 +755,92 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
"roundedCorner4": "4px", "roundedCorner4": "4px",
"roundedCorner6": "6px", "roundedCorner6": "6px",
}, },
"fonts": Object { "fonts": {
"large": Object { "large": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "18px", "fontSize": "18px",
"fontWeight": 400, "fontWeight": 400,
}, },
"medium": Object { "medium": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "14px", "fontSize": "14px",
"fontWeight": 400, "fontWeight": 400,
}, },
"mediumPlus": Object { "mediumPlus": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "16px", "fontSize": "16px",
"fontWeight": 400, "fontWeight": 400,
}, },
"mega": Object { "mega": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "68px", "fontSize": "68px",
"fontWeight": 600, "fontWeight": 600,
}, },
"small": Object { "small": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "12px", "fontSize": "12px",
"fontWeight": 400, "fontWeight": 400,
}, },
"smallPlus": Object { "smallPlus": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "12px", "fontSize": "12px",
"fontWeight": 400, "fontWeight": 400,
}, },
"superLarge": Object { "superLarge": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "42px", "fontSize": "42px",
"fontWeight": 600, "fontWeight": 600,
}, },
"tiny": Object { "tiny": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "10px", "fontSize": "10px",
"fontWeight": 400, "fontWeight": 400,
}, },
"xLarge": Object { "xLarge": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "20px", "fontSize": "20px",
"fontWeight": 600, "fontWeight": 600,
}, },
"xLargePlus": Object { "xLargePlus": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "24px", "fontSize": "24px",
"fontWeight": 600, "fontWeight": 600,
}, },
"xSmall": Object { "xSmall": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "10px", "fontSize": "10px",
"fontWeight": 400, "fontWeight": 400,
}, },
"xxLarge": Object { "xxLarge": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "28px", "fontSize": "28px",
"fontWeight": 600, "fontWeight": 600,
}, },
"xxLargePlus": Object { "xxLargePlus": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
@@ -847,7 +849,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
}, },
}, },
"isInverted": false, "isInverted": false,
"palette": Object { "palette": {
"accent": "#0078d4", "accent": "#0078d4",
"black": "#000000", "black": "#000000",
"blackTranslucent40": "rgba(0,0,0,.4)", "blackTranslucent40": "rgba(0,0,0,.4)",
@@ -900,7 +902,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
"yellowLight": "#fff100", "yellowLight": "#fff100",
}, },
"rtl": undefined, "rtl": undefined,
"semanticColors": Object { "semanticColors": {
"accentButtonBackground": "#0078d4", "accentButtonBackground": "#0078d4",
"accentButtonText": "#ffffff", "accentButtonText": "#ffffff",
"actionLink": "#323130", "actionLink": "#323130",
@@ -1005,7 +1007,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
"warningIcon": "#797775", "warningIcon": "#797775",
"warningText": "#323130", "warningText": "#323130",
}, },
"spacing": Object { "spacing": {
"l1": "20px", "l1": "20px",
"l2": "32px", "l2": "32px",
"m": "16px", "m": "16px",
@@ -1017,7 +1019,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
> >
<a <a
aria-label="capacity calculator of azure cosmos db" aria-label="capacity calculator of azure cosmos db"
className="ms-Link root-117" className="ms-Link underlinedLink outlineNone root-117"
href="https://cosmos.azure.com/capacitycalculator/" href="https://cosmos.azure.com/capacitycalculator/"
onClick={[Function]} onClick={[Function]}
target="_blank" target="_blank"
@@ -1040,7 +1042,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
aria-label="maxRUDescription" aria-label="maxRUDescription"
key=".0:$.$.0" key=".0:$.$.0"
style={ style={
Object { {
"fontWeight": 600, "fontWeight": 600,
"lineHeight": "20px", "lineHeight": "20px",
} }
@@ -1051,7 +1053,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
aria-label="maxRUDescription" aria-label="maxRUDescription"
className="css-110" className="css-110"
style={ style={
Object { {
"fontWeight": 600, "fontWeight": 600,
"lineHeight": "20px", "lineHeight": "20px",
} }
@@ -1073,9 +1075,9 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
delay={1} delay={1}
styles={[Function]} styles={[Function]}
theme={ theme={
Object { {
"disableGlobalClassNames": false, "disableGlobalClassNames": false,
"effects": Object { "effects": {
"elevation16": "0 6.4px 14.4px 0 rgba(0, 0, 0, 0.132), 0 1.2px 3.6px 0 rgba(0, 0, 0, 0.108)", "elevation16": "0 6.4px 14.4px 0 rgba(0, 0, 0, 0.132), 0 1.2px 3.6px 0 rgba(0, 0, 0, 0.108)",
"elevation4": "0 1.6px 3.6px 0 rgba(0, 0, 0, 0.132), 0 0.3px 0.9px 0 rgba(0, 0, 0, 0.108)", "elevation4": "0 1.6px 3.6px 0 rgba(0, 0, 0, 0.132), 0 0.3px 0.9px 0 rgba(0, 0, 0, 0.108)",
"elevation64": "0 25.6px 57.6px 0 rgba(0, 0, 0, 0.22), 0 4.8px 14.4px 0 rgba(0, 0, 0, 0.18)", "elevation64": "0 25.6px 57.6px 0 rgba(0, 0, 0, 0.22), 0 4.8px 14.4px 0 rgba(0, 0, 0, 0.18)",
@@ -1084,92 +1086,92 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
"roundedCorner4": "4px", "roundedCorner4": "4px",
"roundedCorner6": "6px", "roundedCorner6": "6px",
}, },
"fonts": Object { "fonts": {
"large": Object { "large": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "18px", "fontSize": "18px",
"fontWeight": 400, "fontWeight": 400,
}, },
"medium": Object { "medium": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "14px", "fontSize": "14px",
"fontWeight": 400, "fontWeight": 400,
}, },
"mediumPlus": Object { "mediumPlus": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "16px", "fontSize": "16px",
"fontWeight": 400, "fontWeight": 400,
}, },
"mega": Object { "mega": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "68px", "fontSize": "68px",
"fontWeight": 600, "fontWeight": 600,
}, },
"small": Object { "small": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "12px", "fontSize": "12px",
"fontWeight": 400, "fontWeight": 400,
}, },
"smallPlus": Object { "smallPlus": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "12px", "fontSize": "12px",
"fontWeight": 400, "fontWeight": 400,
}, },
"superLarge": Object { "superLarge": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "42px", "fontSize": "42px",
"fontWeight": 600, "fontWeight": 600,
}, },
"tiny": Object { "tiny": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "10px", "fontSize": "10px",
"fontWeight": 400, "fontWeight": 400,
}, },
"xLarge": Object { "xLarge": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "20px", "fontSize": "20px",
"fontWeight": 600, "fontWeight": 600,
}, },
"xLargePlus": Object { "xLargePlus": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "24px", "fontSize": "24px",
"fontWeight": 600, "fontWeight": 600,
}, },
"xSmall": Object { "xSmall": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "10px", "fontSize": "10px",
"fontWeight": 400, "fontWeight": 400,
}, },
"xxLarge": Object { "xxLarge": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "28px", "fontSize": "28px",
"fontWeight": 600, "fontWeight": 600,
}, },
"xxLargePlus": Object { "xxLargePlus": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
@@ -1178,7 +1180,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
}, },
}, },
"isInverted": false, "isInverted": false,
"palette": Object { "palette": {
"accent": "#0078d4", "accent": "#0078d4",
"black": "#000000", "black": "#000000",
"blackTranslucent40": "rgba(0,0,0,.4)", "blackTranslucent40": "rgba(0,0,0,.4)",
@@ -1231,7 +1233,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
"yellowLight": "#fff100", "yellowLight": "#fff100",
}, },
"rtl": undefined, "rtl": undefined,
"semanticColors": Object { "semanticColors": {
"accentButtonBackground": "#0078d4", "accentButtonBackground": "#0078d4",
"accentButtonText": "#ffffff", "accentButtonText": "#ffffff",
"actionLink": "#323130", "actionLink": "#323130",
@@ -1336,7 +1338,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
"warningIcon": "#797775", "warningIcon": "#797775",
"warningText": "#323130", "warningText": "#323130",
}, },
"spacing": Object { "spacing": {
"l1": "20px", "l1": "20px",
"l2": "32px", "l2": "32px",
"m": "16px", "m": "16px",
@@ -1368,9 +1370,9 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
styles={[Function]} styles={[Function]}
tabIndex={0} tabIndex={0}
theme={ theme={
Object { {
"disableGlobalClassNames": false, "disableGlobalClassNames": false,
"effects": Object { "effects": {
"elevation16": "0 6.4px 14.4px 0 rgba(0, 0, 0, 0.132), 0 1.2px 3.6px 0 rgba(0, 0, 0, 0.108)", "elevation16": "0 6.4px 14.4px 0 rgba(0, 0, 0, 0.132), 0 1.2px 3.6px 0 rgba(0, 0, 0, 0.108)",
"elevation4": "0 1.6px 3.6px 0 rgba(0, 0, 0, 0.132), 0 0.3px 0.9px 0 rgba(0, 0, 0, 0.108)", "elevation4": "0 1.6px 3.6px 0 rgba(0, 0, 0, 0.132), 0 0.3px 0.9px 0 rgba(0, 0, 0, 0.108)",
"elevation64": "0 25.6px 57.6px 0 rgba(0, 0, 0, 0.22), 0 4.8px 14.4px 0 rgba(0, 0, 0, 0.18)", "elevation64": "0 25.6px 57.6px 0 rgba(0, 0, 0, 0.22), 0 4.8px 14.4px 0 rgba(0, 0, 0, 0.18)",
@@ -1379,92 +1381,92 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
"roundedCorner4": "4px", "roundedCorner4": "4px",
"roundedCorner6": "6px", "roundedCorner6": "6px",
}, },
"fonts": Object { "fonts": {
"large": Object { "large": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "18px", "fontSize": "18px",
"fontWeight": 400, "fontWeight": 400,
}, },
"medium": Object { "medium": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "14px", "fontSize": "14px",
"fontWeight": 400, "fontWeight": 400,
}, },
"mediumPlus": Object { "mediumPlus": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "16px", "fontSize": "16px",
"fontWeight": 400, "fontWeight": 400,
}, },
"mega": Object { "mega": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "68px", "fontSize": "68px",
"fontWeight": 600, "fontWeight": 600,
}, },
"small": Object { "small": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "12px", "fontSize": "12px",
"fontWeight": 400, "fontWeight": 400,
}, },
"smallPlus": Object { "smallPlus": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "12px", "fontSize": "12px",
"fontWeight": 400, "fontWeight": 400,
}, },
"superLarge": Object { "superLarge": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "42px", "fontSize": "42px",
"fontWeight": 600, "fontWeight": 600,
}, },
"tiny": Object { "tiny": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "10px", "fontSize": "10px",
"fontWeight": 400, "fontWeight": 400,
}, },
"xLarge": Object { "xLarge": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "20px", "fontSize": "20px",
"fontWeight": 600, "fontWeight": 600,
}, },
"xLargePlus": Object { "xLargePlus": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "24px", "fontSize": "24px",
"fontWeight": 600, "fontWeight": 600,
}, },
"xSmall": Object { "xSmall": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "10px", "fontSize": "10px",
"fontWeight": 400, "fontWeight": 400,
}, },
"xxLarge": Object { "xxLarge": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "28px", "fontSize": "28px",
"fontWeight": 600, "fontWeight": 600,
}, },
"xxLargePlus": Object { "xxLargePlus": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
@@ -1473,7 +1475,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
}, },
}, },
"isInverted": false, "isInverted": false,
"palette": Object { "palette": {
"accent": "#0078d4", "accent": "#0078d4",
"black": "#000000", "black": "#000000",
"blackTranslucent40": "rgba(0,0,0,.4)", "blackTranslucent40": "rgba(0,0,0,.4)",
@@ -1526,7 +1528,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
"yellowLight": "#fff100", "yellowLight": "#fff100",
}, },
"rtl": undefined, "rtl": undefined,
"semanticColors": Object { "semanticColors": {
"accentButtonBackground": "#0078d4", "accentButtonBackground": "#0078d4",
"accentButtonText": "#ffffff", "accentButtonText": "#ffffff",
"actionLink": "#323130", "actionLink": "#323130",
@@ -1631,7 +1633,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
"warningIcon": "#797775", "warningIcon": "#797775",
"warningText": "#323130", "warningText": "#323130",
}, },
"spacing": Object { "spacing": {
"l1": "20px", "l1": "20px",
"l2": "32px", "l2": "32px",
"m": "16px", "m": "16px",
@@ -1656,7 +1658,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
hidden={true} hidden={true}
id="tooltip1" id="tooltip1"
style={ style={
Object { {
"border": 0, "border": 0,
"height": 1, "height": 1,
"margin": -1, "margin": -1,
@@ -1688,11 +1690,11 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
required={true} required={true}
step={1000} step={1000}
styles={ styles={
Object { {
"field": Object { "field": {
"fontSize": 12, "fontSize": 12,
}, },
"fieldGroup": Object { "fieldGroup": {
"height": 27, "height": 27,
"width": 300, "width": 300,
}, },
@@ -1714,9 +1716,9 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
step={1000} step={1000}
styles={[Function]} styles={[Function]}
theme={ theme={
Object { {
"disableGlobalClassNames": false, "disableGlobalClassNames": false,
"effects": Object { "effects": {
"elevation16": "0 6.4px 14.4px 0 rgba(0, 0, 0, 0.132), 0 1.2px 3.6px 0 rgba(0, 0, 0, 0.108)", "elevation16": "0 6.4px 14.4px 0 rgba(0, 0, 0, 0.132), 0 1.2px 3.6px 0 rgba(0, 0, 0, 0.108)",
"elevation4": "0 1.6px 3.6px 0 rgba(0, 0, 0, 0.132), 0 0.3px 0.9px 0 rgba(0, 0, 0, 0.108)", "elevation4": "0 1.6px 3.6px 0 rgba(0, 0, 0, 0.132), 0 0.3px 0.9px 0 rgba(0, 0, 0, 0.108)",
"elevation64": "0 25.6px 57.6px 0 rgba(0, 0, 0, 0.22), 0 4.8px 14.4px 0 rgba(0, 0, 0, 0.18)", "elevation64": "0 25.6px 57.6px 0 rgba(0, 0, 0, 0.22), 0 4.8px 14.4px 0 rgba(0, 0, 0, 0.18)",
@@ -1725,92 +1727,92 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
"roundedCorner4": "4px", "roundedCorner4": "4px",
"roundedCorner6": "6px", "roundedCorner6": "6px",
}, },
"fonts": Object { "fonts": {
"large": Object { "large": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "18px", "fontSize": "18px",
"fontWeight": 400, "fontWeight": 400,
}, },
"medium": Object { "medium": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "14px", "fontSize": "14px",
"fontWeight": 400, "fontWeight": 400,
}, },
"mediumPlus": Object { "mediumPlus": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "16px", "fontSize": "16px",
"fontWeight": 400, "fontWeight": 400,
}, },
"mega": Object { "mega": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "68px", "fontSize": "68px",
"fontWeight": 600, "fontWeight": 600,
}, },
"small": Object { "small": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "12px", "fontSize": "12px",
"fontWeight": 400, "fontWeight": 400,
}, },
"smallPlus": Object { "smallPlus": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "12px", "fontSize": "12px",
"fontWeight": 400, "fontWeight": 400,
}, },
"superLarge": Object { "superLarge": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "42px", "fontSize": "42px",
"fontWeight": 600, "fontWeight": 600,
}, },
"tiny": Object { "tiny": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "10px", "fontSize": "10px",
"fontWeight": 400, "fontWeight": 400,
}, },
"xLarge": Object { "xLarge": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "20px", "fontSize": "20px",
"fontWeight": 600, "fontWeight": 600,
}, },
"xLargePlus": Object { "xLargePlus": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "24px", "fontSize": "24px",
"fontWeight": 600, "fontWeight": 600,
}, },
"xSmall": Object { "xSmall": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "10px", "fontSize": "10px",
"fontWeight": 400, "fontWeight": 400,
}, },
"xxLarge": Object { "xxLarge": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
"fontSize": "28px", "fontSize": "28px",
"fontWeight": 600, "fontWeight": 600,
}, },
"xxLargePlus": Object { "xxLargePlus": {
"MozOsxFontSmoothing": "grayscale", "MozOsxFontSmoothing": "grayscale",
"WebkitFontSmoothing": "antialiased", "WebkitFontSmoothing": "antialiased",
"fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif", "fontFamily": "'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif",
@@ -1819,7 +1821,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
}, },
}, },
"isInverted": false, "isInverted": false,
"palette": Object { "palette": {
"accent": "#0078d4", "accent": "#0078d4",
"black": "#000000", "black": "#000000",
"blackTranslucent40": "rgba(0,0,0,.4)", "blackTranslucent40": "rgba(0,0,0,.4)",
@@ -1872,7 +1874,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
"yellowLight": "#fff100", "yellowLight": "#fff100",
}, },
"rtl": undefined, "rtl": undefined,
"semanticColors": Object { "semanticColors": {
"accentButtonBackground": "#0078d4", "accentButtonBackground": "#0078d4",
"accentButtonText": "#ffffff", "accentButtonText": "#ffffff",
"actionLink": "#323130", "actionLink": "#323130",
@@ -1977,7 +1979,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
"warningIcon": "#797775", "warningIcon": "#797775",
"warningText": "#323130", "warningText": "#323130",
}, },
"spacing": Object { "spacing": {
"l1": "20px", "l1": "20px",
"l2": "32px", "l2": "32px",
"m": "16px", "m": "16px",

View File

@@ -1,48 +1,48 @@
import React from "react";
import { shallow } from "enzyme"; import { shallow } from "enzyme";
import { TreeComponent, TreeNode, TreeNodeComponent } from "./TreeComponent"; import React from "react";
import { LegacyTreeComponent, LegacyTreeNode, LegacyTreeNodeComponent } from "./LegacyTreeComponent";
const buildChildren = (): TreeNode[] => { const buildChildren = (): LegacyTreeNode[] => {
const grandChild11: TreeNode = { const grandChild11: LegacyTreeNode = {
label: "ZgrandChild11", label: "ZgrandChild11",
}; };
const grandChild12: TreeNode = { const grandChild12: LegacyTreeNode = {
label: "AgrandChild12", label: "AgrandChild12",
}; };
const child1: TreeNode = { const child1: LegacyTreeNode = {
label: "Bchild1", label: "Bchild1",
children: [grandChild11, grandChild12], children: [grandChild11, grandChild12],
}; };
const child2: TreeNode = { const child2: LegacyTreeNode = {
label: "2child2", label: "2child2",
}; };
return [child1, child2]; return [child1, child2];
}; };
const buildChildren2 = (): TreeNode[] => { const buildChildren2 = (): LegacyTreeNode[] => {
const grandChild11: TreeNode = { const grandChild11: LegacyTreeNode = {
label: "ZgrandChild11", label: "ZgrandChild11",
}; };
const grandChild12: TreeNode = { const grandChild12: LegacyTreeNode = {
label: "AgrandChild12", label: "AgrandChild12",
}; };
const child1: TreeNode = { const child1: LegacyTreeNode = {
label: "aChild", label: "aChild",
}; };
const child2: TreeNode = { const child2: LegacyTreeNode = {
label: "bchild", label: "bchild",
children: [grandChild11, grandChild12], children: [grandChild11, grandChild12],
}; };
const child3: TreeNode = { const child3: LegacyTreeNode = {
label: "cchild", label: "cchild",
}; };
const child4: TreeNode = { const child4: LegacyTreeNode = {
label: "dchild", label: "dchild",
children: [grandChild11, grandChild12], children: [grandChild11, grandChild12],
}; };
@@ -50,7 +50,7 @@ const buildChildren2 = (): TreeNode[] => {
return [child1, child2, child3, child4]; return [child1, child2, child3, child4];
}; };
describe("TreeComponent", () => { describe("LegacyTreeComponent", () => {
it("renders a simple tree", () => { it("renders a simple tree", () => {
const root = { const root = {
label: "root", label: "root",
@@ -62,14 +62,14 @@ describe("TreeComponent", () => {
className: "tree", className: "tree",
}; };
const wrapper = shallow(<TreeComponent {...props} />); const wrapper = shallow(<LegacyTreeComponent {...props} />);
expect(wrapper).toMatchSnapshot(); expect(wrapper).toMatchSnapshot();
}); });
}); });
describe("TreeNodeComponent", () => { describe("LegacyTreeNodeComponent", () => {
it("renders a simple node (sorted children, expanded)", () => { it("renders a simple node (sorted children, expanded)", () => {
const node: TreeNode = { const node: LegacyTreeNode = {
label: "label", label: "label",
id: "id", id: "id",
children: buildChildren(), children: buildChildren(),
@@ -98,12 +98,12 @@ describe("TreeNodeComponent", () => {
generation: 12, generation: 12,
paddingLeft: 23, paddingLeft: 23,
}; };
const wrapper = shallow(<TreeNodeComponent {...props} />); const wrapper = shallow(<LegacyTreeNodeComponent {...props} />);
expect(wrapper).toMatchSnapshot(); expect(wrapper).toMatchSnapshot();
}); });
it("renders unsorted children by default", () => { it("renders unsorted children by default", () => {
const node: TreeNode = { const node: LegacyTreeNode = {
label: "label", label: "label",
children: buildChildren(), children: buildChildren(),
isExpanded: true, isExpanded: true,
@@ -113,12 +113,12 @@ describe("TreeNodeComponent", () => {
generation: 2, generation: 2,
paddingLeft: 9, paddingLeft: 9,
}; };
const wrapper = shallow(<TreeNodeComponent {...props} />); const wrapper = shallow(<LegacyTreeNodeComponent {...props} />);
expect(wrapper).toMatchSnapshot(); expect(wrapper).toMatchSnapshot();
}); });
it("does not render children by default", () => { it("does not render children by default", () => {
const node: TreeNode = { const node: LegacyTreeNode = {
label: "label", label: "label",
children: buildChildren(), children: buildChildren(),
isAlphaSorted: false, isAlphaSorted: false,
@@ -128,12 +128,12 @@ describe("TreeNodeComponent", () => {
generation: 2, generation: 2,
paddingLeft: 9, paddingLeft: 9,
}; };
const wrapper = shallow(<TreeNodeComponent {...props} />); const wrapper = shallow(<LegacyTreeNodeComponent {...props} />);
expect(wrapper).toMatchSnapshot(); expect(wrapper).toMatchSnapshot();
}); });
it("renders sorted children, expanded, leaves and parents separated", () => { it("renders sorted children, expanded, leaves and parents separated", () => {
const node: TreeNode = { const node: LegacyTreeNode = {
label: "label", label: "label",
id: "id", id: "id",
children: buildChildren2(), children: buildChildren2(),
@@ -156,12 +156,12 @@ describe("TreeNodeComponent", () => {
generation: 12, generation: 12,
paddingLeft: 23, paddingLeft: 23,
}; };
const wrapper = shallow(<TreeNodeComponent {...props} />); const wrapper = shallow(<LegacyTreeNodeComponent {...props} />);
expect(wrapper).toMatchSnapshot(); expect(wrapper).toMatchSnapshot();
}); });
it("renders loading icon", () => { it("renders loading icon", () => {
const node: TreeNode = { const node: LegacyTreeNode = {
label: "label", label: "label",
children: [], children: [],
isExpanded: true, isExpanded: true,
@@ -172,7 +172,7 @@ describe("TreeNodeComponent", () => {
generation: 2, generation: 2,
paddingLeft: 9, paddingLeft: 9,
}; };
const wrapper = shallow(<TreeNodeComponent {...props} />); const wrapper = shallow(<LegacyTreeNodeComponent {...props} />);
expect(wrapper).toMatchSnapshot(); expect(wrapper).toMatchSnapshot();
}); });
}); });

View File

@@ -12,6 +12,7 @@ import {
IContextualMenuItemProps, IContextualMenuItemProps,
IContextualMenuProps, IContextualMenuProps,
} from "@fluentui/react"; } from "@fluentui/react";
import { TreeNodeMenuItem } from "Explorer/Controls/TreeComponent/TreeNodeComponent";
import * as React from "react"; import * as React from "react";
import AnimateHeight from "react-animate-height"; import AnimateHeight from "react-animate-height";
import LoadingIndicator_3Squares from "../../../../images/LoadingIndicator_3Squares.gif"; import LoadingIndicator_3Squares from "../../../../images/LoadingIndicator_3Squares.gif";
@@ -22,18 +23,10 @@ import { StyleConstants } from "../../../Common/StyleConstants";
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants"; import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor"; import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
export interface TreeNodeMenuItem { export interface LegacyTreeNode {
label: string;
onClick: () => void;
iconSrc?: string;
isDisabled?: boolean;
styleClass?: string;
}
export interface TreeNode {
label: string; label: string;
id?: string; id?: string;
children?: TreeNode[]; children?: LegacyTreeNode[];
contextMenu?: TreeNodeMenuItem[]; contextMenu?: TreeNodeMenuItem[];
iconSrc?: string; iconSrc?: string;
isExpanded?: boolean; isExpanded?: boolean;
@@ -50,34 +43,37 @@ export interface TreeNode {
onContextMenuOpen?: () => void; onContextMenuOpen?: () => void;
} }
export interface TreeComponentProps { export interface LegacyTreeComponentProps {
rootNode: TreeNode; rootNode: LegacyTreeNode;
style?: any; style?: any;
className?: string; className?: string;
} }
export class TreeComponent extends React.Component<TreeComponentProps> { export class LegacyTreeComponent extends React.Component<LegacyTreeComponentProps> {
public render(): JSX.Element { public render(): JSX.Element {
return ( return (
<div style={this.props.style} className={`treeComponent ${this.props.className}`} role="tree"> <div style={this.props.style} className={`treeComponent ${this.props.className}`} role="tree">
<TreeNodeComponent paddingLeft={0} node={this.props.rootNode} generation={0} /> <LegacyTreeNodeComponent paddingLeft={0} node={this.props.rootNode} generation={0} />
</div> </div>
); );
} }
} }
/* Tree node is a react component */ /* Tree node is a react component */
interface TreeNodeComponentProps { interface LegacyTreeNodeComponentProps {
node: TreeNode; node: LegacyTreeNode;
generation: number; generation: number;
paddingLeft: number; paddingLeft: number;
} }
interface TreeNodeComponentState { interface LegacyTreeNodeComponentState {
isExpanded: boolean; isExpanded: boolean;
isMenuShowing: boolean; isMenuShowing: boolean;
} }
export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, TreeNodeComponentState> { export class LegacyTreeNodeComponent extends React.Component<
LegacyTreeNodeComponentProps,
LegacyTreeNodeComponentState
> {
private static readonly paddingPerGenerationPx = 16; private static readonly paddingPerGenerationPx = 16;
private static readonly iconOffset = 22; private static readonly iconOffset = 22;
private static readonly transitionDurationMS = 200; private static readonly transitionDurationMS = 200;
@@ -85,7 +81,7 @@ export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, T
private contextMenuRef = React.createRef<HTMLDivElement>(); private contextMenuRef = React.createRef<HTMLDivElement>();
private isExpanded: boolean; private isExpanded: boolean;
constructor(props: TreeNodeComponentProps) { constructor(props: LegacyTreeNodeComponentProps) {
super(props); super(props);
this.isExpanded = props.node.isExpanded; this.isExpanded = props.node.isExpanded;
this.state = { this.state = {
@@ -94,17 +90,13 @@ export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, T
}; };
} }
componentDidUpdate(prevProps: TreeNodeComponentProps, prevState: TreeNodeComponentState) { componentDidUpdate(prevProps: LegacyTreeNodeComponentProps, prevState: LegacyTreeNodeComponentState) {
// Only call when expand has actually changed // Only call when expand has actually changed
if (this.state.isExpanded !== prevState.isExpanded) { if (this.state.isExpanded !== prevState.isExpanded) {
if (this.state.isExpanded) { if (this.state.isExpanded) {
console.log("IN HERE"); this.props.node.onExpanded && setTimeout(this.props.node.onExpanded, LegacyTreeNodeComponent.callbackDelayMS);
this.props.node.onExpanded &&
setTimeout(async () => {
await this.props.node.onExpanded();
}, TreeNodeComponent.callbackDelayMS);
} else { } else {
this.props.node.onCollapsed && setTimeout(this.props.node.onCollapsed, TreeNodeComponent.callbackDelayMS); this.props.node.onCollapsed && setTimeout(this.props.node.onCollapsed, LegacyTreeNodeComponent.callbackDelayMS);
} }
} }
if (this.props.node.isExpanded !== this.isExpanded) { if (this.props.node.isExpanded !== this.isExpanded) {
@@ -119,18 +111,18 @@ export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, T
return this.renderNode(this.props.node, this.props.generation); return this.renderNode(this.props.node, this.props.generation);
} }
private static getSortedChildren(treeNode: TreeNode): TreeNode[] { private static getSortedChildren(treeNode: LegacyTreeNode): LegacyTreeNode[] {
if (!treeNode || !treeNode.children) { if (!treeNode || !treeNode.children) {
return undefined; return undefined;
} }
const compareFct = (a: TreeNode, b: TreeNode) => a.label.localeCompare(b.label); const compareFct = (a: LegacyTreeNode, b: LegacyTreeNode) => a.label.localeCompare(b.label);
let unsortedChildren; let unsortedChildren;
if (treeNode.isLeavesParentsSeparate) { if (treeNode.isLeavesParentsSeparate) {
// Separate parents and leave // Separate parents and leave
const parents: TreeNode[] = treeNode.children.filter((node) => node.children); const parents: LegacyTreeNode[] = treeNode.children.filter((node) => node.children);
const leaves: TreeNode[] = treeNode.children.filter((node) => !node.children); const leaves: LegacyTreeNode[] = treeNode.children.filter((node) => !node.children);
if (treeNode.isAlphaSorted) { if (treeNode.isAlphaSorted) {
parents.sort(compareFct); parents.sort(compareFct);
@@ -145,18 +137,18 @@ export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, T
return unsortedChildren; return unsortedChildren;
} }
private static isNodeHeaderBlank(node: TreeNode): boolean { private static isNodeHeaderBlank(node: LegacyTreeNode): boolean {
return (node.label === undefined || node.label === null) && !node.contextMenu; return (node.label === undefined || node.label === null) && !node.contextMenu;
} }
private renderNode(node: TreeNode, generation: number): JSX.Element { private renderNode(node: LegacyTreeNode, generation: number): JSX.Element {
let paddingLeft = generation * TreeNodeComponent.paddingPerGenerationPx; const paddingLeft = generation * LegacyTreeNodeComponent.paddingPerGenerationPx;
let additionalOffsetPx = 15; let additionalOffsetPx = 15;
if (node.children) { if (node.children) {
const childrenWithSubChildren = node.children.filter((child: TreeNode) => !!child.children); const childrenWithSubChildren = node.children.filter((child: LegacyTreeNode) => !!child.children);
if (childrenWithSubChildren.length > 0) { if (childrenWithSubChildren.length > 0) {
additionalOffsetPx = TreeNodeComponent.iconOffset; additionalOffsetPx = LegacyTreeNodeComponent.iconOffset;
} }
} }
@@ -164,16 +156,17 @@ export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, T
const showSelected = const showSelected =
this.props.node.isSelected && this.props.node.isSelected &&
this.props.node.isSelected() && this.props.node.isSelected() &&
!TreeNodeComponent.isAnyDescendantSelected(this.props.node); !LegacyTreeNodeComponent.isAnyDescendantSelected(this.props.node);
const headerStyle: React.CSSProperties = { paddingLeft: this.props.paddingLeft }; const headerStyle: React.CSSProperties = { paddingLeft: this.props.paddingLeft };
if (TreeNodeComponent.isNodeHeaderBlank(node)) { if (LegacyTreeNodeComponent.isNodeHeaderBlank(node)) {
headerStyle.height = 0; headerStyle.height = 0;
headerStyle.padding = 0; headerStyle.padding = 0;
} }
return ( return (
<div <div
data-test={`Tree/TreeNode:${node.label}`}
className={`${this.props.node.className || ""} main${generation} nodeItem ${showSelected ? "selected" : ""}`} className={`${this.props.node.className || ""} main${generation} nodeItem ${showSelected ? "selected" : ""}`}
onClick={(event: React.MouseEvent<HTMLDivElement>) => this.onNodeClick(event, node)} onClick={(event: React.MouseEvent<HTMLDivElement>) => this.onNodeClick(event, node)}
onKeyPress={(event: React.KeyboardEvent<HTMLDivElement>) => this.onNodeKeyPress(event, node)} onKeyPress={(event: React.KeyboardEvent<HTMLDivElement>) => this.onNodeKeyPress(event, node)}
@@ -182,9 +175,9 @@ export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, T
> >
<div <div
className={`treeNodeHeader ${this.state.isMenuShowing ? "showingMenu" : ""}`} className={`treeNodeHeader ${this.state.isMenuShowing ? "showingMenu" : ""}`}
data-test={`Tree/TreeNode/Header:${node.label}`}
style={headerStyle} style={headerStyle}
tabIndex={node.children ? -1 : 0} tabIndex={node.children ? -1 : 0}
data-test={node.label}
> >
{this.renderCollapseExpandIcon(node)} {this.renderCollapseExpandIcon(node)}
{node.iconSrc && <img className="nodeIcon" src={node.iconSrc} alt="" />} {node.iconSrc && <img className="nodeIcon" src={node.iconSrc} alt="" />}
@@ -199,10 +192,13 @@ export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, T
<img className="loadingIcon" src={LoadingIndicator_3Squares} hidden={!this.props.node.isLoading} /> <img className="loadingIcon" src={LoadingIndicator_3Squares} hidden={!this.props.node.isLoading} />
</div> </div>
{node.children && ( {node.children && (
<AnimateHeight duration={TreeNodeComponent.transitionDurationMS} height={this.state.isExpanded ? "auto" : 0}> <AnimateHeight
duration={LegacyTreeNodeComponent.transitionDurationMS}
height={this.state.isExpanded ? "auto" : 0}
>
<div className="nodeChildren" data-test={node.label} role="group"> <div className="nodeChildren" data-test={node.label} role="group">
{TreeNodeComponent.getSortedChildren(node).map((childNode: TreeNode) => ( {LegacyTreeNodeComponent.getSortedChildren(node).map((childNode: LegacyTreeNode) => (
<TreeNodeComponent <LegacyTreeNodeComponent
key={`${childNode.label}-${generation + 1}-${childNode.timestamp}`} key={`${childNode.label}-${generation + 1}-${childNode.timestamp}`}
node={childNode} node={childNode}
generation={generation + 1} generation={generation + 1}
@@ -220,12 +216,14 @@ export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, T
* Recursive: is the node or any descendant selected * Recursive: is the node or any descendant selected
* @param node * @param node
*/ */
private static isAnyDescendantSelected(node: TreeNode): boolean { private static isAnyDescendantSelected(node: LegacyTreeNode): boolean {
return ( return (
node.children && node.children &&
node.children.reduce( node.children.reduce(
(previous: boolean, child: TreeNode) => (previous: boolean, child: LegacyTreeNode) =>
previous || (child.isSelected && child.isSelected()) || TreeNodeComponent.isAnyDescendantSelected(child), previous ||
(child.isSelected && child.isSelected()) ||
LegacyTreeNodeComponent.isAnyDescendantSelected(child),
false, false,
) )
); );
@@ -236,10 +234,10 @@ export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, T
} }
private onRightClick = (): void => { private onRightClick = (): void => {
this.contextMenuRef.current.firstChild.dispatchEvent(TreeNodeComponent.createClickEvent()); this.contextMenuRef.current.firstChild.dispatchEvent(LegacyTreeNodeComponent.createClickEvent());
}; };
private renderContextMenuButton(node: TreeNode): JSX.Element { private renderContextMenuButton(node: LegacyTreeNode): JSX.Element {
const menuItemLabel = "More"; const menuItemLabel = "More";
const buttonStyles: Partial<IButtonStyles> = { const buttonStyles: Partial<IButtonStyles> = {
rootFocused: { outline: `1px dashed ${StyleConstants.FocusColor}` }, rootFocused: { outline: `1px dashed ${StyleConstants.FocusColor}` },
@@ -267,9 +265,9 @@ export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, T
onMenuDismissed: (contextualMenu?: IContextualMenuProps) => this.setState({ isMenuShowing: false }), onMenuDismissed: (contextualMenu?: IContextualMenuProps) => this.setState({ isMenuShowing: false }),
contextualMenuItemAs: (props: IContextualMenuItemProps) => ( contextualMenuItemAs: (props: IContextualMenuItemProps) => (
<div <div
data-test={`treeComponentMenuItemContainer`} data-test={`Tree/TreeNode/MenuItem:${props.item.text}`}
className="treeComponentMenuItemContainer" className="treeComponentMenuItemContainer"
onContextMenu={(e) => e.target.dispatchEvent(TreeNodeComponent.createClickEvent())} onContextMenu={(e) => e.target.dispatchEvent(LegacyTreeNodeComponent.createClickEvent())}
> >
{props.item.onRenderIcon()} {props.item.onRenderIcon()}
<span <span
@@ -301,7 +299,7 @@ export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, T
); );
} }
private renderCollapseExpandIcon(node: TreeNode): JSX.Element { private renderCollapseExpandIcon(node: LegacyTreeNode): JSX.Element {
if (!node.children || !node.label) { if (!node.children || !node.label) {
return <></>; return <></>;
} }
@@ -318,12 +316,12 @@ export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, T
); );
} }
private onNodeClick = (event: React.MouseEvent<HTMLDivElement>, node: TreeNode): void => { private onNodeClick = (event: React.MouseEvent<HTMLDivElement>, node: LegacyTreeNode): void => {
event.stopPropagation(); event.stopPropagation();
if (node.children) { if (node.children) {
const isExpanded = !this.state.isExpanded; const isExpanded = !this.state.isExpanded;
// Prevent collapsing if node header is blank // Prevent collapsing if node header is blank
if (!(TreeNodeComponent.isNodeHeaderBlank(node) && !isExpanded)) { if (!(LegacyTreeNodeComponent.isNodeHeaderBlank(node) && !isExpanded)) {
this.setState({ isExpanded }); this.setState({ isExpanded });
} }
} }
@@ -331,14 +329,14 @@ export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, T
this.props.node.onClick && this.props.node.onClick(this.state.isExpanded); this.props.node.onClick && this.props.node.onClick(this.state.isExpanded);
}; };
private onNodeKeyPress = (event: React.KeyboardEvent<HTMLDivElement>, node: TreeNode): void => { private onNodeKeyPress = (event: React.KeyboardEvent<HTMLDivElement>, node: LegacyTreeNode): void => {
if (event.charCode === Constants.KeyCodes.Space || event.charCode === Constants.KeyCodes.Enter) { if (event.charCode === Constants.KeyCodes.Space || event.charCode === Constants.KeyCodes.Enter) {
event.stopPropagation(); event.stopPropagation();
this.props.node.onClick && this.props.node.onClick(this.state.isExpanded); this.props.node.onClick && this.props.node.onClick(this.state.isExpanded);
} }
}; };
private onCollapseExpandIconKeyPress = (event: React.KeyboardEvent<HTMLDivElement>, node: TreeNode): void => { private onCollapseExpandIconKeyPress = (event: React.KeyboardEvent<HTMLDivElement>, node: LegacyTreeNode): void => {
if (event.charCode === Constants.KeyCodes.Space || event.charCode === Constants.KeyCodes.Enter) { if (event.charCode === Constants.KeyCodes.Space || event.charCode === Constants.KeyCodes.Enter) {
event.stopPropagation(); event.stopPropagation();
if (node.children) { if (node.children) {

View File

@@ -0,0 +1,65 @@
import { makeStyles, shorthands, treeItemLevelToken } from "@fluentui/react-components";
import { cosmosShorthands, tokens } from "Explorer/Theme/ThemeUtil";
export type TreeStyleName = keyof ReturnType<typeof useTreeStyles>;
const treeIconWidth = "--cosmos-Tree--iconWidth" as const;
const leafNodeSpacing = "--cosmos-Tree--leafNodeSpacing" as const;
const actionButtonBackground = "--cosmos-Tree--actionButtonBackground" as const;
export const useTreeStyles = makeStyles({
treeContainer: {
height: "100%",
...shorthands.overflow("auto"),
},
tree: {
width: "fit-content",
minWidth: "100%",
rowGap: "0px",
paddingTop: "0px",
[treeIconWidth]: "20px",
[leafNodeSpacing]: "24px",
},
nodeIcon: {
width: `var(${treeIconWidth})`,
height: `var(${treeIconWidth})`,
},
treeItem: {},
nodeLabel: {},
treeItemLayout: {
fontSize: tokens.fontSizeBase300,
height: tokens.layoutRowHeight,
...cosmosShorthands.borderBottom(),
paddingLeft: `calc(var(${treeItemLevelToken}, 1) * ${tokens.spacingHorizontalXXL})`,
// Some sneaky CSS variables stuff to change the background color of the action button on hover.
[actionButtonBackground]: tokens.colorNeutralBackground1,
"&:hover": {
[actionButtonBackground]: tokens.colorNeutralBackground1Hover,
},
},
actionsButtonContainer: {
position: "sticky",
right: 0,
},
actionsButton: {
backgroundColor: `var(${actionButtonBackground})`,
},
treeItemLayoutNoIcon: {
// Pad the text out by the level, the width of the icon, AND the usual spacing between the icon and the level.
// It would be nice to see if we can use Grid layout or something here, but that would require overriding a lot of the existing Tree component behavior.
paddingLeft: `calc((var(${treeItemLevelToken}, 1) * ${tokens.spacingHorizontalXXL}) + var(${leafNodeSpacing}))`,
},
selectedItem: {
backgroundColor: tokens.colorNeutralBackground1Selected,
},
databaseNode: {
fontWeight: tokens.fontWeightSemibold,
},
collectionNode: {
fontWeight: tokens.fontWeightSemibold,
},
loadMoreNode: {
color: tokens.colorBrandForegroundLink,
},
});

View File

@@ -0,0 +1,188 @@
import { TreeItem, TreeItemLayout } from "@fluentui/react-components";
import PromiseSource from "Utils/PromiseSource";
import { mount, shallow } from "enzyme";
import React from "react";
import { act } from "react-dom/test-utils";
import { TreeNode, TreeNodeComponent } from "./TreeNodeComponent";
function generateTestNode(id: string, additionalProps?: Partial<TreeNode>): TreeNode {
const node: TreeNode = {
id,
label: `${id}Label`,
className: "nodeIcon",
iconSrc: `${id}Icon`,
onClick: jest.fn().mockName(`${id}Click`),
...additionalProps,
};
return node;
}
describe("TreeNodeComponent", () => {
it("renders a single node", () => {
const node = generateTestNode("root");
const component = shallow(<TreeNodeComponent openItems={[]} node={node} treeNodeId={node.id} />);
expect(component).toMatchSnapshot();
// The "click" handler is actually attached to onOpenChange, with a type of "Click".
component
.find(TreeItem)
.props()
.onOpenChange(null!, { open: true, value: "borp", target: null!, event: null!, type: "Click" });
expect(node.onClick).toHaveBeenCalled();
});
it("renders a node with a menu", () => {
const node = generateTestNode("root", {
contextMenu: [
{
label: "enabledItem",
onClick: jest.fn().mockName("enabledItemClick"),
},
{
label: "disabledItem",
onClick: jest.fn().mockName("disabledItemClick"),
isDisabled: true,
},
],
});
const component = shallow(<TreeNodeComponent openItems={[]} node={node} treeNodeId={node.id} />);
expect(component).toMatchSnapshot();
});
it("renders a loading spinner if the node is loading", async () => {
const loading = new PromiseSource();
const node = generateTestNode("root", {
onExpanded: () => loading.promise,
});
const component = shallow(<TreeNodeComponent openItems={[]} node={node} treeNodeId={node.id} />);
act(() => {
component
.find(TreeItem)
.props()
.onOpenChange(null!, { open: true, value: "borp", target: null!, event: null!, type: "ExpandIconClick" });
});
expect(component).toMatchSnapshot("loading");
await loading.resolveAndWait();
expect(component).toMatchSnapshot("loaded");
});
it("renders single selected leaf node as selected", () => {
const node = generateTestNode("root", {
isSelected: () => true,
});
const component = shallow(<TreeNodeComponent openItems={[]} node={node} treeNodeId={node.id} />);
expect(component).toMatchSnapshot();
});
it("renders selected parent node as selected if no descendant nodes are selected", () => {
const node = generateTestNode("root", {
isSelected: () => true,
children: [
generateTestNode("child1", {
children: [generateTestNode("grandchild1"), generateTestNode("grandchild2")],
}),
generateTestNode("child2"),
],
});
const component = shallow(<TreeNodeComponent openItems={[]} node={node} treeNodeId={node.id} />);
expect(component).toMatchSnapshot();
});
it("renders selected parent node as unselected if any descendant node is selected", () => {
const node = generateTestNode("root", {
isSelected: () => true,
children: [
generateTestNode("child1", {
children: [
generateTestNode("grandchild1", {
isSelected: () => true,
}),
generateTestNode("grandchild2"),
],
}),
generateTestNode("child2"),
],
});
const component = shallow(<TreeNodeComponent openItems={[]} node={node} treeNodeId={node.id} />);
expect(component.find(TreeItemLayout).props().style?.backgroundColor).toBeUndefined();
expect(component).toMatchSnapshot();
});
it("renders an icon if the node has one", () => {
const node = generateTestNode("root", {
iconSrc: "the-icon.svg",
});
const component = shallow(<TreeNodeComponent openItems={[]} node={node} treeNodeId={node.id} />);
expect(component).toMatchSnapshot();
});
it("renders a node as expandable if it has empty, but defined, children array", () => {
const node = generateTestNode("root", {
isLoading: true,
children: [
generateTestNode("child1", {
children: [],
}),
generateTestNode("child2"),
],
});
const component = shallow(<TreeNodeComponent openItems={[]} node={node} treeNodeId={node.id} />);
expect(component).toMatchSnapshot();
});
it("does not render children if the node is loading", () => {
const node = generateTestNode("root", {
isLoading: true,
children: [
generateTestNode("child1", {
children: [generateTestNode("grandchild1"), generateTestNode("grandchild2")],
}),
generateTestNode("child2"),
],
});
const component = shallow(<TreeNodeComponent openItems={[]} node={node} treeNodeId={node.id} />);
expect(component).toMatchSnapshot();
});
it("fully renders a tree", () => {
const child3Loading = new PromiseSource();
const node = generateTestNode("root", {
isSelected: () => true,
children: [
generateTestNode("child1", {
children: [
generateTestNode("grandchild1", {
iconSrc: "grandchild1Icon.svg",
isSelected: () => true,
}),
generateTestNode("grandchild2"),
],
}),
generateTestNode("child2Loading", {
isLoading: true,
children: [generateTestNode("grandchild3NotRendered")],
}),
generateTestNode("child3Expanding", {
onExpanded: () => child3Loading.promise,
}),
],
});
const component = mount(<TreeNodeComponent openItems={[]} node={node} treeNodeId={node.id} />);
// Find and expand the child3Expanding node
const expandingChild = component.find(TreeItem).filterWhere((n) => n.props().value === "root/child3ExpandingLabel");
act(() => {
expandingChild.props().onOpenChange(null!, {
open: true,
value: "root/child3ExpandingLabel",
target: null!,
event: null!,
type: "Click",
});
});
expect(component).toMatchSnapshot();
});
});

View File

@@ -0,0 +1,236 @@
import {
Button,
Menu,
MenuItem,
MenuList,
MenuOpenChangeData,
MenuOpenEvent,
MenuPopover,
MenuTrigger,
Spinner,
Tree,
TreeItem,
TreeItemLayout,
TreeItemValue,
TreeOpenChangeData,
TreeOpenChangeEvent,
mergeClasses,
} from "@fluentui/react-components";
import { ChevronDown20Regular, ChevronRight20Regular, MoreHorizontal20Regular } from "@fluentui/react-icons";
import { TreeStyleName, useTreeStyles } from "Explorer/Controls/TreeComponent/Styles";
import * as React from "react";
import { useCallback } from "react";
export interface TreeNodeMenuItem {
label: string;
onClick: () => void;
iconSrc?: string;
isDisabled?: boolean;
styleClass?: string;
}
export interface TreeNode {
label: string;
id?: string;
children?: TreeNode[];
contextMenu?: TreeNodeMenuItem[];
iconSrc?: string | JSX.Element;
isExpanded?: boolean;
className?: TreeStyleName;
isAlphaSorted?: boolean;
// data?: any; // Piece of data corresponding to this node
timestamp?: number;
isLeavesParentsSeparate?: boolean; // Display parents together first, then leaves
isLoading?: boolean;
isSelected?: () => boolean;
onClick?: () => void; // Only if a leaf, other click will expand/collapse
onExpanded?: () => Promise<void>;
onCollapsed?: () => void;
onContextMenuOpen?: () => void;
}
export interface TreeNodeComponentProps {
node: TreeNode;
className?: string;
treeNodeId: string;
openItems: TreeItemValue[];
}
/** Function that returns true if any descendant (at any depth) of this node is selected. */
function isAnyDescendantSelected(node: TreeNode): boolean {
return (
node.children &&
node.children.reduce(
(previous: boolean, child: TreeNode) =>
previous || (child.isSelected && child.isSelected()) || isAnyDescendantSelected(child),
false,
)
);
}
export const TreeNodeComponent: React.FC<TreeNodeComponentProps> = ({
node,
treeNodeId,
openItems,
}: TreeNodeComponentProps): JSX.Element => {
const [isLoading, setIsLoading] = React.useState<boolean>(false);
const treeStyles = useTreeStyles();
const getSortedChildren = (treeNode: TreeNode): TreeNode[] => {
if (!treeNode || !treeNode.children) {
return undefined;
}
const compareFct = (a: TreeNode, b: TreeNode) => a.label.localeCompare(b.label);
let unsortedChildren;
if (treeNode.isLeavesParentsSeparate) {
// Separate parents and leave
const parents: TreeNode[] = treeNode.children.filter((node) => node.children);
const leaves: TreeNode[] = treeNode.children.filter((node) => !node.children);
if (treeNode.isAlphaSorted) {
parents.sort(compareFct);
leaves.sort(compareFct);
}
unsortedChildren = parents.concat(leaves);
} else {
unsortedChildren = treeNode.isAlphaSorted ? treeNode.children.sort(compareFct) : treeNode.children;
}
return unsortedChildren;
};
// A branch node is any node with a defined children array, even if the array is empty.
const isBranch = !!node.children;
const onOpenChange = useCallback(
(_: TreeOpenChangeEvent, data: TreeOpenChangeData) => {
if (data.type === "Click" && !isBranch && node.onClick) {
node.onClick();
}
if (!node.isExpanded && data.open && node.onExpanded) {
// Catch the transition non-expanded to expanded
setIsLoading(true);
node.onExpanded?.().then(() => setIsLoading(false));
} else if (node.isExpanded && !data.open && node.onCollapsed) {
// Catch the transition expanded to non-expanded
node.onCollapsed?.();
}
},
[isBranch, node, setIsLoading],
);
const onMenuOpenChange = useCallback(
(e: MenuOpenEvent, data: MenuOpenChangeData) => {
if (data.open) {
node.onContextMenuOpen?.();
}
},
[node],
);
// We show a node as selected if it is selected AND no descendant is selected.
// We want to show only the deepest selected node as selected.
const isCurrentNodeSelected = node.isSelected && node.isSelected();
const shouldShowAsSelected = isCurrentNodeSelected && !isAnyDescendantSelected(node);
const contextMenuItems = (node.contextMenu ?? []).map((menuItem) => (
<MenuItem
data-test={`TreeNode/ContextMenuItem:${menuItem.label}`}
disabled={menuItem.isDisabled}
key={menuItem.label}
onClick={menuItem.onClick}
>
{menuItem.label}
</MenuItem>
));
// We use the expandIcon slot to hold the node icon too.
// We only show a node icon for leaf nodes, even if a branch node has an iconSrc.
const expandIcon = isLoading ? (
<Spinner size="extra-tiny" />
) : !isBranch ? (
typeof node.iconSrc === "string" ? (
<img src={node.iconSrc} className={treeStyles.nodeIcon} alt="" />
) : (
node.iconSrc
)
) : openItems.includes(treeNodeId) ? (
<ChevronDown20Regular />
) : (
<ChevronRight20Regular />
);
const treeItem = (
<TreeItem
data-test={`TreeNodeContainer:${treeNodeId}`}
value={treeNodeId}
itemType={isBranch ? "branch" : "leaf"}
onOpenChange={onOpenChange}
className={treeStyles.treeItem}
>
<TreeItemLayout
className={mergeClasses(
treeStyles.treeItemLayout,
expandIcon ? undefined : treeStyles.treeItemLayoutNoIcon,
shouldShowAsSelected && treeStyles.selectedItem,
node.className && treeStyles[node.className],
)}
data-test={`TreeNode:${treeNodeId}`}
actions={
contextMenuItems.length > 0 && {
className: treeStyles.actionsButtonContainer,
children: (
<Menu onOpenChange={onMenuOpenChange}>
<MenuTrigger disableButtonEnhancement>
<Button
aria-label="More options"
className={mergeClasses(treeStyles.actionsButton, shouldShowAsSelected && treeStyles.selectedItem)}
data-test="TreeNode/ContextMenuTrigger"
appearance="subtle"
icon={<MoreHorizontal20Regular />}
/>
</MenuTrigger>
<MenuPopover data-test={`TreeNode/ContextMenu:${treeNodeId}`}>
<MenuList>{contextMenuItems}</MenuList>
</MenuPopover>
</Menu>
),
}
}
expandIcon={expandIcon}
>
<span className={treeStyles.nodeLabel}>{node.label}</span>
</TreeItemLayout>
{!node.isLoading && node.children?.length > 0 && (
<Tree className={treeStyles.tree}>
{getSortedChildren(node).map((childNode: TreeNode) => (
<TreeNodeComponent
openItems={openItems}
key={childNode.label}
node={childNode}
treeNodeId={`${treeNodeId}/${childNode.label}`}
/>
))}
</Tree>
)}
</TreeItem>
);
if (contextMenuItems.length === 0) {
return treeItem;
}
// For accessibility, it's highly recommended that any 'actions' also be available in the context menu.
// See https://react.fluentui.dev/?path=/docs/components-tree--default#actions
return (
<Menu positioning="below-end" openOnContext onOpenChange={onMenuOpenChange}>
<MenuTrigger disableButtonEnhancement>{treeItem}</MenuTrigger>
<MenuPopover>
<MenuList>{contextMenuItems}</MenuList>
</MenuPopover>
</Menu>
);
};

View File

@@ -1,27 +1,27 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`TreeComponent renders a simple tree 1`] = ` exports[`LegacyTreeComponent renders a simple tree 1`] = `
<div <div
className="treeComponent tree" className="treeComponent tree"
role="tree" role="tree"
> >
<TreeNodeComponent <LegacyTreeNodeComponent
generation={0} generation={0}
node={ node={
Object { {
"children": Array [ "children": [
Object { {
"children": Array [ "children": [
Object { {
"label": "ZgrandChild11", "label": "ZgrandChild11",
}, },
Object { {
"label": "AgrandChild12", "label": "AgrandChild12",
}, },
], ],
"label": "Bchild1", "label": "Bchild1",
}, },
Object { {
"label": "2child2", "label": "2child2",
}, },
], ],
@@ -33,18 +33,19 @@ exports[`TreeComponent renders a simple tree 1`] = `
</div> </div>
`; `;
exports[`TreeNodeComponent does not render children by default 1`] = ` exports[`LegacyTreeNodeComponent does not render children by default 1`] = `
<div <div
className=" main2 nodeItem " className=" main2 nodeItem "
data-test="Tree/TreeNode:label"
onClick={[Function]} onClick={[Function]}
onKeyPress={[Function]} onKeyPress={[Function]}
role="treeitem" role="treeitem"
> >
<div <div
className="treeNodeHeader " className="treeNodeHeader "
data-test="label" data-test="Tree/TreeNode/Header:label"
style={ style={
Object { {
"paddingLeft": 9, "paddingLeft": 9,
} }
} }
@@ -55,7 +56,7 @@ exports[`TreeNodeComponent does not render children by default 1`] = `
className="expandCollapseIcon" className="expandCollapseIcon"
onKeyPress={[Function]} onKeyPress={[Function]}
role="button" role="button"
src={Object {}} src={{}}
tabIndex={0} tabIndex={0}
/> />
<span <span
@@ -77,7 +78,7 @@ exports[`TreeNodeComponent does not render children by default 1`] = `
<AnimateHeight <AnimateHeight
animateOpacity={false} animateOpacity={false}
animationStateClasses={ animationStateClasses={
Object { {
"animating": "rah-animating", "animating": "rah-animating",
"animatingDown": "rah-animating--down", "animatingDown": "rah-animating--down",
"animatingToHeightAuto": "rah-animating--to-height-auto", "animatingToHeightAuto": "rah-animating--to-height-auto",
@@ -95,23 +96,23 @@ exports[`TreeNodeComponent does not render children by default 1`] = `
duration={200} duration={200}
easing="ease" easing="ease"
height={0} height={0}
style={Object {}} style={{}}
> >
<div <div
className="nodeChildren" className="nodeChildren"
data-test="label" data-test="label"
role="group" role="group"
> >
<TreeNodeComponent <LegacyTreeNodeComponent
generation={3} generation={3}
key="Bchild1-3-undefined" key="Bchild1-3-undefined"
node={ node={
Object { {
"children": Array [ "children": [
Object { {
"label": "ZgrandChild11", "label": "ZgrandChild11",
}, },
Object { {
"label": "AgrandChild12", "label": "AgrandChild12",
}, },
], ],
@@ -120,11 +121,11 @@ exports[`TreeNodeComponent does not render children by default 1`] = `
} }
paddingLeft={32} paddingLeft={32}
/> />
<TreeNodeComponent <LegacyTreeNodeComponent
generation={3} generation={3}
key="2child2-3-undefined" key="2child2-3-undefined"
node={ node={
Object { {
"label": "2child2", "label": "2child2",
} }
} }
@@ -135,9 +136,10 @@ exports[`TreeNodeComponent does not render children by default 1`] = `
</div> </div>
`; `;
exports[`TreeNodeComponent renders a simple node (sorted children, expanded) 1`] = ` exports[`LegacyTreeNodeComponent renders a simple node (sorted children, expanded) 1`] = `
<div <div
className="nodeClassname main12 nodeItem " className="nodeClassname main12 nodeItem "
data-test="Tree/TreeNode:label"
id="id" id="id"
onClick={[Function]} onClick={[Function]}
onKeyPress={[Function]} onKeyPress={[Function]}
@@ -145,9 +147,9 @@ exports[`TreeNodeComponent renders a simple node (sorted children, expanded) 1`]
> >
<div <div
className="treeNodeHeader " className="treeNodeHeader "
data-test="label" data-test="Tree/TreeNode/Header:label"
style={ style={
Object { {
"paddingLeft": 23, "paddingLeft": 23,
} }
} }
@@ -158,7 +160,7 @@ exports[`TreeNodeComponent renders a simple node (sorted children, expanded) 1`]
className="expandCollapseIcon" className="expandCollapseIcon"
onKeyPress={[Function]} onKeyPress={[Function]}
role="button" role="button"
src={Object {}} src={{}}
tabIndex={0} tabIndex={0}
/> />
<span <span
@@ -175,10 +177,10 @@ exports[`TreeNodeComponent renders a simple node (sorted children, expanded) 1`]
ariaLabel="More options" ariaLabel="More options"
className="treeMenuEllipsis" className="treeMenuEllipsis"
menuIconProps={ menuIconProps={
Object { {
"iconName": "More", "iconName": "More",
"styles": Object { "styles": {
"root": Object { "root": {
"fontSize": "18px", "fontSize": "18px",
"fontWeight": "bold", "fontWeight": "bold",
}, },
@@ -186,13 +188,13 @@ exports[`TreeNodeComponent renders a simple node (sorted children, expanded) 1`]
} }
} }
menuProps={ menuProps={
Object { {
"contextualMenuItemAs": [Function], "contextualMenuItemAs": [Function],
"coverTarget": true, "coverTarget": true,
"directionalHint": 3, "directionalHint": 3,
"isBeakVisible": false, "isBeakVisible": false,
"items": Array [ "items": [
Object { {
"className": undefined, "className": undefined,
"disabled": true, "disabled": true,
"key": "menuLabel", "key": "menuLabel",
@@ -207,8 +209,8 @@ exports[`TreeNodeComponent renders a simple node (sorted children, expanded) 1`]
} }
name="More" name="More"
styles={ styles={
Object { {
"rootFocused": Object { "rootFocused": {
"outline": "1px dashed undefined", "outline": "1px dashed undefined",
}, },
} }
@@ -229,7 +231,7 @@ exports[`TreeNodeComponent renders a simple node (sorted children, expanded) 1`]
<AnimateHeight <AnimateHeight
animateOpacity={false} animateOpacity={false}
animationStateClasses={ animationStateClasses={
Object { {
"animating": "rah-animating", "animating": "rah-animating",
"animatingDown": "rah-animating--down", "animatingDown": "rah-animating--down",
"animatingToHeightAuto": "rah-animating--to-height-auto", "animatingToHeightAuto": "rah-animating--to-height-auto",
@@ -247,33 +249,33 @@ exports[`TreeNodeComponent renders a simple node (sorted children, expanded) 1`]
duration={200} duration={200}
easing="ease" easing="ease"
height="auto" height="auto"
style={Object {}} style={{}}
> >
<div <div
className="nodeChildren" className="nodeChildren"
data-test="label" data-test="label"
role="group" role="group"
> >
<TreeNodeComponent <LegacyTreeNodeComponent
generation={13} generation={13}
key="2child2-13-undefined" key="2child2-13-undefined"
node={ node={
Object { {
"label": "2child2", "label": "2child2",
} }
} }
paddingLeft={214} paddingLeft={214}
/> />
<TreeNodeComponent <LegacyTreeNodeComponent
generation={13} generation={13}
key="Bchild1-13-undefined" key="Bchild1-13-undefined"
node={ node={
Object { {
"children": Array [ "children": [
Object { {
"label": "ZgrandChild11", "label": "ZgrandChild11",
}, },
Object { {
"label": "AgrandChild12", "label": "AgrandChild12",
}, },
], ],
@@ -287,18 +289,19 @@ exports[`TreeNodeComponent renders a simple node (sorted children, expanded) 1`]
</div> </div>
`; `;
exports[`TreeNodeComponent renders loading icon 1`] = ` exports[`LegacyTreeNodeComponent renders loading icon 1`] = `
<div <div
className=" main2 nodeItem " className=" main2 nodeItem "
data-test="Tree/TreeNode:label"
onClick={[Function]} onClick={[Function]}
onKeyPress={[Function]} onKeyPress={[Function]}
role="treeitem" role="treeitem"
> >
<div <div
className="treeNodeHeader " className="treeNodeHeader "
data-test="label" data-test="Tree/TreeNode/Header:label"
style={ style={
Object { {
"paddingLeft": 9, "paddingLeft": 9,
} }
} }
@@ -309,7 +312,7 @@ exports[`TreeNodeComponent renders loading icon 1`] = `
className="expandCollapseIcon" className="expandCollapseIcon"
onKeyPress={[Function]} onKeyPress={[Function]}
role="button" role="button"
src={Object {}} src={{}}
tabIndex={0} tabIndex={0}
/> />
<span <span
@@ -331,7 +334,7 @@ exports[`TreeNodeComponent renders loading icon 1`] = `
<AnimateHeight <AnimateHeight
animateOpacity={false} animateOpacity={false}
animationStateClasses={ animationStateClasses={
Object { {
"animating": "rah-animating", "animating": "rah-animating",
"animatingDown": "rah-animating--down", "animatingDown": "rah-animating--down",
"animatingToHeightAuto": "rah-animating--to-height-auto", "animatingToHeightAuto": "rah-animating--to-height-auto",
@@ -349,7 +352,7 @@ exports[`TreeNodeComponent renders loading icon 1`] = `
duration={200} duration={200}
easing="ease" easing="ease"
height="auto" height="auto"
style={Object {}} style={{}}
> >
<div <div
className="nodeChildren" className="nodeChildren"
@@ -360,9 +363,10 @@ exports[`TreeNodeComponent renders loading icon 1`] = `
</div> </div>
`; `;
exports[`TreeNodeComponent renders sorted children, expanded, leaves and parents separated 1`] = ` exports[`LegacyTreeNodeComponent renders sorted children, expanded, leaves and parents separated 1`] = `
<div <div
className="nodeClassname main12 nodeItem " className="nodeClassname main12 nodeItem "
data-test="Tree/TreeNode:label"
id="id" id="id"
onClick={[Function]} onClick={[Function]}
onKeyPress={[Function]} onKeyPress={[Function]}
@@ -370,9 +374,9 @@ exports[`TreeNodeComponent renders sorted children, expanded, leaves and parents
> >
<div <div
className="treeNodeHeader " className="treeNodeHeader "
data-test="label" data-test="Tree/TreeNode/Header:label"
style={ style={
Object { {
"paddingLeft": 23, "paddingLeft": 23,
} }
} }
@@ -383,7 +387,7 @@ exports[`TreeNodeComponent renders sorted children, expanded, leaves and parents
className="expandCollapseIcon" className="expandCollapseIcon"
onKeyPress={[Function]} onKeyPress={[Function]}
role="button" role="button"
src={Object {}} src={{}}
tabIndex={0} tabIndex={0}
/> />
<span <span
@@ -400,10 +404,10 @@ exports[`TreeNodeComponent renders sorted children, expanded, leaves and parents
ariaLabel="More options" ariaLabel="More options"
className="treeMenuEllipsis" className="treeMenuEllipsis"
menuIconProps={ menuIconProps={
Object { {
"iconName": "More", "iconName": "More",
"styles": Object { "styles": {
"root": Object { "root": {
"fontSize": "18px", "fontSize": "18px",
"fontWeight": "bold", "fontWeight": "bold",
}, },
@@ -411,20 +415,20 @@ exports[`TreeNodeComponent renders sorted children, expanded, leaves and parents
} }
} }
menuProps={ menuProps={
Object { {
"contextualMenuItemAs": [Function], "contextualMenuItemAs": [Function],
"coverTarget": true, "coverTarget": true,
"directionalHint": 3, "directionalHint": 3,
"isBeakVisible": false, "isBeakVisible": false,
"items": Array [], "items": [],
"onMenuDismissed": [Function], "onMenuDismissed": [Function],
"onMenuOpened": [Function], "onMenuOpened": [Function],
} }
} }
name="More" name="More"
styles={ styles={
Object { {
"rootFocused": Object { "rootFocused": {
"outline": "1px dashed undefined", "outline": "1px dashed undefined",
}, },
} }
@@ -445,7 +449,7 @@ exports[`TreeNodeComponent renders sorted children, expanded, leaves and parents
<AnimateHeight <AnimateHeight
animateOpacity={false} animateOpacity={false}
animationStateClasses={ animationStateClasses={
Object { {
"animating": "rah-animating", "animating": "rah-animating",
"animatingDown": "rah-animating--down", "animatingDown": "rah-animating--down",
"animatingToHeightAuto": "rah-animating--to-height-auto", "animatingToHeightAuto": "rah-animating--to-height-auto",
@@ -463,23 +467,23 @@ exports[`TreeNodeComponent renders sorted children, expanded, leaves and parents
duration={200} duration={200}
easing="ease" easing="ease"
height="auto" height="auto"
style={Object {}} style={{}}
> >
<div <div
className="nodeChildren" className="nodeChildren"
data-test="label" data-test="label"
role="group" role="group"
> >
<TreeNodeComponent <LegacyTreeNodeComponent
generation={13} generation={13}
key="bchild-13-undefined" key="bchild-13-undefined"
node={ node={
Object { {
"children": Array [ "children": [
Object { {
"label": "ZgrandChild11", "label": "ZgrandChild11",
}, },
Object { {
"label": "AgrandChild12", "label": "AgrandChild12",
}, },
], ],
@@ -488,16 +492,16 @@ exports[`TreeNodeComponent renders sorted children, expanded, leaves and parents
} }
paddingLeft={192} paddingLeft={192}
/> />
<TreeNodeComponent <LegacyTreeNodeComponent
generation={13} generation={13}
key="dchild-13-undefined" key="dchild-13-undefined"
node={ node={
Object { {
"children": Array [ "children": [
Object { {
"label": "ZgrandChild11", "label": "ZgrandChild11",
}, },
Object { {
"label": "AgrandChild12", "label": "AgrandChild12",
}, },
], ],
@@ -506,21 +510,21 @@ exports[`TreeNodeComponent renders sorted children, expanded, leaves and parents
} }
paddingLeft={192} paddingLeft={192}
/> />
<TreeNodeComponent <LegacyTreeNodeComponent
generation={13} generation={13}
key="aChild-13-undefined" key="aChild-13-undefined"
node={ node={
Object { {
"label": "aChild", "label": "aChild",
} }
} }
paddingLeft={214} paddingLeft={214}
/> />
<TreeNodeComponent <LegacyTreeNodeComponent
generation={13} generation={13}
key="cchild-13-undefined" key="cchild-13-undefined"
node={ node={
Object { {
"label": "cchild", "label": "cchild",
} }
} }
@@ -531,18 +535,19 @@ exports[`TreeNodeComponent renders sorted children, expanded, leaves and parents
</div> </div>
`; `;
exports[`TreeNodeComponent renders unsorted children by default 1`] = ` exports[`LegacyTreeNodeComponent renders unsorted children by default 1`] = `
<div <div
className=" main2 nodeItem " className=" main2 nodeItem "
data-test="Tree/TreeNode:label"
onClick={[Function]} onClick={[Function]}
onKeyPress={[Function]} onKeyPress={[Function]}
role="treeitem" role="treeitem"
> >
<div <div
className="treeNodeHeader " className="treeNodeHeader "
data-test="label" data-test="Tree/TreeNode/Header:label"
style={ style={
Object { {
"paddingLeft": 9, "paddingLeft": 9,
} }
} }
@@ -553,7 +558,7 @@ exports[`TreeNodeComponent renders unsorted children by default 1`] = `
className="expandCollapseIcon" className="expandCollapseIcon"
onKeyPress={[Function]} onKeyPress={[Function]}
role="button" role="button"
src={Object {}} src={{}}
tabIndex={0} tabIndex={0}
/> />
<span <span
@@ -575,7 +580,7 @@ exports[`TreeNodeComponent renders unsorted children by default 1`] = `
<AnimateHeight <AnimateHeight
animateOpacity={false} animateOpacity={false}
animationStateClasses={ animationStateClasses={
Object { {
"animating": "rah-animating", "animating": "rah-animating",
"animatingDown": "rah-animating--down", "animatingDown": "rah-animating--down",
"animatingToHeightAuto": "rah-animating--to-height-auto", "animatingToHeightAuto": "rah-animating--to-height-auto",
@@ -593,23 +598,23 @@ exports[`TreeNodeComponent renders unsorted children by default 1`] = `
duration={200} duration={200}
easing="ease" easing="ease"
height="auto" height="auto"
style={Object {}} style={{}}
> >
<div <div
className="nodeChildren" className="nodeChildren"
data-test="label" data-test="label"
role="group" role="group"
> >
<TreeNodeComponent <LegacyTreeNodeComponent
generation={3} generation={3}
key="Bchild1-3-undefined" key="Bchild1-3-undefined"
node={ node={
Object { {
"children": Array [ "children": [
Object { {
"label": "ZgrandChild11", "label": "ZgrandChild11",
}, },
Object { {
"label": "AgrandChild12", "label": "AgrandChild12",
}, },
], ],
@@ -618,11 +623,11 @@ exports[`TreeNodeComponent renders unsorted children by default 1`] = `
} }
paddingLeft={32} paddingLeft={32}
/> />
<TreeNodeComponent <LegacyTreeNodeComponent
generation={3} generation={3}
key="2child2-3-undefined" key="2child2-3-undefined"
node={ node={
Object { {
"label": "2child2", "label": "2child2",
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -1,147 +0,0 @@
import {
Button,
Menu,
MenuItem,
MenuList,
MenuPopover,
MenuTrigger,
Spinner,
Tree,
TreeItem,
TreeItemLayout,
TreeOpenChangeData,
TreeOpenChangeEvent,
} from "@fluentui/react-components";
import { MoreHorizontal20Regular } from "@fluentui/react-icons";
import { tokens } from "@fluentui/react-theme";
import * as React from "react";
export interface TreeNode2MenuItem {
label: string;
onClick: () => void;
iconSrc?: string;
isDisabled?: boolean;
styleClass?: string;
}
export interface TreeNode2 {
label: string;
id?: string;
children?: TreeNode2[];
contextMenu?: TreeNode2MenuItem[];
iconSrc?: string;
isExpanded?: boolean;
className?: string;
isAlphaSorted?: boolean;
// data?: any; // Piece of data corresponding to this node
timestamp?: number;
isLeavesParentsSeparate?: boolean; // Display parents together first, then leaves
isLoading?: boolean;
isScrollable?: boolean;
isSelected?: () => boolean;
onClick?: () => void; // Only if a leaf, other click will expand/collapse
onExpanded?: () => Promise<void>;
onCollapsed?: () => void;
onContextMenuOpen?: () => void;
}
export interface TreeNode2ComponentProps {
node: TreeNode2;
className?: string;
treeNodeId: string;
}
const getTreeIcon = (iconSrc: string): JSX.Element => <img src={iconSrc} alt="" style={{ width: 20, height: 20 }} />;
export const TreeNode2Component: React.FC<TreeNode2ComponentProps> = ({
node,
treeNodeId,
}: TreeNode2ComponentProps): JSX.Element => {
const [isLoading, setIsLoading] = React.useState<boolean>(false);
const getSortedChildren = (treeNode: TreeNode2): TreeNode2[] => {
if (!treeNode || !treeNode.children) {
return undefined;
}
const compareFct = (a: TreeNode2, b: TreeNode2) => a.label.localeCompare(b.label);
let unsortedChildren;
if (treeNode.isLeavesParentsSeparate) {
// Separate parents and leave
const parents: TreeNode2[] = treeNode.children.filter((node) => node.children);
const leaves: TreeNode2[] = treeNode.children.filter((node) => !node.children);
if (treeNode.isAlphaSorted) {
parents.sort(compareFct);
leaves.sort(compareFct);
}
unsortedChildren = parents.concat(leaves);
} else {
unsortedChildren = treeNode.isAlphaSorted ? treeNode.children.sort(compareFct) : treeNode.children;
}
return unsortedChildren;
};
const onOpenChange = (_: TreeOpenChangeEvent, data: TreeOpenChangeData) => {
if (!node.isExpanded && data.open && node.onExpanded) {
// Catch the transition non-expanded to expanded
setIsLoading(true);
node.onExpanded?.().then(() => setIsLoading(false));
} else if (node.isExpanded && !data.open && node.onCollapsed) {
// Catch the transition expanded to non-expanded
node.onCollapsed?.();
}
};
return (
<TreeItem
value={treeNodeId}
itemType={node.children !== undefined ? "branch" : "leaf"}
style={{ height: "100%" }}
onOpenChange={onOpenChange}
>
<TreeItemLayout
className={node.className}
actions={
node.contextMenu && (
<Menu>
<MenuTrigger disableButtonEnhancement>
<Button aria-label="More options" appearance="subtle" icon={<MoreHorizontal20Regular />} />
</MenuTrigger>
<MenuPopover>
<MenuList>
{node.contextMenu.map((menuItem) => (
<MenuItem disabled={menuItem.isDisabled} key={menuItem.label} onClick={menuItem.onClick}>
{menuItem.label}
</MenuItem>
))}
</MenuList>
</MenuPopover>
</Menu>
)
}
expandIcon={isLoading ? <Spinner size="extra-tiny" /> : undefined}
iconBefore={node.iconSrc && getTreeIcon(node.iconSrc)}
style={{
backgroundColor: node.isSelected && node.isSelected() ? tokens.colorNeutralBackground1Selected : undefined,
}}
>
<span onClick={() => node.onClick?.()}>{node.label}</span>
</TreeItemLayout>
{!node.isLoading && node.children?.length > 0 && (
<Tree style={{ overflow: node.isScrollable ? "auto" : undefined }}>
{getSortedChildren(node).map((childNode: TreeNode2) => (
<TreeNode2Component
key={childNode.label}
node={childNode}
treeNodeId={`${treeNodeId}/${childNode.label}`}
/>
))}
</Tree>
)}
</TreeItem>
);
};

View File

@@ -1,13 +1,17 @@
import * as msal from "@azure/msal-browser";
import { Link } from "@fluentui/react/lib/Link"; import { Link } from "@fluentui/react/lib/Link";
import { isPublicInternetAccessAllowed } from "Common/DatabaseAccountUtility"; import { isPublicInternetAccessAllowed } from "Common/DatabaseAccountUtility";
import { sendMessage } from "Common/MessageHandler"; import { sendMessage } from "Common/MessageHandler";
import { Platform, configContext } from "ConfigContext"; import { Platform, configContext } from "ConfigContext";
import { MessageTypes } from "Contracts/ExplorerContracts"; import { MessageTypes } from "Contracts/ExplorerContracts";
import { useDataPlaneRbac } from "Explorer/Panes/SettingsPane/SettingsPane";
import { getCopilotEnabled, isCopilotFeatureRegistered } from "Explorer/QueryCopilot/Shared/QueryCopilotClient"; import { getCopilotEnabled, isCopilotFeatureRegistered } from "Explorer/QueryCopilot/Shared/QueryCopilotClient";
import { IGalleryItem } from "Juno/JunoClient"; import { IGalleryItem } from "Juno/JunoClient";
import { scheduleRefreshDatabaseResourceToken } from "Platform/Fabric/FabricUtil"; import { scheduleRefreshDatabaseResourceToken } from "Platform/Fabric/FabricUtil";
import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility"; import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
import { acquireTokenWithMsal, getMsalInstance } from "Utils/AuthorizationUtils";
import { allowedNotebookServerUrls, validateEndpoint } from "Utils/EndpointUtils"; import { allowedNotebookServerUrls, validateEndpoint } from "Utils/EndpointUtils";
import { update } from "Utils/arm/generatedClients/cosmos/databaseAccounts";
import { useQueryCopilot } from "hooks/useQueryCopilot"; import { useQueryCopilot } from "hooks/useQueryCopilot";
import * as ko from "knockout"; import * as ko from "knockout";
import React from "react"; import React from "react";
@@ -30,15 +34,13 @@ import { PhoenixClient } from "../Phoenix/PhoenixClient";
import * as ExplorerSettings from "../Shared/ExplorerSettings"; import * as ExplorerSettings from "../Shared/ExplorerSettings";
import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants"; import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor"; import * as TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor";
import { isAccountNewerThanThresholdInMs, userContext } from "../UserContext"; import { isAccountNewerThanThresholdInMs, updateUserContext, userContext } from "../UserContext";
import { getCollectionName, getUploadName } from "../Utils/APITypeUtils"; import { getCollectionName, getUploadName } from "../Utils/APITypeUtils";
import { stringToBlob } from "../Utils/BlobUtils"; import { stringToBlob } from "../Utils/BlobUtils";
import { isCapabilityEnabled } from "../Utils/CapabilityUtils"; import { isCapabilityEnabled } from "../Utils/CapabilityUtils";
import { fromContentUri, toRawContentUri } from "../Utils/GitHubUtils"; import { fromContentUri, toRawContentUri } from "../Utils/GitHubUtils";
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils"; import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../Utils/NotificationConsoleUtils"; import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../Utils/NotificationConsoleUtils";
import { update } from "../Utils/arm/generatedClients/cosmos/databaseAccounts";
import { listByDatabaseAccount } from "../Utils/arm/generatedClients/cosmosNotebooks/notebookWorkspaces";
import { useSidePanel } from "../hooks/useSidePanel"; import { useSidePanel } from "../hooks/useSidePanel";
import { useTabs } from "../hooks/useTabs"; import { useTabs } from "../hooks/useTabs";
import "./ComponentRegisterer"; import "./ComponentRegisterer";
@@ -56,7 +58,6 @@ import { AddCollectionPanel } from "./Panes/AddCollectionPanel";
import { CassandraAddCollectionPane } from "./Panes/CassandraAddCollectionPane/CassandraAddCollectionPane"; import { CassandraAddCollectionPane } from "./Panes/CassandraAddCollectionPane/CassandraAddCollectionPane";
import { ExecuteSprocParamsPane } from "./Panes/ExecuteSprocParamsPane/ExecuteSprocParamsPane"; import { ExecuteSprocParamsPane } from "./Panes/ExecuteSprocParamsPane/ExecuteSprocParamsPane";
import { StringInputPane } from "./Panes/StringInputPane/StringInputPane"; import { StringInputPane } from "./Panes/StringInputPane/StringInputPane";
import { UploadFilePane } from "./Panes/UploadFilePane/UploadFilePane";
import { UploadItemsPane } from "./Panes/UploadItemsPane/UploadItemsPane"; import { UploadItemsPane } from "./Panes/UploadItemsPane/UploadItemsPane";
import { CassandraAPIDataClient, TableDataClient, TablesAPIDataClient } from "./Tables/TableDataClient"; import { CassandraAPIDataClient, TableDataClient, TablesAPIDataClient } from "./Tables/TableDataClient";
import NotebookV2Tab, { NotebookTabOptions } from "./Tabs/NotebookV2Tab"; import NotebookV2Tab, { NotebookTabOptions } from "./Tabs/NotebookV2Tab";
@@ -138,14 +139,6 @@ export default class Explorer {
this.isTabsContentExpanded = ko.observable(false); this.isTabsContentExpanded = ko.observable(false);
document.addEventListener(
"contextmenu",
(e) => {
e.preventDefault();
},
false,
);
$(() => { $(() => {
$(document.body).click(() => $(".commandDropdownContainer").hide()); $(document.body).click(() => $(".commandDropdownContainer").hide());
}); });
@@ -261,12 +254,48 @@ export default class Explorer {
}; };
useDialog.getState().openDialog(addSynapseLinkDialogProps); useDialog.getState().openDialog(addSynapseLinkDialogProps);
TelemetryProcessor.traceStart(Action.EnableAzureSynapseLink); TelemetryProcessor.traceStart(Action.EnableAzureSynapseLink);
}
// TODO: return result public async openLoginForEntraIDPopUp(): Promise<void> {
if (userContext.databaseAccount.properties?.documentEndpoint) {
const hrefEndpoint = new URL(userContext.databaseAccount.properties.documentEndpoint).href.replace(
/\/$/,
"/.default",
);
const msalInstance = await getMsalInstance();
try {
const response = await msalInstance.loginPopup({
redirectUri: configContext.msalRedirectURI,
scopes: [],
});
localStorage.setItem("cachedTenantId", response.tenantId);
const cachedAccount = msalInstance.getAllAccounts()?.[0];
msalInstance.setActiveAccount(cachedAccount);
const aadToken = await acquireTokenWithMsal(msalInstance, {
forceRefresh: true,
scopes: [hrefEndpoint],
authority: `${configContext.AAD_ENDPOINT}${localStorage.getItem("cachedTenantId")}`,
});
updateUserContext({ aadToken: aadToken });
useDataPlaneRbac.setState({ aadTokenUpdated: true });
} catch (error) {
if (error instanceof msal.AuthError && error.errorCode === msal.BrowserAuthErrorMessage.popUpWindowError.code) {
logConsoleError(
"We were unable to establish authorization for this account, due to pop-ups being disabled in the browser.\nPlease enable pop-ups for this site and try again",
);
} else {
const errorJson = JSON.stringify(error);
logConsoleError(
`Failed to perform authorization for this account, due to the following error: \n${errorJson}`,
);
}
}
}
} }
public openNPSSurveyDialog(): void { public openNPSSurveyDialog(): void {
if (!Platform.Portal) { if (!Platform.Portal || !["Postgres", "SQL", "Mongo"].includes(userContext.apiType)) {
return; return;
} }
@@ -364,16 +393,16 @@ export default class Explorer {
return true; return true;
}; };
public onRefreshResourcesClick = (): void => { public onRefreshResourcesClick = async (): Promise<void> => {
if (configContext.platform === Platform.Fabric) { if (configContext.platform === Platform.Fabric) {
scheduleRefreshDatabaseResourceToken(true).then(() => this.refreshAllDatabases()); scheduleRefreshDatabaseResourceToken(true).then(() => this.refreshAllDatabases());
return; return;
} }
userContext.authType === AuthType.ResourceToken await (userContext.authType === AuthType.ResourceToken
? this.refreshDatabaseForResourceToken() ? this.refreshDatabaseForResourceToken()
: this.refreshAllDatabases(); : this.refreshAllDatabases());
this.refreshNotebookList(); await this.refreshNotebookList();
}; };
// Facade // Facade
@@ -510,104 +539,6 @@ export default class Explorer {
.then((memoryUsageInfo) => useNotebook.getState().setMemoryUsageInfo(memoryUsageInfo)); .then((memoryUsageInfo) => useNotebook.getState().setMemoryUsageInfo(memoryUsageInfo));
} }
public resetNotebookWorkspace(): void {
if (!useNotebook.getState().isNotebookEnabled || !this.notebookManager?.notebookClient) {
handleError(
"Attempt to reset notebook workspace, but notebook is not enabled",
"Explorer/resetNotebookWorkspace",
);
return;
}
const dialogContent = useNotebook.getState().isPhoenixNotebooks
? "Notebooks saved in the temporary workspace will be deleted. Do you want to proceed?"
: "This lets you keep your notebook files and the workspace will be restored to default. Proceed anyway?";
const resetConfirmationDialogProps: DialogProps = {
isModal: true,
title: "Reset Workspace",
subText: dialogContent,
primaryButtonText: "OK",
secondaryButtonText: "Cancel",
onPrimaryButtonClick: this._resetNotebookWorkspace,
onSecondaryButtonClick: () => useDialog.getState().closeDialog(),
};
useDialog.getState().openDialog(resetConfirmationDialogProps);
}
private async _containsDefaultNotebookWorkspace(databaseAccount: DataModels.DatabaseAccount): Promise<boolean> {
if (!databaseAccount) {
return false;
}
try {
const { value: workspaces } = await listByDatabaseAccount(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
);
return workspaces && workspaces.length > 0 && workspaces.some((workspace) => workspace.name === "default");
} catch (error) {
Logger.logError(getErrorMessage(error), "Explorer/_containsDefaultNotebookWorkspace");
return false;
}
}
private _resetNotebookWorkspace = async () => {
useDialog.getState().closeDialog();
const clearInProgressMessage = logConsoleProgress("Resetting notebook workspace");
let connectionStatus: ContainerConnectionInfo;
try {
const notebookServerInfo = useNotebook.getState().notebookServerInfo;
if (!notebookServerInfo || !notebookServerInfo.notebookServerEndpoint) {
const error = "No server endpoint detected";
Logger.logError(error, "NotebookContainerClient/resetWorkspace");
logConsoleError(error);
return;
}
TelemetryProcessor.traceStart(Action.PhoenixResetWorkspace, {
dataExplorerArea: Areas.Notebook,
});
if (useNotebook.getState().isPhoenixNotebooks) {
useTabs.getState().closeAllNotebookTabs(true);
connectionStatus = {
status: ConnectionStatusType.Connecting,
};
useNotebook.getState().setConnectionInfo(connectionStatus);
}
const connectionInfo = await this.notebookManager?.notebookClient.resetWorkspace();
if (connectionInfo?.status !== HttpStatusCodes.OK) {
throw new Error(`Reset Workspace: Received status code- ${connectionInfo?.status}`);
}
if (!connectionInfo?.data?.phoenixServiceUrl) {
throw new Error(`Reset Workspace: PhoenixServiceUrl is invalid!`);
}
if (useNotebook.getState().isPhoenixNotebooks) {
await this.setNotebookInfo(true, connectionInfo, connectionStatus);
useNotebook.getState().setIsRefreshed(!useNotebook.getState().isRefreshed);
}
logConsoleInfo("Successfully reset notebook workspace");
TelemetryProcessor.traceSuccess(Action.PhoenixResetWorkspace, {
dataExplorerArea: Areas.Notebook,
});
} catch (error) {
logConsoleError(`Failed to reset notebook workspace: ${error}`);
TelemetryProcessor.traceFailure(Action.PhoenixResetWorkspace, {
dataExplorerArea: Areas.Notebook,
error: getErrorMessage(error),
errorStack: getErrorStack(error),
});
if (useNotebook.getState().isPhoenixNotebooks) {
connectionStatus = {
status: ConnectionStatusType.Failed,
};
useNotebook.getState().resetContainerConnection(connectionStatus);
useNotebook.getState().setIsRefreshed(!useNotebook.getState().isRefreshed);
}
throw error;
} finally {
clearInProgressMessage();
}
};
private getDeltaDatabases( private getDeltaDatabases(
updatedDatabaseList: DataModels.Database[], updatedDatabaseList: DataModels.Database[],
databases: ViewModels.Database[], databases: ViewModels.Database[],
@@ -1010,92 +941,6 @@ export default class Explorer {
); );
} }
/**
* This creates a new notebook file, then opens the notebook
*/
public async onNewNotebookClicked(parent?: NotebookContentItem, isGithubTree?: boolean): Promise<void> {
if (!useNotebook.getState().isNotebookEnabled || !this.notebookManager?.notebookContentClient) {
const error = "Attempt to create new notebook, but notebook is not enabled";
handleError(error, "Explorer/onNewNotebookClicked");
throw new Error(error);
}
if (useNotebook.getState().isPhoenixNotebooks) {
if (isGithubTree) {
await this.allocateContainer(PoolIdType.DefaultPoolId);
parent = parent || this.resourceTree.myNotebooksContentRoot;
this.createNewNoteBook(parent, isGithubTree);
} else {
useDialog.getState().showOkCancelModalDialog(
Notebook.newNotebookModalTitle,
undefined,
"Create",
async () => {
await this.allocateContainer(PoolIdType.DefaultPoolId);
parent = parent || this.resourceTree.myNotebooksContentRoot;
this.createNewNoteBook(parent, isGithubTree);
},
"Cancel",
undefined,
this.getNewNoteWarningText(),
);
}
} else {
parent = parent || this.resourceTree.myNotebooksContentRoot;
this.createNewNoteBook(parent, isGithubTree);
}
}
private getNewNoteWarningText(): JSX.Element {
return (
<>
<p>{Notebook.newNotebookModalContent1}</p>
<br />
<p>
{Notebook.newNotebookModalContent2}
<Link href={Notebook.cosmosNotebookHomePageUrl} target="_blank">
{Notebook.learnMore}
</Link>
</p>
</>
);
}
private createNewNoteBook(parent?: NotebookContentItem, isGithubTree?: boolean): void {
const clearInProgressMessage = logConsoleProgress(`Creating new notebook in ${parent.path}`);
const startKey: number = TelemetryProcessor.traceStart(Action.CreateNewNotebook, {
dataExplorerArea: Constants.Areas.Notebook,
});
this.notebookManager?.notebookContentClient
.createNewNotebookFile(parent, isGithubTree)
.then((newFile: NotebookContentItem) => {
logConsoleInfo(`Successfully created: ${newFile.name}`);
TelemetryProcessor.traceSuccess(
Action.CreateNewNotebook,
{
dataExplorerArea: Constants.Areas.Notebook,
},
startKey,
);
return this.openNotebook(newFile);
})
.then(() => this.resourceTree.triggerRender())
.catch((error) => {
const errorMessage = `Failed to create a new notebook: ${getErrorMessage(error)}`;
logConsoleError(errorMessage);
TelemetryProcessor.traceFailure(
Action.CreateNewNotebook,
{
dataExplorerArea: Constants.Areas.Notebook,
error: errorMessage,
errorStack: getErrorStack(error),
},
startKey,
);
})
.finally(clearInProgressMessage);
}
// TODO: Delete this function when ResourceTreeAdapter is removed. // TODO: Delete this function when ResourceTreeAdapter is removed.
public async refreshContentItem(item: NotebookContentItem): Promise<void> { public async refreshContentItem(item: NotebookContentItem): Promise<void> {
if (!useNotebook.getState().isNotebookEnabled || !this.notebookManager?.notebookContentClient) { if (!useNotebook.getState().isNotebookEnabled || !this.notebookManager?.notebookContentClient) {
@@ -1130,10 +975,6 @@ export default class Explorer {
let title: string; let title: string;
switch (kind) { switch (kind) {
case ViewModels.TerminalKind.Default:
title = "Terminal";
break;
case ViewModels.TerminalKind.Mongo: case ViewModels.TerminalKind.Mongo:
title = "Mongo Shell"; title = "Mongo Shell";
break; break;
@@ -1287,36 +1128,6 @@ export default class Explorer {
.openSidePanel("Input parameters", <ExecuteSprocParamsPane storedProcedure={storedProcedure} />); .openSidePanel("Input parameters", <ExecuteSprocParamsPane storedProcedure={storedProcedure} />);
} }
public openUploadFilePanel(parent?: NotebookContentItem): void {
if (useNotebook.getState().isPhoenixNotebooks) {
useDialog.getState().showOkCancelModalDialog(
Notebook.newNotebookUploadModalTitle,
undefined,
"Upload",
async () => {
await this.allocateContainer(PoolIdType.DefaultPoolId);
parent = parent || this.resourceTree.myNotebooksContentRoot;
this.uploadFilePanel(parent);
},
"Cancel",
undefined,
this.getNewNoteWarningText(),
);
} else {
parent = parent || this.resourceTree.myNotebooksContentRoot;
this.uploadFilePanel(parent);
}
}
private uploadFilePanel(parent?: NotebookContentItem): void {
useSidePanel
.getState()
.openSidePanel(
"Upload file to notebook server",
<UploadFilePane uploadFile={(name: string, content: string) => this.uploadFile(name, content, parent)} />,
);
}
public getDownloadModalConent(fileName: string): JSX.Element { public getDownloadModalConent(fileName: string): JSX.Element {
if (useNotebook.getState().isPhoenixNotebooks) { if (useNotebook.getState().isPhoenixNotebooks) {
return ( return (
@@ -1385,21 +1196,25 @@ export default class Explorer {
} }
public async refreshSampleData(): Promise<void> { public async refreshSampleData(): Promise<void> {
if (!userContext.sampleDataConnectionInfo) { try {
if (!userContext.sampleDataConnectionInfo) {
return;
}
const collection: DataModels.Collection = await readSampleCollection();
if (!collection) {
return;
}
const databaseId = userContext.sampleDataConnectionInfo?.databaseId;
if (!databaseId) {
return;
}
const sampleDataResourceTokenCollection = new ResourceTokenCollection(this, databaseId, collection, true);
useDatabases.setState({ sampleDataResourceTokenCollection });
} catch (error) {
Logger.logError(getErrorMessage(error), "Explorer");
return; return;
} }
const collection: DataModels.Collection = await readSampleCollection();
if (!collection) {
return;
}
const databaseId = userContext.sampleDataConnectionInfo?.databaseId;
if (!databaseId) {
return;
}
const sampleDataResourceTokenCollection = new ResourceTokenCollection(this, databaseId, collection, true);
useDatabases.setState({ sampleDataResourceTokenCollection });
} }
} }

View File

@@ -737,17 +737,17 @@ export class D3ForceGraph implements GraphRenderer {
.on("dblclick", function (this: Element, _: MouseEvent, d: D3Node) { .on("dblclick", function (this: Element, _: MouseEvent, d: D3Node) {
// https://stackoverflow.com/a/41945742 ('this' implicitly has type 'any' because it does not have a type annotation) // https://stackoverflow.com/a/41945742 ('this' implicitly has type 'any' because it does not have a type annotation)
// this is the <g> element // this is the <g> element
self.onNodeClicked(this.parentNode, d); self.onNodeClicked(this.parentNode as BaseType, d);
}) })
.on("click", function (this: Element, _: MouseEvent, d: D3Node) { .on("click", function (this: Element, _: MouseEvent, d: D3Node) {
// this is the <g> element // this is the <g> element
self.onNodeClicked(this.parentNode, d); self.onNodeClicked(this.parentNode as BaseType, d);
}) })
.on("keypress", function (this: Element, event: KeyboardEvent, d: D3Node) { .on("keypress", function (this: Element, event: KeyboardEvent, d: D3Node) {
if (event.charCode === Constants.KeyCodes.Space || event.charCode === Constants.KeyCodes.Enter) { if (event.charCode === Constants.KeyCodes.Space || event.charCode === Constants.KeyCodes.Enter) {
event.stopPropagation(); event.stopPropagation();
// this is the <g> element // this is the <g> element
self.onNodeClicked(this.parentNode, d); self.onNodeClicked(this.parentNode as BaseType, d);
} }
}); });
var nodeSize = this.igraphConfig.nodeSize; var nodeSize = this.igraphConfig.nodeSize;

View File

@@ -1,4 +1,4 @@
import { GraphData, GremlinVertex, GremlinEdge } from "./GraphData"; import { GraphData, GremlinEdge, GremlinVertex } from "./GraphData";
describe("Graph Data", () => { describe("Graph Data", () => {
it("should set only one node as root", () => { it("should set only one node as root", () => {
@@ -54,12 +54,12 @@ describe("Graph Data", () => {
// in edge // in edge
graphData.removeEdge("e1", false); graphData.removeEdge("e1", false);
expect(graphData.edges.length).toBe(1); expect(graphData.edges.length).toBe(1);
expect(graphData).not.toContain(jasmine.objectContaining({ id: "e1" })); expect(graphData).not.toContainEqual({ id: "e1" });
// out edge // out edge
graphData.removeEdge("e2", false); graphData.removeEdge("e2", false);
expect(graphData.edges.length).toBe(0); expect(graphData.edges.length).toBe(0);
expect(graphData).not.toContain(jasmine.objectContaining({ id: "e2" })); expect(graphData).not.toContainEqual({ id: "e2" });
}); });
it("should get string node property", () => { it("should get string node property", () => {

View File

@@ -342,7 +342,7 @@ describe("GraphExplorer", () => {
}); });
describe("Load Graph button", () => { describe("Load Graph button", () => {
beforeEach(async (done) => { beforeEach((done) => {
const backendResponses: BackendResponses = {}; const backendResponses: BackendResponses = {};
backendResponses["g.V()"] = backendResponses["g.V('1')"] = { backendResponses["g.V()"] = backendResponses["g.V('1')"] = {
response: [{ id: "1", type: "vertex" }], response: [{ id: "1", type: "vertex" }],
@@ -632,24 +632,15 @@ describe("GraphExplorer", () => {
it("should display RU consumption", () => { it("should display RU consumption", () => {
// Find link for query stats // Find link for query stats
const links = wrapper.find(".toggleSwitch"); const queryStatsTab = wrapper.find(`button[name="${GraphExplorer.QUERY_STATS_BUTTON_LABEL}"]`);
queryStatsTab.simulate("click");
const values = wrapper.find(".queryMetricsSummary td");
let isRUDisplayed = false; let isRUDisplayed = false;
for (let i = 0; i < links.length; i++) { values.forEach((value) => {
const link = links.at(i); if (Number(value.text()) === gVRU) {
if (link.text() === GraphExplorer.QUERY_STATS_BUTTON_LABEL) { isRUDisplayed = true;
link.simulate("click");
const values = wrapper.find(".queryMetricsSummary td");
for (let j = 0; j < values.length; j++) {
if (Number(values.at(j).text()) === gVRU) {
isRUDisplayed = true;
break;
}
}
break;
} }
} });
expect(isRUDisplayed).toBe(true); expect(isRUDisplayed).toBe(true);
}); });
}); });

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