Compare commits

..

138 Commits

Author SHA1 Message Date
Sung-Hyun Kang
0b7cbfbf23 move nps survey open dialog call to after explorer initialization 2024-08-13 13:06:01 -05:00
Sung-Hyun Kang
76913d42f1 Merge branch 'master' into NPS_Dialog 2024-08-13 12:44:32 -05:00
Sung-Hyun Kang
7ab2bd38f4 move nps survey open dialog call to after explorer initialization 2024-08-13 12:34:46 -05: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
Asier Isayas
dfcb771939 Activate Mongo Proxy and Cassandra Proxy in Prod (#1794)
* activate Mongo Proxy and Cassandra Proxy in Prod

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

* fix pr check tests

---------

Co-authored-by: Asier Isayas <aisayas@microsoft.com>
2024-04-09 13:45:36 -07:00
jawelton74
6925fa8e4e Replace Entra app client secret auth with OpenID Connect in E2E tests. (#1792)
* Use Az login with OpenID connection to get test credentials.

* Set subscription id environment variable.

* Update testExplorer and cleanup job.

* Retrieve access token in test case and pass to testExplorer.

* Add debug tracing for tests.

* Set up other mongo test to use Az CLI creds.

* Revert subscription id retrieval.

* Add CLI credentials retrieval to rest of tests.

* Fix missing imports.

* Clean up redundant code.

* Remove commented import statement.
2024-04-09 10:55:08 -07:00
jawelton74
7f6338b68b Change copilot settings call to use new backend endpoint. (#1781)
* Change copilot settings call to use new backend endpoint.

* Refactor EndpointUtils function for new backend enablement.
2024-04-04 10:18:50 -07:00
Ashley Stanton-Nurse
db50f42832 [Task #3061771] Correct render order issues on undo (#1785)
* fix #3061771 by correcting render order issues on undo

* clarifying comment

* fix lints

* push an undo stop before executing edits

* tidy up some unnecessary comments
2024-04-04 09:17:09 -07:00
Ashley Stanton-Nurse
f533eeb0fc add support for react dev tools in the cosmos explorer (#1788) 2024-04-04 09:16:23 -07:00
sunghyunkang1111
3c5d899e47 add the new message to the bottom to avoid contract breaking (#1786) 2024-04-02 17:54:53 -05:00
Ashley Stanton-Nurse
b44778b00a fix #3061738 by unclobbering some Monaco styles we clobber (#1784) 2024-04-02 10:51:19 -07:00
sunghyunkang1111
1464745659 Add activate/close tab contracts and add to queryTab (#1783) 2024-04-02 11:49:33 -05:00
Asier Isayas
18cc2a4195 Activate Mongo and Cassandra Proxies in MPAC (#1776)
* Fix API endpoint for CassandraProxy query API

* activated mongo proxy

* added mpac

* Activate CassandraProxy API endpoints for MPAC

* Run npm format

* Set CASSANDRA_PROXY_OUTBOUND_IPS_ALLOWLISTED when we detect new
Cassandra Proxy endpoints in IP rules.

* query documents API fix

* simplify ip check

---------

Co-authored-by: Senthamil Sindhu <sindhuba@microsoft.com>
Co-authored-by: Asier Isayas <aisayas@microsoft.com>
Co-authored-by: Jade Welton <jawelton@microsoft.com>
2024-04-02 09:34:58 -07:00
sindhuba
86f2bc171f Remove notebooks UI (#1779)
* Fix API endpoint for CassandraProxy query API

* Remove notebooks UI components

* Fix tests

* Address comments

* Fix unit tests

* Remove commented code
2024-04-01 08:44:42 -07:00
jawelton74
cabedf4a29 Enable new backend endpoint to be passed to Data Explorer via message. (#1782) 2024-04-01 07:54:04 -07:00
sunghyunkang1111
5aa6b0abe1 fix opening collections (#1780)
* fix opening collections

* fix opening collections

* fix opening collections

* fix opening collections
2024-03-28 12:10:32 -05:00
Vsevolod Kukol
f24b0bcf1b Show the Feedback button in Portal only (#1775)
This feature is not supported on any other platform incl. Hosted.
2024-03-28 16:05:38 +01:00
JustinKol
56408a97d7 Add container ids to tabs (#1772)
* Added container ids to tabs

* prettier run

* Updated for undefined scenarios

* prettier

* added ellipsis to long container names

* added slice

* prettier

* Added ellipsis to long DB names in tabs

* Added undefined DB case

* Replaced dots with ellipsis character

* corrected undefined return value
2024-03-26 12:36:04 -04:00
Vsevolod Kukol
0df68c4967 Fix initial container loading in Fabric (#1771)
* Fix initial container loading in Fabric

There is a rendering issue where the documents table doesn't resize properly if explorer is loaded in the beackground (invisible).
To workaround this, track DE visibility from Fabric RPC and open the first container only once DE becomes visible. If DE has been already shown, open the container right away.

* Preserve glitchy behavior if Fabric UX doesn't send isVisible

* Preserve Fabric visibility in global status
and fix a race condition where visibility might change during initialization.
2024-03-26 17:22:15 +01:00
Asier Isayas
e09930d9d0 Support Token API in new Portal Backend (#1773)
* added support for generate token

* fix checks

* fix checks

* deactivate mongo proxy

* fix tests

* remove mongo proxy from mpac

* change endpoints to prod

* npm run format

* add await

---------

Co-authored-by: Asier Isayas <aisayas@microsoft.com>
2024-03-22 13:18:02 -04:00
sunghyunkang1111
da2e874ae6 Fix bugs on data transfer and bring back query explanation and remove query prompt from editor (#1777)
* Fix minor issues

* add back preview tag

* bring back query explanation and remove prompt in editor
2024-03-21 11:23:42 -05:00
Vsevolod Kukol
a524138ac9 Don't show the new Home button in Fabric (#1774)
as the whole Home tab feature is not supported there.
2024-03-20 00:28:24 +01:00
sindhuba
39b0fb9e2c Fix API endpoint for CassandraProxy query API (#1769) 2024-03-18 10:49:33 -07:00
sunghyunkang1111
ac22e88d9c rebranding of inline copilot (#1767)
* rebranding of inline copilot

* rebranding of inline copilot

* rebranding of inline copilot

* fix styling
2024-03-18 12:15:24 -05:00
Laurent Nguyen
91d9e27049 Turn off fetching authorization token (#1766) 2024-03-14 21:56:26 +01:00
JustinKol
f881f7fd2f Enabled the ability to close the home tab (#1765) 2024-03-13 16:32:59 -04:00
vchske
4c74525b5d Adding computed properties to Settings tab for containers (#1763)
* Adding computed properties to Settings tab for containers

* Fixing files for prettier and a test snapshot
2024-03-12 14:55:14 -07:00
sindhuba
1a6d8d5357 Add CassandraProxy support in DE (#1764) 2024-03-11 15:17:01 -07:00
sunghyunkang1111
56c0049e9a add feedback policies integration with copilot (#1745)
* add feedback policies integration with copilot

* remove teaching bubble and welcome modal

* force prod phoenix endpoint in MPAC

* force prod phoenix endpoint in MPAC
2024-03-06 10:33:21 -06:00
MokireddySampath
b3837a089d color of the link has been changed to get the approved color contrast ratio of 4.5:1 (#1710) 2024-03-06 12:57:05 +05:30
MokireddySampath
e68aaebca6 Asterisk has beenadded beside the heading of input to show that it is a mandatory input (#1749) 2024-03-06 12:56:34 +05:30
MokireddySampath
6e1c4fd037 Bug 1242529: [Usable - Azure Cosmos DB - Input Parameters]: Aria-label is not descriptive enough for the 'Close(x)' button of 'Input Parameters' blade. (#1760)
* screen reader name for the button has been changed to read out the name of the dialog box

* tests have been updated
2024-03-06 12:56:07 +05:30
MokireddySampath
0039adf1c2 Border has been added to distinguish clear notifications from text (#1750) 2024-03-06 12:54:44 +05:30
MokireddySampath
5d4e9d82bb Bug 1240907: Aria-label is not descriptive enough for 'More(...)' button present under 'SQL API' section. (#1748)
* screen reader name for the more button has been modified as suggested

* e2e test have been updated

* e2e tests updated
2024-03-06 12:48:46 +05:30
MokireddySampath
47bdc9c426 styling changes have been made o remove the overlaping of focus outlines (#1721) 2024-03-06 12:47:57 +05:30
MokireddySampath
b8457e3bf9 defect2278780 (#1472)
* arialabel has been added to close button of invitational youtube video

* heading role has been addedd and tag has been changed to h1

* outline has been restored to choose columns link in entities page

* Update QuickstartCarousel.tsx

* Update SplashScreen.tsx

* Update TableEntity.tsx

* outline for edit entity has been added on focus

* keyboard accessibility added to rows in table entities

* Update queryBuilder.less

* Update TableEntity.tsx

* Update PanelComponent.less

* Update DataTableBindingManager.ts

* Update DataTableBindingManager.ts

* Update DataTableBindingManager.ts

* Update DataTableBindingManager.ts

* Update DataTableBindingManager.ts
2024-03-06 12:43:44 +05:30
332 changed files with 38668 additions and 31615 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

@@ -8,6 +8,9 @@ on:
pull_request: pull_request:
branches: branches:
- master - master
permissions:
id-token: write
contents: read
jobs: jobs:
codemetrics: codemetrics:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -101,72 +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
NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET: ${{ secrets.NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET }}
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/notebooks/upload.spec.ts // TEMP disabled since notebooks service is off
- ./test/sql/resourceToken.spec.ts
- ./test/tables/container.spec.ts
steps:
- uses: actions/checkout@v4
- 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/')
@@ -216,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

View File

@@ -9,6 +9,10 @@ on:
# Once every hour # Once every hour
- cron: "0 15 * * *" - cron: "0 15 * * *"
permissions:
id-token: write
contents: read
# A workflow run is made up of one or more jobs that can run sequentially or in parallel # A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs: jobs:
# This workflow contains a single job called "build" # This workflow contains a single job called "build"
@@ -16,10 +20,17 @@ jobs:
name: "Cleanup Test Database Accounts" name: "Cleanup Test Database Accounts"
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: env:
NOTEBOOKS_TEST_RUNNER_CLIENT_ID: ${{ secrets.NOTEBOOKS_TEST_RUNNER_CLIENT_ID }} AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET: ${{ secrets.NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET }}
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- 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 - name: Use Node.js 18.x
uses: actions/setup-node@v1 uses: actions/setup-node@v1
with: with:

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"
}

View File

@@ -1,5 +1,5 @@
{ {
"JUNO_ENDPOINT": "https://tools-staging.cosmos.azure.com", "JUNO_ENDPOINT": "https://tools.cosmos.azure.com",
"isTerminalEnabled" : true, "isTerminalEnabled": true,
"isPhoenixEnabled" : true "isPhoenixEnabled": true
} }

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

3
images/Home_16.svg Normal file
View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.31299 1.26164C7.69849 0.897163 8.30151 0.897163 8.68701 1.26164L13.5305 5.84098C13.8302 6.12431 14 6.51853 14 6.93094V12.5002C14 13.3286 13.3284 14.0002 12.5 14.0002H10.5C9.67157 14.0002 9 13.3286 9 12.5002V10.0002C9 9.72407 8.77614 9.50021 8.5 9.50021H7.5C7.22386 9.50021 7 9.72407 7 10.0002V12.5002C7 13.3286 6.32843 14.0002 5.5 14.0002H3.5C2.67157 14.0002 2 13.3286 2 12.5002V6.93094C2 6.51853 2.1698 6.12431 2.46948 5.84098L7.31299 1.26164ZM8 1.98828L3.15649 6.56762C3.0566 6.66207 3 6.79347 3 6.93094V12.5002C3 12.7763 3.22386 13.0002 3.5 13.0002H5.5C5.77614 13.0002 6 12.7763 6 12.5002V10.0002C6 9.17179 6.67157 8.50022 7.5 8.50022H8.5C9.32843 8.50022 10 9.17179 10 10.0002V12.5002C10 12.7763 10.2239 13.0002 10.5 13.0002H12.5C12.7761 13.0002 13 12.7763 13 12.5002V6.93094C13 6.79347 12.9434 6.66207 12.8435 6.56762L8 1.98828Z" fill="#0078D4" />
</svg>

After

Width:  |  Height:  |  Size: 968 B

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,38 +2256,49 @@ 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;
} }
.monaco-editor .quick-input-list-label {
/* Restore some of Monaco's default styles that are clobbered by our global styles */
padding: 0;
line-height: 22px;
}
.monaco-editor .quick-input-list .highlight {
/* Padding in highlighted text within the quick input list breaks the flow of the text */
padding: 0;
}
td a { td a {
color: #393939; color: #393939;
} }
@@ -2305,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;
@@ -2355,10 +2352,9 @@ a:link {
.tabsManagerContainer { .tabsManagerContainer {
height: 100%; height: 100%;
flex-grow: 1; display: grid;
overflow: hidden; grid-template-rows: 36px 36px 1fr;
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 {
@@ -2547,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 {
@@ -2612,6 +2610,7 @@ a:link {
} }
.tabPanesContainer { .tabPanesContainer {
grid-row: span 2; // Fill the remaining space
display: flex; display: flex;
height: 100%; height: 100%;
overflow: hidden; overflow: hidden;
@@ -2647,7 +2646,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);
} }
@@ -2683,67 +2682,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
@@ -124,7 +130,37 @@ export enum MongoBackendEndpointType {
remote, remote,
} }
// TODO: 435619 Add default endpoints per cloud and use regional only when available export class BackendApi {
public static readonly GenerateToken: string = "GenerateToken";
public static readonly PortalSettings: string = "PortalSettings";
public static readonly AccountRestrictions: string = "AccountRestrictions";
}
export class PortalBackendEndpoints {
public static readonly Development: string = "https://localhost:7235";
public static readonly Mpac: string = "https://cdb-ms-mpac-pbe.cosmos.azure.com";
public static readonly Prod: string = "https://cdb-ms-prod-pbe.cosmos.azure.com";
public static readonly Fairfax: string = "https://cdb-ff-prod-pbe.cosmos.azure.us";
public static readonly Mooncake: string = "https://cdb-mc-prod-pbe.cosmos.azure.cn";
}
export class MongoProxyEndpoints {
public static readonly Local: string = "https://localhost:7238";
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 Fairfax: string = "https://cdb-ff-prod-mp.cosmos.azure.us";
public static readonly Mooncake: string = "https://cdb-mc-prod-mp.cosmos.azure.cn";
}
export class CassandraProxyEndpoints {
public static readonly Development: string = "https://localhost:7240";
public static readonly Mpac: string = "https://cdb-ms-mpac-cp.cosmos.azure.com";
public static readonly Prod: string = "https://cdb-ms-prod-cp.cosmos.azure.com";
public static readonly Fairfax: string = "https://cdb-ff-prod-cp.cosmos.azure.us";
public static readonly Mooncake: string = "https://cdb-mc-prod-cp.cosmos.azure.cn";
}
//TODO: Remove this when new backend is migrated over
export class CassandraBackend { export class CassandraBackend {
public static readonly createOrDeleteApi: string = "api/cassandra/createordelete"; public static readonly createOrDeleteApi: string = "api/cassandra/createordelete";
public static readonly guestCreateOrDeleteApi: string = "api/guest/cassandra/createordelete"; public static readonly guestCreateOrDeleteApi: string = "api/guest/cassandra/createordelete";
@@ -136,6 +172,17 @@ export class CassandraBackend {
public static readonly guestSchemaApi: string = "api/guest/cassandra/schema"; public static readonly guestSchemaApi: string = "api/guest/cassandra/schema";
} }
export class CassandraProxyAPIs {
public static readonly createOrDeleteApi: string = "api/cassandra/createordelete";
public static readonly connectionStringCreateOrDeleteApi: string = "api/connectionstring/cassandra/createordelete";
public static readonly queryApi: string = "api/cassandra";
public static readonly connectionStringQueryApi: string = "api/connectionstring/cassandra";
public static readonly keysApi: string = "api/cassandra/keys";
public static readonly connectionStringKeysApi: string = "api/connectionstring/cassandra/keys";
public static readonly schemaApi: string = "api/cassandra/schema";
public static readonly connectionStringSchemaApi: string = "api/connectionstring/cassandra/schema";
}
export class Queries { export class Queries {
public static CustomPageOption: string = "custom"; public static CustomPageOption: string = "custom";
public static UnlimitedPageOption: string = "unlimited"; public static UnlimitedPageOption: string = "unlimited";
@@ -150,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";
@@ -209,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 {
@@ -435,22 +489,6 @@ export class JunoEndpoints {
public static readonly Stage = "https://tools-staging.cosmos.azure.com"; public static readonly Stage = "https://tools-staging.cosmos.azure.com";
} }
export class MongoProxyEndpoints {
public static readonly Development: string = "https://localhost:7238";
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 Fairfax: string = "https://cdb-ff-prod-mp.cosmos.azure.us";
public static readonly Mooncake: string = "https://cdb-mc-prod-mp.cosmos.azure.cn";
}
export class CassandraProxyEndpoints {
public static readonly Development: string = "https://localhost:7240";
public static readonly Mpac: string = "https://cdb-ms-mpac-cp.cosmos.azure.com";
public static readonly Prod: string = "https://cdb-ms-prod-cp.cosmos.azure.com";
public static readonly Fairfax: string = "https://cdb-ff-prod-cp.cosmos.azure.us";
public static readonly Mooncake: string = "https://cdb-mc-prod-cp.cosmos.azure.cn";
}
export class PriorityLevel { export class PriorityLevel {
public static readonly High = "high"; public static readonly High = "high";
public static readonly Low = "low"; public static readonly Low = "low";

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,7 +1,6 @@
import * as Cosmos from "@azure/cosmos"; import * as Cosmos from "@azure/cosmos";
import { sendCachedDataMessage } from "Common/MessageHandler";
import { getAuthorizationTokenUsingResourceTokens } from "Common/getAuthorizationTokenUsingResourceTokens"; import { getAuthorizationTokenUsingResourceTokens } from "Common/getAuthorizationTokenUsingResourceTokens";
import { AuthorizationToken, MessageTypes } 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";
@@ -12,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;
@@ -51,21 +62,36 @@ export const tokenProvider = async (requestInfo: Cosmos.RequestInfo) => {
case Cosmos.ResourceType.offer: case Cosmos.ResourceType.offer:
case Cosmos.ResourceType.user: case Cosmos.ResourceType.user:
case Cosmos.ResourceType.permission: case Cosmos.ResourceType.permission:
// User master tokens // For now, these operations aren't used, so fetching the authorization token is commented out.
const authorizationToken = await sendCachedDataMessage<AuthorizationToken>( // This provider must return a real token to pass validation by the client, so we return the cached resource token
MessageTypes.GetAuthorizationToken, // (which is a valid token, but won't work for these operations).
[requestInfo], const resourceTokens2 = userContext.fabricContext.databaseConnectionInfo.resourceTokens;
userContext.fabricContext.connectionId, return getAuthorizationTokenUsingResourceTokens(resourceTokens2, requestInfo.path, requestInfo.resourceId);
);
console.log("Response from Fabric: ", authorizationToken); /* ************** TODO: Uncomment this code if we need to support these operations **************
headers[HttpHeaders.msDate] = authorizationToken.XDate; // User master tokens
return decodeURIComponent(authorizationToken.PrimaryReadWriteToken); const authorizationToken = await sendCachedDataMessage<AuthorizationToken>(
FabricMessageTypes.GetAuthorizationToken,
[requestInfo],
userContext.fabricContext.connectionId,
);
console.log("Response from Fabric: ", authorizationToken);
headers[HttpHeaders.msDate] = authorizationToken.XDate;
return decodeURIComponent(authorizationToken.PrimaryReadWriteToken);
***********************************************************************************************/
} }
} }
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);
} }
@@ -130,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;
@@ -150,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,
@@ -68,6 +70,7 @@ export function sendMessage(data: any): void {
} }
export function sendReadyMessage(): void { export function sendReadyMessage(): void {
console.log("SENDING READY MESSAGE");
_sendMessage({ _sendMessage({
signature: "pcIframe", signature: "pcIframe",
kind: "ready", kind: "ready",
@@ -95,10 +98,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

@@ -67,7 +67,7 @@ export function queryDocuments(
query: string, query: string,
continuationToken?: string, continuationToken?: string,
): Promise<QueryResponse> { ): Promise<QueryResponse> {
if (!useMongoProxyEndpoint("resourcelist")) { if (!useMongoProxyEndpoint("resourcelist") || !useMongoProxyEndpoint("queryDocuments")) {
return queryDocuments_ToBeDeprecated(databaseId, collection, isResourceList, query, continuationToken); return queryDocuments_ToBeDeprecated(databaseId, collection, isResourceList, query, continuationToken);
} }
@@ -106,7 +106,7 @@ export function queryDocuments(
headers[CosmosSDKConstants.HttpHeaders.Continuation] = continuationToken; headers[CosmosSDKConstants.HttpHeaders.Continuation] = continuationToken;
} }
const path = isResourceList ? "/resourcelist" : ""; const path = isResourceList ? "/resourcelist" : "/queryDocuments";
return window return window
.fetch(`${endpoint}${path}`, { .fetch(`${endpoint}${path}`, {
@@ -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,16 +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 {
let canAccessMongoProxy: boolean = userContext.databaseAccount.properties.publicNetworkAccess === "Enabled";
if (userContext.databaseAccount.properties.ipRules?.length > 0) {
canAccessMongoProxy = canAccessMongoProxy && configContext.MONGO_PROXY_OUTBOUND_IPS_ALLOWLISTED;
}
return (
canAccessMongoProxy &&
configContext.NEW_MONGO_APIS?.includes(api) &&
[MongoProxyEndpoints.Development, MongoProxyEndpoints.Mpac].includes(configContext.MONGO_PROXY_ENDPOINT)
);
}

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

@@ -122,14 +122,21 @@ const pollDataTransferJobOperation = async (
updateDataTransferJob(body); updateDataTransferJob(body);
if (status === "Cancelled" || status === "Failed" || status === "Faulted") { if (status === "Cancelled") {
removeFromPolling(jobName);
clearMessage && clearMessage();
const cancelMessage = `Data transfer job ${jobName} cancelled`;
NotificationConsoleUtils.logConsoleError(cancelMessage);
throw new AbortError(cancelMessage);
}
if (status === "Failed" || status === "Faulted") {
removeFromPolling(jobName); removeFromPolling(jobName);
const errorMessage = body?.properties?.error const errorMessage = body?.properties?.error
? JSON.stringify(body?.properties?.error) ? JSON.stringify(body?.properties?.error)
: "Operation could not be completed"; : "Operation could not be completed";
const error = new Error(errorMessage); const error = new Error(errorMessage);
clearMessage && clearMessage(); clearMessage && clearMessage();
NotificationConsoleUtils.logConsoleError(`Data transfer job ${jobName} Failed`); NotificationConsoleUtils.logConsoleError(`Data transfer job ${jobName} failed: ${errorMessage}`);
throw new AbortError(error); throw new AbortError(error);
} }
if (status === "Completed") { if (status === "Completed") {

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

@@ -1,4 +1,10 @@
import { CassandraProxyEndpoints, JunoEndpoints, MongoProxyEndpoints } from "Common/Constants"; import {
BackendApi,
CassandraProxyEndpoints,
JunoEndpoints,
MongoProxyEndpoints,
PortalBackendEndpoints,
} from "Common/Constants";
import { import {
allowedAadEndpoints, allowedAadEndpoints,
allowedArcadiaEndpoints, allowedArcadiaEndpoints,
@@ -36,14 +42,22 @@ 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;
PORTAL_BACKEND_ENDPOINT?: string;
NEW_BACKEND_APIS?: BackendApi[];
MONGO_BACKEND_ENDPOINT?: string; MONGO_BACKEND_ENDPOINT?: string;
MONGO_PROXY_ENDPOINT?: string; MONGO_PROXY_ENDPOINT?: string;
MONGO_PROXY_OUTBOUND_IPS_ALLOWLISTED?: boolean; MONGO_PROXY_OUTBOUND_IPS_ALLOWLISTED?: boolean;
NEW_MONGO_APIS?: string[]; NEW_MONGO_APIS?: string[];
CASSANDRA_PROXY_ENDPOINT?: string; CASSANDRA_PROXY_ENDPOINT?: string;
CASSANDRA_PROXY_OUTBOUND_IPS_ALLOWLISTED: boolean;
NEW_CASSANDRA_APIS?: string[];
PROXY_PATH?: string; PROXY_PATH?: string;
JUNO_ENDPOINT: string; JUNO_ENDPOINT: string;
GITHUB_CLIENT_ID: string; GITHUB_CLIENT_ID: string;
@@ -73,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/",
@@ -82,23 +97,31 @@ 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
GITHUB_TEST_ENV_CLIENT_ID: "b63fc8cbf87fd3c6e2eb", // Registered OAuth app: https://github.com/organizations/AzureCosmosDBNotebooks/settings/applications/1777772 GITHUB_TEST_ENV_CLIENT_ID: "b63fc8cbf87fd3c6e2eb", // Registered OAuth app: https://github.com/organizations/AzureCosmosDBNotebooks/settings/applications/1777772
JUNO_ENDPOINT: JunoEndpoints.Prod, JUNO_ENDPOINT: JunoEndpoints.Prod,
BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com", BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com",
PORTAL_BACKEND_ENDPOINT: PortalBackendEndpoints.Prod,
MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Prod, MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Prod,
NEW_MONGO_APIS: [ NEW_MONGO_APIS: [
// "resourcelist", // "resourcelist",
// "queryDocuments",
// "createDocument", // "createDocument",
// "readDocument", // "readDocument",
// "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,
NEW_CASSANDRA_APIS: ["postQuery", "createOrDelete", "getKeys", "getSchema"],
CASSANDRA_PROXY_OUTBOUND_IPS_ALLOWLISTED: false,
isTerminalEnabled: false, isTerminalEnabled: false,
isPhoenixEnabled: false, isPhoenixEnabled: false,
}; };
@@ -176,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,8 +158,10 @@ 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;
} }
export interface CollectionsWithPagination { export interface CollectionsWithPagination {
@@ -193,10 +196,23 @@ 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 {
name: string;
query: string;
}
export type ComputedProperties = ComputedProperty[];
export interface PartitionKey { export interface PartitionKey {
paths: string[]; paths: string[];
kind: "Hash" | "Range" | "MultiHash"; kind: "Hash" | "Range" | "MultiHash";
@@ -325,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";
@@ -53,6 +53,7 @@ export type FabricMessageV2 =
id: string; id: string;
message: { message: {
connectionId: string; connectionId: string;
isVisible: boolean;
}; };
} }
| { | {
@@ -72,7 +73,7 @@ export type FabricMessageV2 =
}; };
} }
| { | {
type: "setToolbarStatus"; type: "explorerVisible";
message: { message: {
visible: boolean; visible: boolean;
}; };

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,13 +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,
export interface AuthorizationToken {
XDate: string;
PrimaryReadWriteToken: string;
} }

View File

@@ -135,6 +135,7 @@ export interface Collection extends CollectionBase {
changeFeedPolicy: ko.Observable<DataModels.ChangeFeedPolicy>; changeFeedPolicy: ko.Observable<DataModels.ChangeFeedPolicy>;
geospatialConfig: ko.Observable<DataModels.GeospatialConfig>; geospatialConfig: ko.Observable<DataModels.GeospatialConfig>;
documentIds: ko.ObservableArray<DocumentId>; documentIds: ko.ObservableArray<DocumentId>;
computedProperties: ko.Observable<DataModels.ComputedProperties>;
cassandraKeys: CassandraTableKeys; cassandraKeys: CassandraTableKeys;
cassandraSchema: CassandraTableKey[]; cassandraSchema: CassandraTableKey[];
@@ -175,6 +176,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;
@@ -323,9 +329,9 @@ export enum DocumentExplorerState {
noDocumentSelected, noDocumentSelected,
newDocumentValid, newDocumentValid,
newDocumentInvalid, newDocumentInvalid,
exisitingDocumentNoEdits, existingDocumentNoEdits,
exisitingDocumentDirtyValid, existingDocumentDirtyValid,
exisitingDocumentDirtyInvalid, existingDocumentDirtyInvalid,
} }
export enum IndexingPolicyEditorState { export enum IndexingPolicyEditorState {
@@ -338,9 +344,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 {
@@ -386,6 +392,7 @@ export interface DataExplorerInputsFrame {
dnsSuffix?: string; dnsSuffix?: string;
serverId?: string; serverId?: string;
extensionEndpoint?: string; extensionEndpoint?: string;
portalBackendEndpoint?: string;
mongoProxyEndpoint?: string; mongoProxyEndpoint?: string;
cassandraProxyEndpoint?: string; cassandraProxyEndpoint?: string;
subscriptionType?: SubscriptionType; subscriptionType?: SubscriptionType;
@@ -407,6 +414,7 @@ export interface DataExplorerInputsFrame {
features?: { features?: {
[key: string]: string; [key: string]: string;
}; };
feedbackPolicies?: any;
} }
export interface SelfServeFrameInputs { export interface SelfServeFrameInputs {
@@ -417,6 +425,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 = {
@@ -46,9 +55,25 @@ export class EditorReact extends React.Component<EditorReactProps, EditorReactSt
}, 100); }, 100);
} }
public componentDidUpdate(previous: EditorReactProps) { public componentDidUpdate() {
if (this.props.content !== previous.content) { if (!this.editor) {
this.editor?.setValue(this.props.content); return;
}
const existingContent = this.editor.getModel().getValue();
if (this.props.content !== existingContent) {
if (this.props.isReadOnly) {
this.editor.setValue(this.props.content);
} else {
this.editor.pushUndoStop();
this.editor.executeEdits("", [
{
range: this.editor.getModel().getFullModelRange(),
text: this.props.content,
},
]);
}
} }
} }
@@ -59,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)}
/> />
@@ -71,9 +98,14 @@ export class EditorReact extends React.Component<EditorReactProps, EditorReactSt
protected configureEditor(editor: monaco.editor.IStandaloneCodeEditor) { protected configureEditor(editor: monaco.editor.IStandaloneCodeEditor) {
this.editor = editor; this.editor = editor;
const queryEditorModel = this.editor.getModel();
if (!this.props.isReadOnly && this.props.onContentChanged) { if (!this.props.isReadOnly && this.props.onContentChanged) {
queryEditorModel.onDidChangeContent(() => { // Hooking the model's onDidChangeContent event because of some event ordering issues.
// If a single user input causes BOTH the editor content to change AND the cursor selection to change (which is likely),
// then there are some inconsistencies as to which event fires first.
// But the editor.onDidChangeModelContent event seems to always fire before the cursor selection event.
// (This is NOT true for the model's onDidChangeContent event, which sometimes fires after the cursor selection event.)
// If the cursor selection event fires first, then the calling component may re-render the component with old content, so we want to ensure the model content changed event always fires first.
this.editor.onDidChangeModelContent(() => {
const queryEditorModel = this.editor.getModel(); const queryEditorModel = this.editor.getModel();
this.props.onContentChanged(queryEditorModel.getValue()); this.props.onContentChanged(queryEditorModel.getValue());
}); });
@@ -87,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);
},
});
}
} }
/** /**
@@ -98,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",
@@ -110,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

@@ -1,5 +1,14 @@
import { IPivotItemProps, IPivotProps, Pivot, PivotItem } from "@fluentui/react"; import { IPivotItemProps, IPivotProps, Pivot, PivotItem } from "@fluentui/react";
import {
ComputedPropertiesComponent,
ComputedPropertiesComponentProps,
} 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";
@@ -108,6 +117,11 @@ export interface SettingsComponentState {
indexesToAdd: AddMongoIndexProps[]; indexesToAdd: AddMongoIndexProps[];
indexTransformationProgress: number; indexTransformationProgress: number;
computedPropertiesContent: DataModels.ComputedProperties;
computedPropertiesContentBaseline: DataModels.ComputedProperties;
shouldDiscardComputedProperties: boolean;
isComputedPropertiesDirty: boolean;
conflictResolutionPolicyMode: DataModels.ConflictResolutionMode; conflictResolutionPolicyMode: DataModels.ConflictResolutionMode;
conflictResolutionPolicyModeBaseline: DataModels.ConflictResolutionMode; conflictResolutionPolicyModeBaseline: DataModels.ConflictResolutionMode;
conflictResolutionPolicyPath: string; conflictResolutionPolicyPath: string;
@@ -132,8 +146,10 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
private offer: DataModels.Offer; private offer: DataModels.Offer;
private changeFeedPolicyVisible: boolean; private changeFeedPolicyVisible: boolean;
private isFixedContainer: boolean; private isFixedContainer: 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;
@@ -145,8 +161,10 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
this.collection = this.props.settingsTab.collection as ViewModels.Collection; this.collection = this.props.settingsTab.collection as ViewModels.Collection;
this.offer = this.collection?.offer(); this.offer = this.collection?.offer();
this.isAnalyticalStorageEnabled = !!this.collection?.analyticalStorageTtl(); this.isAnalyticalStorageEnabled = !!this.collection?.analyticalStorageTtl();
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;
@@ -198,6 +216,11 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
isMongoIndexingPolicyDiscardable: false, isMongoIndexingPolicyDiscardable: false,
indexTransformationProgress: undefined, indexTransformationProgress: undefined,
computedPropertiesContent: undefined,
computedPropertiesContentBaseline: undefined,
shouldDiscardComputedProperties: false,
isComputedPropertiesDirty: false,
conflictResolutionPolicyMode: undefined, conflictResolutionPolicyMode: undefined,
conflictResolutionPolicyModeBaseline: undefined, conflictResolutionPolicyModeBaseline: undefined,
conflictResolutionPolicyPath: undefined, conflictResolutionPolicyPath: undefined,
@@ -288,6 +311,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
this.state.isSubSettingsSaveable || this.state.isSubSettingsSaveable ||
this.state.isIndexingPolicyDirty || this.state.isIndexingPolicyDirty ||
this.state.isConflictResolutionDirty || this.state.isConflictResolutionDirty ||
this.state.isComputedPropertiesDirty ||
(!!this.state.currentMongoIndexes && this.state.isMongoIndexingPolicySaveable) (!!this.state.currentMongoIndexes && this.state.isMongoIndexingPolicySaveable)
); );
}; };
@@ -298,6 +322,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
this.state.isSubSettingsDiscardable || this.state.isSubSettingsDiscardable ||
this.state.isIndexingPolicyDirty || this.state.isIndexingPolicyDirty ||
this.state.isConflictResolutionDirty || this.state.isConflictResolutionDirty ||
this.state.isComputedPropertiesDirty ||
(!!this.state.currentMongoIndexes && this.state.isMongoIndexingPolicyDiscardable) (!!this.state.currentMongoIndexes && this.state.isMongoIndexingPolicyDiscardable)
); );
}; };
@@ -402,6 +427,9 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
isMongoIndexingPolicySaveable: false, isMongoIndexingPolicySaveable: false,
isMongoIndexingPolicyDiscardable: false, isMongoIndexingPolicyDiscardable: false,
isConflictResolutionDirty: false, isConflictResolutionDirty: false,
computedPropertiesContent: this.state.computedPropertiesContentBaseline,
shouldDiscardComputedProperties: true,
isComputedPropertiesDirty: false,
}); });
}; };
@@ -521,6 +549,31 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
private onMongoIndexingPolicyDiscardableChange = (isMongoIndexingPolicyDiscardable: boolean): void => private onMongoIndexingPolicyDiscardableChange = (isMongoIndexingPolicyDiscardable: boolean): void =>
this.setState({ isMongoIndexingPolicyDiscardable }); this.setState({ isMongoIndexingPolicyDiscardable });
private onComputedPropertiesContentChange = (newComputedProperties: DataModels.ComputedProperties): void =>
this.setState({ computedPropertiesContent: newComputedProperties });
private resetShouldDiscardComputedProperties = (): void => this.setState({ shouldDiscardComputedProperties: false });
private logComputedPropertiesSuccessMessage = (): void => {
if (this.props.settingsTab.onLoadStartKey) {
traceSuccess(
Action.Tab,
{
databaseName: this.collection.databaseId,
collectionName: this.collection.id(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.props.settingsTab.tabTitle(),
},
this.props.settingsTab.onLoadStartKey,
);
this.props.settingsTab.onLoadStartKey = undefined;
}
};
private onComputedPropertiesDirtyChange = (isComputedPropertiesDirty: boolean): void =>
this.setState({ isComputedPropertiesDirty: isComputedPropertiesDirty });
private calculateTotalThroughputUsed = (): void => { private calculateTotalThroughputUsed = (): void => {
this.totalThroughputUsed = 0; this.totalThroughputUsed = 0;
(useDatabases.getState().databases || []).forEach(async (database) => { (useDatabases.getState().databases || []).forEach(async (database) => {
@@ -643,7 +696,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
const indexingPolicyContent = this.collection.indexingPolicy(); const indexingPolicyContent = this.collection.indexingPolicy();
const conflictResolutionPolicy: DataModels.ConflictResolutionPolicy = const conflictResolutionPolicy: DataModels.ConflictResolutionPolicy =
this.collection.conflictResolutionPolicy && this.collection.conflictResolutionPolicy(); this.collection.conflictResolutionPolicy && this.collection.conflictResolutionPolicy();
const conflictResolutionPolicyMode = parseConflictResolutionMode(conflictResolutionPolicy?.mode); const conflictResolutionPolicyMode = parseConflictResolutionMode(conflictResolutionPolicy?.mode);
const conflictResolutionPolicyPath = conflictResolutionPolicy?.conflictResolutionPath; const conflictResolutionPolicyPath = conflictResolutionPolicy?.conflictResolutionPath;
const conflictResolutionPolicyProcedure = parseConflictResolutionProcedure( const conflictResolutionPolicyProcedure = parseConflictResolutionProcedure(
@@ -652,6 +704,12 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
const geospatialConfigTypeString: string = const geospatialConfigTypeString: string =
(this.collection.geospatialConfig && this.collection.geospatialConfig()?.type) || GeospatialConfigType.Geometry; (this.collection.geospatialConfig && this.collection.geospatialConfig()?.type) || GeospatialConfigType.Geometry;
const geoSpatialConfigType = GeospatialConfigType[geospatialConfigTypeString as keyof typeof GeospatialConfigType]; const geoSpatialConfigType = GeospatialConfigType[geospatialConfigTypeString as keyof typeof GeospatialConfigType];
let computedPropertiesContent = this.collection.computedProperties();
if (!computedPropertiesContent || computedPropertiesContent.length === 0) {
computedPropertiesContent = [
{ name: "name_of_property", query: "query_to_compute_property" },
] as DataModels.ComputedProperties;
}
return { return {
throughput: offerThroughput, throughput: offerThroughput,
@@ -678,6 +736,8 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
conflictResolutionPolicyProcedureBaseline: conflictResolutionPolicyProcedure, conflictResolutionPolicyProcedureBaseline: conflictResolutionPolicyProcedure,
geospatialConfigType: geoSpatialConfigType, geospatialConfigType: geoSpatialConfigType,
geospatialConfigTypeBaseline: geoSpatialConfigType, geospatialConfigTypeBaseline: geoSpatialConfigType,
computedPropertiesContent: computedPropertiesContent,
computedPropertiesContentBaseline: computedPropertiesContent,
}; };
}; };
@@ -794,7 +854,12 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
private saveCollectionSettings = async (startKey: number): Promise<void> => { private saveCollectionSettings = async (startKey: number): Promise<void> => {
const newCollection: DataModels.Collection = { ...this.collection.rawDataModel }; const newCollection: DataModels.Collection = { ...this.collection.rawDataModel };
if (this.state.isSubSettingsSaveable || this.state.isIndexingPolicyDirty || this.state.isConflictResolutionDirty) { if (
this.state.isSubSettingsSaveable ||
this.state.isIndexingPolicyDirty ||
this.state.isConflictResolutionDirty ||
this.state.isComputedPropertiesDirty
) {
let defaultTtl: number; let defaultTtl: number;
switch (this.state.timeToLive) { switch (this.state.timeToLive) {
case TtlType.On: case TtlType.On:
@@ -832,6 +897,10 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
newCollection.conflictResolutionPolicy = conflictResolutionChanges; newCollection.conflictResolutionPolicy = conflictResolutionChanges;
} }
if (this.state.isComputedPropertiesDirty) {
newCollection.computedProperties = this.state.computedPropertiesContent;
}
const updatedCollection: DataModels.Collection = await updateCollection( const updatedCollection: DataModels.Collection = await updateCollection(
this.collection.databaseId, this.collection.databaseId,
this.collection.id(), this.collection.id(),
@@ -845,6 +914,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
this.collection.conflictResolutionPolicy(updatedCollection.conflictResolutionPolicy); this.collection.conflictResolutionPolicy(updatedCollection.conflictResolutionPolicy);
this.collection.changeFeedPolicy(updatedCollection.changeFeedPolicy); this.collection.changeFeedPolicy(updatedCollection.changeFeedPolicy);
this.collection.geospatialConfig(updatedCollection.geospatialConfig); this.collection.geospatialConfig(updatedCollection.geospatialConfig);
this.collection.computedProperties(updatedCollection.computedProperties);
if (wasIndexingPolicyModified) { if (wasIndexingPolicyModified) {
await this.refreshIndexTransformationProgress(); await this.refreshIndexTransformationProgress();
@@ -855,6 +925,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
isSubSettingsDiscardable: false, isSubSettingsDiscardable: false,
isIndexingPolicyDirty: false, isIndexingPolicyDirty: false,
isConflictResolutionDirty: false, isConflictResolutionDirty: false,
isComputedPropertiesDirty: false,
}); });
} }
@@ -1033,6 +1104,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 = {
@@ -1049,6 +1121,16 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
onMongoIndexingPolicyDiscardableChange: this.onMongoIndexingPolicyDiscardableChange, onMongoIndexingPolicyDiscardableChange: this.onMongoIndexingPolicyDiscardableChange,
}; };
const computedPropertiesComponentProps: ComputedPropertiesComponentProps = {
computedPropertiesContent: this.state.computedPropertiesContent,
computedPropertiesContentBaseline: this.state.computedPropertiesContentBaseline,
logComputedPropertiesSuccessMessage: this.logComputedPropertiesSuccessMessage,
onComputedPropertiesContentChange: this.onComputedPropertiesContentChange,
onComputedPropertiesDirtyChange: this.onComputedPropertiesDirtyChange,
resetShouldDiscardComputedProperties: this.resetShouldDiscardComputedProperties,
shouldDiscardComputedProperties: this.state.shouldDiscardComputedProperties,
};
const conflictResolutionPolicyComponentProps: ConflictResolutionComponentProps = { const conflictResolutionPolicyComponentProps: ConflictResolutionComponentProps = {
collection: this.collection, collection: this.collection,
conflictResolutionPolicyMode: this.state.conflictResolutionPolicyMode, conflictResolutionPolicyMode: this.state.conflictResolutionPolicyMode,
@@ -1069,6 +1151,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({
@@ -1082,6 +1168,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,
@@ -1111,6 +1204,13 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
}); });
} }
if (this.shouldShowComputedPropertiesEditor) {
tabs.push({
tab: SettingsV2TabTypes.ComputedPropertiesTab,
content: <ComputedPropertiesComponent {...computedPropertiesComponentProps} />,
});
}
const pivotProps: IPivotProps = { const pivotProps: IPivotProps = {
onLinkClick: this.onPivotChange, onLinkClick: this.onPivotChange,
selectedKey: SettingsV2TabTypes[this.state.selectedTab], selectedKey: SettingsV2TabTypes[this.state.selectedTab],

View File

@@ -11,7 +11,6 @@ import {
getThroughputApplyLongDelayMessage, getThroughputApplyLongDelayMessage,
getThroughputApplyShortDelayMessage, getThroughputApplyShortDelayMessage,
getToolTipContainer, getToolTipContainer,
indexingPolicynUnsavedWarningMessage,
manualToAutoscaleDisclaimerElement, manualToAutoscaleDisclaimerElement,
mongoIndexTransformationRefreshingMessage, mongoIndexTransformationRefreshingMessage,
mongoIndexingPolicyAADError, mongoIndexingPolicyAADError,
@@ -39,7 +38,6 @@ class SettingsRenderUtilsTestComponent extends React.Component {
{manualToAutoscaleDisclaimerElement} {manualToAutoscaleDisclaimerElement}
{ttlWarning} {ttlWarning}
{indexingPolicynUnsavedWarningMessage}
{updateThroughputDelayedApplyWarningMessage} {updateThroughputDelayedApplyWarningMessage}
{getThroughputApplyDelayedMessage(false, 1000, "RU/s", "sampleDb", "sampleCollection", 2000)} {getThroughputApplyDelayedMessage(false, 1000, "RU/s", "sampleDb", "sampleCollection", 2000)}

View File

@@ -61,6 +61,8 @@ export interface PriceBreakdown {
currencySign: string; currencySign: string;
} }
export type editorType = "indexPolicy" | "computedProperties";
export const infoAndToolTipTextStyle: ITextStyles = { root: { fontSize: 14, color: "windowtext" } }; export const infoAndToolTipTextStyle: ITextStyles = { root: { fontSize: 14, color: "windowtext" } };
export const noLeftPaddingCheckBoxStyle: ICheckboxStyles = { export const noLeftPaddingCheckBoxStyle: ICheckboxStyles = {
@@ -254,9 +256,10 @@ export const ttlWarning: JSX.Element = (
</Text> </Text>
); );
export const indexingPolicynUnsavedWarningMessage: JSX.Element = ( export const unsavedEditorWarningMessage = (editor: editorType): JSX.Element => (
<Text styles={infoAndToolTipTextStyle}> <Text styles={infoAndToolTipTextStyle}>
You have not saved the latest changes made to your indexing policy. Please click save to confirm the changes. You have not saved the latest changes made to your{" "}
{editor === "indexPolicy" ? "indexing policy" : "computed properties"}. Please click save to confirm the changes.
</Text> </Text>
); );

View File

@@ -0,0 +1,56 @@
import * as DataModels from "Contracts/DataModels";
import { shallow } from "enzyme";
import React from "react";
import { ComputedPropertiesComponent, ComputedPropertiesComponentProps } from "./ComputedPropertiesComponent";
describe("ComputedPropertiesComponent", () => {
const initialComputedPropertiesContent: DataModels.ComputedProperties = [
{
name: "prop1",
query: "query1",
},
];
const baseProps: ComputedPropertiesComponentProps = {
computedPropertiesContent: initialComputedPropertiesContent,
computedPropertiesContentBaseline: initialComputedPropertiesContent,
logComputedPropertiesSuccessMessage: () => {
return;
},
onComputedPropertiesContentChange: () => {
return;
},
onComputedPropertiesDirtyChange: () => {
return;
},
resetShouldDiscardComputedProperties: () => {
return;
},
shouldDiscardComputedProperties: false,
};
it("renders", () => {
const wrapper = shallow(<ComputedPropertiesComponent {...baseProps} />);
expect(wrapper).toMatchSnapshot();
});
it("computed properties are reset", () => {
const wrapper = shallow(<ComputedPropertiesComponent {...baseProps} />);
const computedPropertiesComponentInstance = wrapper.instance() as ComputedPropertiesComponent;
const resetComputedPropertiesEditorMockFn = jest.fn();
computedPropertiesComponentInstance.resetComputedPropertiesEditor = resetComputedPropertiesEditorMockFn;
wrapper.setProps({ shouldDiscardComputedProperties: true });
wrapper.update();
expect(resetComputedPropertiesEditorMockFn.mock.calls.length).toEqual(1);
});
it("dirty is set", () => {
let computedPropertiesComponent = new ComputedPropertiesComponent(baseProps);
expect(computedPropertiesComponent.IsComponentDirty()).toEqual(false);
const newProps = { ...baseProps, computedPropertiesContent: undefined as DataModels.ComputedProperties };
computedPropertiesComponent = new ComputedPropertiesComponent(newProps);
expect(computedPropertiesComponent.IsComponentDirty()).toEqual(true);
});
});

View File

@@ -0,0 +1,128 @@
import { FontIcon, Link, MessageBar, MessageBarType, Stack, Text } from "@fluentui/react";
import * as DataModels from "Contracts/DataModels";
import { titleAndInputStackProps, unsavedEditorWarningMessage } from "Explorer/Controls/Settings/SettingsRenderUtils";
import { isDirty } from "Explorer/Controls/Settings/SettingsUtils";
import { loadMonaco } from "Explorer/LazyMonaco";
import * as monaco from "monaco-editor";
import * as React from "react";
export interface ComputedPropertiesComponentProps {
computedPropertiesContent: DataModels.ComputedProperties;
computedPropertiesContentBaseline: DataModels.ComputedProperties;
logComputedPropertiesSuccessMessage: () => void;
onComputedPropertiesContentChange: (newComputedProperties: DataModels.ComputedProperties) => void;
onComputedPropertiesDirtyChange: (isComputedPropertiesDirty: boolean) => void;
resetShouldDiscardComputedProperties: () => void;
shouldDiscardComputedProperties: boolean;
}
interface ComputedPropertiesComponentState {
computedPropertiesContentIsValid: boolean;
}
export class ComputedPropertiesComponent extends React.Component<
ComputedPropertiesComponentProps,
ComputedPropertiesComponentState
> {
private shouldCheckComponentIsDirty = true;
private computedPropertiesDiv = React.createRef<HTMLDivElement>();
private computedPropertiesEditor: monaco.editor.IStandaloneCodeEditor;
constructor(props: ComputedPropertiesComponentProps) {
super(props);
this.state = {
computedPropertiesContentIsValid: true,
};
}
componentDidUpdate(): void {
if (this.props.shouldDiscardComputedProperties) {
this.resetComputedPropertiesEditor();
this.props.resetShouldDiscardComputedProperties();
}
this.onComponentUpdate();
}
componentDidMount(): void {
this.resetComputedPropertiesEditor();
this.onComponentUpdate();
}
public resetComputedPropertiesEditor = (): void => {
if (!this.computedPropertiesEditor) {
this.createComputedPropertiesEditor();
} else {
const indexingPolicyEditorModel = this.computedPropertiesEditor.getModel();
const value: string = JSON.stringify(this.props.computedPropertiesContent, undefined, 4);
indexingPolicyEditorModel.setValue(value);
}
this.onComponentUpdate();
};
private onComponentUpdate = (): void => {
if (!this.shouldCheckComponentIsDirty) {
this.shouldCheckComponentIsDirty = true;
return;
}
this.props.onComputedPropertiesDirtyChange(this.IsComponentDirty());
this.shouldCheckComponentIsDirty = false;
};
public IsComponentDirty = (): boolean => {
if (
isDirty(this.props.computedPropertiesContent, this.props.computedPropertiesContentBaseline) &&
this.state.computedPropertiesContentIsValid
) {
return true;
}
return false;
};
private async createComputedPropertiesEditor(): Promise<void> {
const value: string = JSON.stringify(this.props.computedPropertiesContent, undefined, 4);
const monaco = await loadMonaco();
this.computedPropertiesEditor = monaco.editor.create(this.computedPropertiesDiv.current, {
value: value,
language: "json",
ariaLabel: "Computed properties",
});
if (this.computedPropertiesEditor) {
const computedPropertiesEditorModel = this.computedPropertiesEditor.getModel();
computedPropertiesEditorModel.onDidChangeContent(this.onEditorContentChange.bind(this));
this.props.logComputedPropertiesSuccessMessage();
}
}
private onEditorContentChange = (): void => {
const computedPropertiesEditorModel = this.computedPropertiesEditor.getModel();
try {
const newComputedPropertiesContent = JSON.parse(
computedPropertiesEditorModel.getValue(),
) as DataModels.ComputedProperties;
this.props.onComputedPropertiesContentChange(newComputedPropertiesContent);
this.setState({ computedPropertiesContentIsValid: true });
} catch (e) {
this.setState({ computedPropertiesContentIsValid: false });
}
};
public render(): JSX.Element {
return (
<Stack {...titleAndInputStackProps}>
{isDirty(this.props.computedPropertiesContent, this.props.computedPropertiesContentBaseline) && (
<MessageBar messageBarType={MessageBarType.warning}>
{unsavedEditorWarningMessage("computedProperties")}
</MessageBar>
)}
<Text style={{ marginLeft: "30px", marginBottom: "10px" }}>
<Link target="_blank" href="https://aka.ms/computed-properties-preview/">
{"Learn more"} <FontIcon iconName="NavigateExternalInline" />
</Link>
&#160; about how to define computed properties and how to use them.
</Text>
<div className="settingsV2Editor" tabIndex={0} ref={this.computedPropertiesDiv}></div>
</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

@@ -3,7 +3,7 @@ import * as monaco from "monaco-editor";
import * as React from "react"; import * as React from "react";
import * as DataModels from "../../../../Contracts/DataModels"; import * as DataModels from "../../../../Contracts/DataModels";
import { loadMonaco } from "../../../LazyMonaco"; import { loadMonaco } from "../../../LazyMonaco";
import { indexingPolicynUnsavedWarningMessage, titleAndInputStackProps } from "../SettingsRenderUtils"; import { titleAndInputStackProps, unsavedEditorWarningMessage } from "../SettingsRenderUtils";
import { isDirty, isIndexTransforming } from "../SettingsUtils"; import { isDirty, isIndexTransforming } from "../SettingsUtils";
import { IndexingPolicyRefreshComponent } from "./IndexingPolicyRefresh/IndexingPolicyRefreshComponent"; import { IndexingPolicyRefreshComponent } from "./IndexingPolicyRefresh/IndexingPolicyRefreshComponent";
@@ -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}
/> />
{isDirty(this.props.indexingPolicyContent, this.props.indexingPolicyContentBaseline) && ( {this.props.isVectorSearchEnabled && (
<MessageBar messageBarType={MessageBarType.warning}>{indexingPolicynUnsavedWarningMessage}</MessageBar> <MessageBar messageBarType={MessageBarType.severeWarning}>
Container vector policies and vector indexes are not modifiable after container creation
</MessageBar>
)} )}
<div className="settingsV2IndexingPolicyEditor" tabIndex={0} ref={this.indexingPolicyDiv}></div> {isDirty(this.props.indexingPolicyContent, this.props.indexingPolicyContentBaseline) && (
<MessageBar messageBarType={MessageBarType.warning}>{unsavedEditorWarningMessage("indexPolicy")}</MessageBar>
)}
<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

@@ -19,7 +19,6 @@ import {
addMongoIndexStackProps, addMongoIndexStackProps,
createAndAddMongoIndexStackProps, createAndAddMongoIndexStackProps,
customDetailsListStyles, customDetailsListStyles,
indexingPolicynUnsavedWarningMessage,
infoAndToolTipTextStyle, infoAndToolTipTextStyle,
mediumWidthStackStyles, mediumWidthStackStyles,
mongoCompoundIndexNotSupportedMessage, mongoCompoundIndexNotSupportedMessage,
@@ -27,15 +26,16 @@ import {
onRenderRow, onRenderRow,
separatorStyles, separatorStyles,
subComponentStackProps, subComponentStackProps,
unsavedEditorWarningMessage,
} from "../../SettingsRenderUtils"; } from "../../SettingsRenderUtils";
import { import {
AddMongoIndexProps, AddMongoIndexProps,
getMongoIndexType,
getMongoIndexTypeText,
isIndexTransforming,
MongoIndexIdField, MongoIndexIdField,
MongoIndexTypes, MongoIndexTypes,
MongoNotificationType, MongoNotificationType,
getMongoIndexType,
getMongoIndexTypeText,
isIndexTransforming,
} from "../../SettingsUtils"; } from "../../SettingsUtils";
import { IndexingPolicyRefreshComponent } from "../IndexingPolicyRefresh/IndexingPolicyRefreshComponent"; import { IndexingPolicyRefreshComponent } from "../IndexingPolicyRefresh/IndexingPolicyRefreshComponent";
import { AddMongoIndexComponent } from "./AddMongoIndexComponent"; import { AddMongoIndexComponent } from "./AddMongoIndexComponent";
@@ -297,7 +297,7 @@ export class MongoIndexingPolicyComponent extends React.Component<MongoIndexingP
if (this.getMongoWarningNotificationMessage()) { if (this.getMongoWarningNotificationMessage()) {
warningMessage = this.getMongoWarningNotificationMessage(); warningMessage = this.getMongoWarningNotificationMessage();
} else if (this.isMongoIndexingPolicySaveable()) { } else if (this.isMongoIndexingPolicySaveable()) {
warningMessage = indexingPolicynUnsavedWarningMessage; warningMessage = unsavedEditorWarningMessage("indexPolicy");
} }
return ( return (

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

@@ -136,15 +136,15 @@ export const PartitionKeyComponent: React.FC<PartitionKeyComponentProps> = ({ da
}; };
const getPercentageComplete = () => { const getPercentageComplete = () => {
const jobStatus = portalDataTransferJob?.properties?.status;
const isCompleted = jobStatus === "Completed";
if (isCompleted) {
return 1;
}
const processedCount = portalDataTransferJob?.properties?.processedCount; const processedCount = portalDataTransferJob?.properties?.processedCount;
const totalCount = portalDataTransferJob?.properties?.totalCount; const totalCount = portalDataTransferJob?.properties?.totalCount;
const jobStatus = portalDataTransferJob?.properties?.status; const isJobInProgress = isCurrentJobInProgress(portalDataTransferJob);
const isCancelled = jobStatus === "Cancelled"; return isJobInProgress ? (totalCount > 0 ? processedCount / totalCount : null) : 0;
const isCompleted = jobStatus === "Completed";
if (totalCount <= 0 && !isCompleted) {
return isCancelled ? 0 : null;
}
return isCompleted ? 1 : processedCount / totalCount;
}; };
return ( return (

View File

@@ -47,7 +47,7 @@ describe("ScaleComponent", () => {
expect(wrapper.exists(ThroughputInputAutoPilotV3Component)).toEqual(true); expect(wrapper.exists(ThroughputInputAutoPilotV3Component)).toEqual(true);
expect(wrapper.exists("#throughputApplyLongDelayMessage")).toEqual(true); expect(wrapper.exists("#throughputApplyLongDelayMessage")).toEqual(true);
expect(wrapper.exists("#throughputApplyShortDelayMessage")).toEqual(false); expect(wrapper.exists("#throughputApplyShortDelayMessage")).toEqual(false);
expect(wrapper.find("#throughputApplyLongDelayMessage").html()).toContain(targetThroughput); expect(wrapper.find("#throughputApplyLongDelayMessage").html()).toContain(`${targetThroughput}`);
const newCollection = { ...collection }; const newCollection = { ...collection };
const maxThroughput = 5000; const maxThroughput = 5000;
@@ -66,7 +66,7 @@ describe("ScaleComponent", () => {
wrapper = shallow(<ScaleComponent {...newProps} />); wrapper = shallow(<ScaleComponent {...newProps} />);
expect(wrapper.exists("#throughputApplyShortDelayMessage")).toEqual(true); expect(wrapper.exists("#throughputApplyShortDelayMessage")).toEqual(true);
expect(wrapper.exists("#throughputApplyLongDelayMessage")).toEqual(false); expect(wrapper.exists("#throughputApplyLongDelayMessage")).toEqual(false);
expect(wrapper.find("#throughputApplyShortDelayMessage").html()).toContain(maxThroughput); expect(wrapper.find("#throughputApplyShortDelayMessage").html()).toContain(`${maxThroughput}`);
}); });
it("autoScale disabled", () => { it("autoScale disabled", () => {

View File

@@ -0,0 +1,36 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ComputedPropertiesComponent renders 1`] = `
<Stack
tokens={
{
"childrenGap": 5,
}
}
>
<Text
style={
{
"marginBottom": "10px",
"marginLeft": "30px",
}
}
>
<StyledLinkBase
href="https://aka.ms/computed-properties-preview/"
target="_blank"
>
Learn more
<FontIcon
iconName="NavigateExternalInline"
/>
</StyledLinkBase>
  about how to define computed properties and how to use them.
</Text>
<div
className="settingsV2Editor"
tabIndex={0}
/>
</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

@@ -3,7 +3,7 @@
exports[`ScaleComponent renders with correct initial notification 1`] = ` exports[`ScaleComponent renders with correct initial notification 1`] = `
<Stack <Stack
tokens={ tokens={
Object { {
"childrenGap": 20, "childrenGap": 20,
} }
} }
@@ -14,8 +14,8 @@ exports[`ScaleComponent renders with correct initial notification 1`] = `
<Text <Text
id="throughputApplyLongDelayMessage" id="throughputApplyLongDelayMessage"
styles={ styles={
Object { {
"root": Object { "root": {
"color": "windowtext", "color": "windowtext",
"fontSize": 14, "fontSize": 14,
}, },
@@ -30,7 +30,7 @@ exports[`ScaleComponent renders with correct initial notification 1`] = `
</StyledMessageBar> </StyledMessageBar>
<Stack <Stack
tokens={ tokens={
Object { {
"childrenGap": 20, "childrenGap": 20,
} }
} }

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

@@ -4,7 +4,7 @@ import * as ViewModels from "../../../Contracts/ViewModels";
import { MongoIndex } from "../../../Utils/arm/generatedClients/cosmos/types"; import { MongoIndex } from "../../../Utils/arm/generatedClients/cosmos/types";
const zeroValue = 0; const zeroValue = 0;
export type isDirtyTypes = boolean | string | number | DataModels.IndexingPolicy; export type isDirtyTypes = boolean | string | number | DataModels.IndexingPolicy | DataModels.ComputedProperties;
export const TtlOff = "off"; export const TtlOff = "off";
export const TtlOn = "on"; export const TtlOn = "on";
export const TtlOnNoDefault = "on-nodefault"; export const TtlOnNoDefault = "on-nodefault";
@@ -46,6 +46,8 @@ export enum SettingsV2TabTypes {
SubSettingsTab, SubSettingsTab,
IndexingPolicyTab, IndexingPolicyTab,
PartitionKeyTab, PartitionKeyTab,
ComputedPropertiesTab,
ContainerVectorPolicyTab,
} }
export interface IsComponentDirtyResult { export interface IsComponentDirtyResult {
@@ -148,7 +150,11 @@ export const getTabTitle = (tab: SettingsV2TabTypes): string => {
case SettingsV2TabTypes.IndexingPolicyTab: case SettingsV2TabTypes.IndexingPolicyTab:
return "Indexing Policy"; return "Indexing Policy";
case SettingsV2TabTypes.PartitionKeyTab: case SettingsV2TabTypes.PartitionKeyTab:
return "Partition Keys"; return "Partition Keys (preview)";
case SettingsV2TabTypes.ComputedPropertiesTab:
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

@@ -40,6 +40,12 @@ export const collection = {
version: 2, version: 2,
}, },
partitionKeyProperties: ["partitionKey"], partitionKeyProperties: ["partitionKey"],
computedProperties: ko.observable<DataModels.ComputedProperties>([
{
name: "queryName",
query: "query",
},
]),
readSettings: () => { readSettings: () => {
return; return;
}, },

View File

@@ -16,27 +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],
"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,
@@ -60,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],
} }
} }
@@ -90,7 +90,7 @@ exports[`SettingsComponent renders 1`] = `
itemKey="SubSettingsTab" itemKey="SubSettingsTab"
key="SubSettingsTab" key="SubSettingsTab"
style={ style={
Object { {
"marginTop": 20, "marginTop": 20,
} }
} }
@@ -100,20 +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],
"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,
@@ -137,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],
} }
} }
@@ -174,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]}
@@ -205,31 +206,31 @@ exports[`SettingsComponent renders 1`] = `
/> />
</PivotItem> </PivotItem>
<PivotItem <PivotItem
headerText="Partition Keys" headerText="Partition Keys (preview)"
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],
"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,
@@ -253,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,
@@ -296,6 +296,40 @@ exports[`SettingsComponent renders 1`] = `
} }
/> />
</PivotItem> </PivotItem>
<PivotItem
headerText="Computed Properties"
itemKey="ComputedPropertiesTab"
key="ComputedPropertiesTab"
style={
{
"marginTop": 20,
}
}
>
<ComputedPropertiesComponent
computedPropertiesContent={
[
{
"name": "queryName",
"query": "query",
},
]
}
computedPropertiesContentBaseline={
[
{
"name": "queryName",
"query": "query",
},
]
}
logComputedPropertiesSuccessMessage={[Function]}
onComputedPropertiesContentChange={[Function]}
onComputedPropertiesDirtyChange={[Function]}
resetShouldDiscardComputedProperties={[Function]}
shouldDiscardComputedProperties={false}
/>
</PivotItem>
</StyledPivot> </StyledPivot>
</div> </div>
</div> </div>

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,
}, },
@@ -99,23 +99,11 @@ exports[`SettingsUtils functions render 1`] = `
</StyledLinkBase> </StyledLinkBase>
. .
</Text> </Text>
<Text
styles={
Object {
"root": Object {
"color": "windowtext",
"fontSize": 14,
},
}
}
>
You have not saved the latest changes made to your indexing policy. Please click save to confirm the changes.
</Text>
<Text <Text
id="updateThroughputDelayedApplyWarningMessage" id="updateThroughputDelayedApplyWarningMessage"
styles={ styles={
Object { {
"root": Object { "root": {
"color": "windowtext", "color": "windowtext",
"fontSize": 14, "fontSize": 14,
}, },
@@ -126,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,
}, },
@@ -146,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,
}, },
@@ -162,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,
}, },
@@ -177,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,
}, },
@@ -191,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,
}, },
@@ -203,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,
}, },
@@ -223,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,
}, },
@@ -259,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,
}, },
@@ -282,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,
}, },
@@ -299,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

@@ -14,7 +14,17 @@
.throughputInputSpacing > :not(:last-child) { .throughputInputSpacing > :not(:last-child) {
margin-bottom: @DefaultSpace; margin-bottom: @DefaultSpace;
} }
.capacitycalculator-link:focus { .capacitycalculator-link:focus {
text-decoration: underline; text-decoration: underline;
outline-offset: 2px; outline-offset: 2px;
} }
.outlineNone{
outline: none !important;
}
.copyQuery:focus::after,
.deleteQuery:focus::after {
outline: none !important;
}

View File

@@ -223,11 +223,10 @@ 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"
className="capacitycalculator-link"
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"
style={{ outline: "none" }}
> >
capacity calculator capacity calculator
</Link> </Link>
@@ -273,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,30 +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="capacitycalculator-link" className="underlinedLink outlineNone"
href="https://cosmos.azure.com/capacitycalculator/" href="https://cosmos.azure.com/capacitycalculator/"
style={
Object {
"outline": "none",
}
}
target="_blank" target="_blank"
> >
<LinkBase <LinkBase
aria-label="capacity calculator of azure cosmos db" aria-label="capacity calculator of azure cosmos db"
className="capacitycalculator-link" className="underlinedLink outlineNone"
href="https://cosmos.azure.com/capacitycalculator/" href="https://cosmos.azure.com/capacitycalculator/"
style={
Object {
"outline": "none",
}
}
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)",
@@ -765,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",
@@ -859,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)",
@@ -912,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",
@@ -1017,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",
@@ -1029,14 +1019,9 @@ 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 capacitycalculator-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]}
style={
Object {
"outline": "none",
}
}
target="_blank" target="_blank"
> >
capacity calculator capacity calculator
@@ -1057,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",
} }
@@ -1068,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",
} }
@@ -1090,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)",
@@ -1101,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",
@@ -1195,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)",
@@ -1248,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",
@@ -1353,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",
@@ -1385,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)",
@@ -1396,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",
@@ -1490,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)",
@@ -1543,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",
@@ -1648,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",
@@ -1673,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,
@@ -1705,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,
}, },
@@ -1731,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)",
@@ -1742,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",
@@ -1836,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)",
@@ -1889,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",
@@ -1994,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,13 +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) {
this.props.node.onExpanded && setTimeout(this.props.node.onExpanded, TreeNodeComponent.callbackDelayMS); this.props.node.onExpanded && setTimeout(this.props.node.onExpanded, LegacyTreeNodeComponent.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) {
@@ -115,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);
@@ -141,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;
} }
} }
@@ -160,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)}
@@ -178,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="" />}
@@ -195,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}
@@ -216,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,
) )
); );
@@ -232,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}` },
@@ -247,7 +249,7 @@ export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, T
name="More" name="More"
title="More" title="More"
className="treeMenuEllipsis" className="treeMenuEllipsis"
ariaLabel={menuItemLabel} ariaLabel={`${menuItemLabel} options`}
menuIconProps={{ menuIconProps={{
iconName: menuItemLabel, iconName: menuItemLabel,
styles: { root: { fontSize: "18px", fontWeight: "bold" } }, styles: { root: { fontSize: "18px", fontWeight: "bold" } },
@@ -263,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
@@ -297,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 <></>;
} }
@@ -314,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 });
} }
} }
@@ -327,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();
});
});

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