Compare commits

...

94 Commits

Author SHA1 Message Date
JustinKol
00c05fce8a Revert "Adding CESCVA feedback button (#1736)"
This reverts commit 9cebe5f9ba.
2024-03-04 13:12:37 -05:00
Asier Isayas
b480c635ca fix create mongo collection and delete mongo document switching (#1758)
Co-authored-by: Asier Isayas <aisayas@microsoft.com>
2024-02-29 12:10:15 -05:00
Laurent Nguyen
16eb096fdb Don't show Settings buttons for Fabric readonly (#1757)
* Don't show Settings buttons for Fabric readonly

* Fix format
2024-02-28 19:34:33 +01:00
Vsevolod Kukol
e9571c0f2d Update layout and colors for Fabric per req from Design (#1756) 2024-02-27 18:25:35 +01:00
Asier Isayas
c9abcc1728 Prompt Mongo and Cassandra users to allow list Mongo and Cassandra proxies in Azure Portal (#1754)
* Mongo Proxy backend API

* merge main into current

* allow mongo proxy endpoints to be constants

* allow mongo proxy endpoints to be constants

* fix test

* show ip address warning for Mongo and Cassandra accounts

* show ip address warning for Mongo and Cassandra accounts

* removed string from prod

* make mongo proxy endpoint mandatory

* added MongoProxyEndpointsV2

* added MongoProxyEndpointsV2

* moved mongo and cassandra endpoints to Constants

* moved mongo and cassandra endpoints to Constants

* moved mongo and cassandra endpoints to Constants

---------

Co-authored-by: Asier Isayas <aisayas@microsoft.com>
2024-02-22 15:53:01 -05:00
Laurent Nguyen
a36f3f7922 Hide Save/Open query buttons and New Document/Save/Update buttons for Fabric read-only (#1755)
* Remove save query button and new document buttons for Fabric. Introduce a isReadOnly flag in Fabric context

* Fix user context init
2024-02-22 18:34:30 +01:00
JustinKol
5a64fc2582 Revert "Adding CESCVA feedback button (#1736)" (#1753)
This reverts commit 9cebe5f9ba.
2024-02-21 13:30:04 -05:00
Laurent Nguyen
12366bb645 Revert "Hide buttons for Fabric or when no write access (#1742)" (#1751)
This reverts commit f403b086ad.
2024-02-21 17:22:17 +01:00
JustinKol
9cebe5f9ba Adding CESCVA feedback button (#1736)
* Adding CESCVA feedback button

* Feedback message logic added
2024-02-20 13:00:31 -05:00
vchske
5d80ecb462 Fixing terminal tab to display correct API type for network warning (#1747) 2024-02-16 16:22:24 -08:00
vchske
f87611a39d Fixing manual throughput cost estimate (#1740)
* Fixing manual throughput cost estimate

* Fix test and prettier errors
2024-02-14 09:55:45 -08:00
sunghyunkang1111
a914fd020c Partition Key Change with Container Copy (#1734)
* initial commit

* Add change partition key logic

* Update snapshot

* Update snapshot

* Update snapshot

* Update snapshot

* Cleanup code

* Disable Change on progress job

* add the database information in the panel

* add the database information in the panel

* clear in progress message and remove large partition key row

* hide from national cloud

* hide from national cloud

* Add check for public cloud
2024-02-13 14:00:27 -06:00
JustinKol
e43b4eee5c Correcting import for MessageTypes (#1743) 2024-02-13 10:35:14 -05:00
Asier Isayas
8b0b3b07d6 Add Mongo Proxy to Data Explorer (Standalone and Portal) (#1738)
* Mongo Proxy backend API

* merge main into current

* allow mongo proxy endpoints to be constants

* allow mongo proxy endpoints to be constants

* fix test

* check for allowed mongo proxy endpoint

* check for allowed mongo proxy endpoint

---------

Co-authored-by: Asier Isayas <aisayas@microsoft.com>
2024-02-09 10:58:10 -05:00
Laurent Nguyen
f403b086ad Hide buttons for Fabric or when no write access (#1742) 2024-02-09 15:10:57 +01:00
jawelton74
f8cfc6c21c Remove references to addCollectionDefaultFlight parameter. (#1741) 2024-02-08 11:49:40 -08:00
Asier Isayas
35ca7944ae Limit RU threshold only to NoSQL (#1739)
* limit RU threshold only to NoSQL

* limit RU threshold only to NoSQL

---------

Co-authored-by: Asier Isayas <aisayas@microsoft.com>
2024-02-07 11:31:13 -05:00
sindhuba
6d98b4a500 Remove localStorage for NPS (#1733)
* Remove localStorage for NPS

* Run npm format

* Update comment
2024-02-06 09:54:50 -08:00
JustinKol
2d06eef9cc Added CESCVA MessageType for FE (#1737) 2024-02-06 12:46:25 -05:00
Asier Isayas
31f7178669 Change RU Threshold to 5000 (#1735)
* ru threshold beta

* use new ru threshold package

* fix typo

* fix merge issue

* fix package-lock.json

* fix test

* fixed settings pane test

* fixed merge issue

* sync with main

* fixed settings pane check

* fix checks

* fixed aria-label error

* fixed aria-label error

* fixed aria-label error

* fixed aria-label error

* remove learn more

* change default RU threshold to 5000

* change default RU threshold to 5000

* change default RU threshold to 5000

---------

Co-authored-by: Asier Isayas <aisayas@microsoft.com>
2024-02-02 11:52:37 -05:00
JustinKol
c0b54f6e84 Added TableAPI whitespace check and trim values (#1731)
* Added TableAPI whitespace check and trim values

* Reverted value is not required unless Row or PrimaryKey

* Prettier Run

* Add full whitespace check on Keys

* Prettier formatting

* Fixed type

* Added whitespace check for tabs, vertical tabs, formfeeds, line breaks, etc

* Fixed logic
2024-02-01 12:51:23 -05:00
jawelton74
cd3eb5b5b3 Fix regression in Quickstart workflow. (#1732) 2024-01-30 16:31:46 -08:00
Asier Isayas
dbb0324a64 RU Threshold (#1728)
* ru threshold beta

* use new ru threshold package

* fix typo

* fix merge issue

* fix package-lock.json

* fix test

* fixed settings pane test

* fixed merge issue

* sync with main

* fixed settings pane check

* fix checks

* fixed aria-label error

* fixed aria-label error

* fixed aria-label error

* fixed aria-label error

* remove learn more

---------

Co-authored-by: Asier Isayas <aisayas@microsoft.com>
2024-01-30 16:21:29 -05:00
sindhuba
e207f3702b Add more logs for NPS (#1729) 2024-01-24 16:46:28 -08:00
MokireddySampath
f496220ed6 Empty coumnheader has been given the icon with name of description (#1718) 2024-01-23 13:28:13 +05:30
MokireddySampath
eb790d09b5 role of heading has been added to the text that is visually appearing… (#1701)
* role of heading has been added to the text that is visually appearing as heading

* Update WelcomeModal.test.tsx.snap
2024-01-23 00:08:27 +05:30
MokireddySampath
323305e485 state of the buttons will now be updated by screen reader (#1716) 2024-01-20 09:17:47 +05:30
sunghyunkang1111
70635e426f Fix the teaching bubble popup and enable copilot card (#1722)
* Fix the teaching bubble popup and enable copilot card

* add close copilot button title

* fix compilation
2024-01-19 09:37:17 -06:00
sindhuba
5a5bf34d4d Update logic for NPS survey for existing accounts > 90 days (#1725)
* Update logic for NPS survey for existing accounts > 90 days

* Remove lint error

* Address comments

* Fix error in code
2024-01-19 07:08:11 -08:00
Laurent Nguyen
0975591945 Fabric: clean up RPC and support show/hide toolbar message (#1680)
* Use Promise for allResourceToken fabric message. Cleanup token message handling and add debounce.

* Improve rpc and update initalization flow

* Fix format

* Rev up message names for new version

* Refactor RPC with Fabric

* Build fix

* Fix build

* Fix format

* Update Message types

* Fix format

* Fix comments

* Fabric toolbar style and support to show/hide it (#1720)

* Add Fabric specific Toolbar design

* Add Fabric message to show/hide the Toolbar

* Fix CommandBarUtil formatting

* Update zustand state on setToolbarStatus to trigger a redraw of the command bar with updated visibility

---------

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

* Fix format

---------

Co-authored-by: Vsevolod Kukol <sevoku@microsoft.com>
2024-01-18 17:13:04 +01:00
sunghyunkang1111
5d13463bdb Copilot settings (#1719)
* gating copilot with AFEC

* Add enable sample DB in settings

* Add enable sample DB in settings

* Add enable sample DB in settings

* PR comments
2024-01-09 13:32:20 -06:00
MokireddySampath
c4cceceafc Status attribute added for the message to be readout by screenreader (#1709) 2024-01-04 20:43:44 +05:30
MokireddySampath
532a453f5a Aria label has been updated to the button(Enable copilot/Disable copilot) (#1706) 2024-01-04 19:38:05 +05:30
MokireddySampath
9355a3ae04 Altt text and role has been added to the 'copilot icon' (#1705) 2024-01-04 19:37:50 +05:30
MokireddySampath
14456c2102 label has been added to the textfield of copilot promptbar (#1704) 2024-01-04 19:37:35 +05:30
MokireddySampath
0c9264e8b3 Bug 2819239:Screen reader does not announce the loading information which appears on invoking the 'Send' button. (#1703)
* Alert has been added and content also added accordign to the loader state

* Snapshot of tests have been updated
2024-01-04 19:37:16 +05:30
MokireddySampath
0dd1032357 Bug 2817823: [Programmatic access - Cosmos DB Query Copilot - Query]: Accessible name is not defined for the 'Like', 'Dislike' and 'Send' buttons present on the page. (#1700)
* text color of link has been changed to get the contrast ratio of atleaast 4.5:1

* screen reader names have been added to the buttons

* Update QueryCopilotPromptbar.tsx
2024-01-04 19:35:12 +05:30
MokireddySampath
5011d12f16 text color of link has been changed to get the contrast ratio of atleaast 4.5:1 (#1699) 2024-01-04 19:34:58 +05:30
MokireddySampath
a7e5ff2a9f status role has been added for the screen reader to announce the status message displayed (#1697) 2024-01-04 19:34:26 +05:30
MokireddySampath
ad1391f623 visual and arialabel were different which has been changed as required and tests have been updated (#1685) 2024-01-04 19:34:14 +05:30
MokireddySampath
a2a5407b15 Label has been added to the text field on selecting autoscale in throughputc (#1676) 2024-01-04 19:33:55 +05:30
Laurent Nguyen
e9181f19d7 Fix datatables issue and indicator not loading for Table API > Entities. Upgrade jquery. Fix right panel resize issue. (#1713)
* Fix datatables issue and indicator not loading for Table API > Entities

* Fix jquery and datatables compile issues. Add patch for datatables.net-colreorder error in types

* Fix side panel size. Fix bug resizing side panel.

* Update PanelContainerComponent unit test snapshot

* Fix commented code
2024-01-03 14:52:34 +01:00
JustinKol
c91ac39248 Query Max Retry settings (#1688)
* Added query retry settings

* prettier run

* Fixed tests and queryDocuments

* Fixed tests

* corrected logic

* Updated tests and logic

* Removed optional flag

* Added default value text

* Reworded text

* moved retry options to CosmosClient

* removed unused references to retryOptions

* Reverting formatting

* reverting

* revert

* prettier run

* Correct default and added options directly to the client

* Prettier run and unit test update

* Corrected tooltip and constant name

* Added inSeconds to WaitTime
2023-12-29 08:12:20 -05:00
Armando Trejo Oliver
c82a4737c6 Add support for Dogfood (#1715)
* Add support for Dogfood

* Format
2023-12-20 05:53:02 -08:00
jawelton74
c6aefacc4e Fix the Nuget Publish MPAC action in CI. (#1712) 2023-12-13 11:44:46 -08:00
bogercraig
bf7c0aac63 Enabling endpoint discovery to test region failure in a non-dev environment. (#1711) 2023-12-13 10:44:17 -08:00
Laurent Nguyen
1bf4683894 Make Data Explorer work on node v18 (#1654)
* Upgrade packages to enable npm i with node 18

* Fix crypto and querystring issue

* Fix webpack errors during npm start

* Upgrade monaco editor. Fix alias in webconfig

* Remove deprecated file-loader. Upgrade webpack to latest.

* Fix format

* Upgrade webpack, eslint and typescript

* Update p-retry and fluentui packages

* Revert monaco package upgrade

* Fix notebook compile errors

* Fix lint errors

* Update jest snapshots

* Fix unit tests

* Update node version to 18

* Fix compile error

* Fix compile error

* Fix format

* Turn off warning overlay for webpack devServer

* Fix format

* Re-add monaco webpack plugin and upgrade monaco-editor

* Update package-lock.json

* Fix build issue

* Move MonacoWebpackPlugin to previous place in webpack.config.js

* update package-lock.json

* Fix package-lock.json

* Update package-lock.json

* Fix export ChoiceItem not found warning for self serve. Remove warning turn off in webpack config.

* Update checkout and setup actions in for ci tests

* Disable Gallery callout

* Fix disable gallery header

* Totally disable New gallery callout

* Upgrade all github actions to latest
2023-12-13 10:24:40 -08:00
sunghyunkang1111
59a50d72fe Remove copilot feature registration check (#1708)
* Remove copilot banner

* fix test
2023-12-11 14:42:10 -06:00
bogercraig
b19aa3fbca Reverting enabling endpoint discovery since it didn't have time during CCOA to bake in MPAC. (#1698) 2023-11-28 13:29:27 -08:00
MokireddySampath
0f69b7998f outline offset has been added for the focus indicator to be visible on navigation (#1551) 2023-11-22 00:08:59 +05:30
MokireddySampath
acd4787b3d WCAG 1.3.1,WCAG 4.1.2: Ensures every form element has a label (textarea) (#1549) 2023-11-22 00:08:32 +05:30
MokireddySampath
4ec6e7b8cc aria posinset has been removed, since it is not an attribute that is supported by the HTML elements used (#1547) 2023-11-22 00:08:17 +05:30
MokireddySampath
4fcbf7f0ea Alert is being updated for every change, hence the screenreader readsout the alert everytime a change occursin the input (#1544)
* Alert is being updated for every change, hence the screenreader readsout the alert everytime a change occursin the input

* Update DeleteCollectionConfirmationPane.tsx

* Update DeleteCollectionConfirmationPane.tsx
2023-11-22 00:07:33 +05:30
MokireddySampath
d0c75e4490 role has been changed to buttton for the graphic images of edit and delete buttons (#1652) 2023-11-22 00:07:02 +05:30
MokireddySampath
50bd17dd61 role=presentation is removed for the image in add new clause button, since it is not accepted format (#1653) 2023-11-22 00:06:35 +05:30
MokireddySampath
3d300cc1e4 heading has been changed to follow the hierarchy down the page (#1656) 2023-11-22 00:06:09 +05:30
MokireddySampath
6e956d27ad Provisioned throughput checkbox is not aligned (#1661) 2023-11-22 00:05:42 +05:30
MokireddySampath
b419bec5a3 Bug 2748872: "Back" button is not keyboard accessible which is present under "Email" screen. (#1665)
* Keyboard navigation was not present to the back button on the add row table dialog which has been added through this commit

* Update PanelComponent.less
2023-11-22 00:02:05 +05:30
MokireddySampath
d305eb9f9b Aria-label text link hs been changed to match the visble link (#1666) 2023-11-22 00:01:45 +05:30
MokireddySampath
05f3bef5b9 Dependency has been added tot he useeffect for tabs to avoid rerendering continuosly (#1682) 2023-11-22 00:00:03 +05:30
bogercraig
6af1925d15 Enabling enableEndpointDiscovery to test DE Region Outage Resiliency (#1694)
* Setting enableEndpointDiscovery to true to test client resilience during region outage.
When this is set to false, it can impact the SDK's failover behavior.
2023-11-16 11:56:39 -08:00
sunghyunkang1111
b9cbfc924f do not throw error when checking for initial checking of copilot enablement (#1693) 2023-11-14 17:22:12 -06:00
sindhuba
1aa4c18119 Increase NPS sample size and remove constraints (#1692)
* Increase NPS sample size and remove constraints

* Run npm format

* Run npm format:check
2023-11-14 22:26:57 +05:30
sunghyunkang1111
0ab07419ce fix the phoenix endpoint (#1691) 2023-11-10 18:53:23 -06:00
sunghyunkang1111
40b8127a6f P1 bugs for copilot (#1689)
* P1 bugs for copilot

* P1 bugs for copilot

* P1 bugs for copilot

* Update branding and AFEC

* Update branding and AFEC

* add timeout for ARM calls

* increase test timeout

* fix formatting

* fix formatting

* fix formatting

* fix formatting

* don't call ARM when connectionstring login
2023-11-10 16:55:39 -06:00
sunghyunkang1111
0e124f4881 Copilot user db (#1672)
* Implement copilot for user database

* Fix minor bugs

* fix bugs

* Add user database copilot

* Add placeholder text on copilot

* Add AFEC adn killswitch

* Add new v2 sampledatabase endpoint

* Add telemetry

* fix telemetry bug

* Add query edited telemetry

* add authorization header

* Add back to the staging env for phoenix

* point to stage for phoenix

* Preview commit for test env

* Preview link for staging

* change the staging url

* fix lint, unit tests

* fix lint, unit tests

* fix formatting
2023-11-09 11:55:25 -06:00
vchske
a5e1b37ba6 Bugbash updates (#1684)
* Minor changes to vcore mongo quickstart based on feedback

* Text change vcore to vCore
2023-11-08 16:04:06 -08:00
jawelton74
15d111e3db Add directions to use AAD login if account blocked from connection (#1683)
string login.
2023-11-01 15:46:54 -07:00
Asier Isayas
1726e4df51 Remove enableResourceGraph feature flag (#1681)
* fetch subs and accounts via graph

* fixed subscription rendering

* add feature flag enableResourceGraph

* add feature flag enableResourceGraph

* remove enableResourceGraph feature flag

---------

Co-authored-by: Asier Isayas <aisayas@microsoft.com>
2023-10-27 14:09:21 -04:00
vchske
bc68b4dbf9 Minor changes to vcore mongo quickstart based on feedback (#1678) 2023-10-26 16:46:01 -07:00
vchske
15e35eaa82 Adds restarting vcore mongo shell if user enters wrong password (#1675)
* Adds restarting vcore mongo shell if user enters wrong password

* Deleted unnecessary comment
2023-10-26 16:40:59 -07:00
Asier Isayas
f69cd4c495 Fetch subs and accounts via MS graph (#1677)
* fetch subs and accounts via graph

* fixed subscription rendering

* add feature flag enableResourceGraph

* add feature flag enableResourceGraph

---------

Co-authored-by: Asier Isayas <aisayas@microsoft.com>
2023-10-26 12:00:39 -04:00
Laurent Nguyen
661f6f4bfb Clear console and properly handle error message when querying database or containers (#1679) 2023-10-26 15:55:48 +00:00
JustinKol
3a703b0bd0 Explorer null handling (#1658)
* Cassandra null value handling

* prettier run

* Added boolean and date

* Reverted null handling switch statement and added editing warning

* ran prettier

* putting new line back in

* prettier run

* changed warning message

* updated snapshot

* fixed undefined condition

* removed toString from undefined
2023-10-25 10:12:42 -04:00
jawelton74
70c031d04b Check for accounts that are restricted from connection string login. (#1673)
* Call Portal Backend to check for accounts that are restricted from
connection string login.

* Remove debug logging and revert to actual backend endpoint.

* Use React hooks for displaying error message.

* Move accountrestrictions route under guest.

* Check restrictions before resource token check.

* Revert test changes.
2023-10-23 12:04:19 -07:00
MokireddySampath
b949f60c2a Defect2271490 (#1474)
* 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

* header text color changed to meet luminosity ratio requirement

* capacity calculator link has been added with underline on focus

* add property is readout twice while using screenreader

* arialabel added to the add property button

* screenreader content changed to announce the entire alert

* alert message is partially readout on error

* Update fulldatatables.less

* Update queryBuilder.less

* Update TableEntity.tsx

* Update ThroughputInput.less

* Update ThroughputInput.tsx

* Update ThroughputInput.test.tsx.snap

* Update NewVertexComponent.tsx

* Update PanelInfoErrorComponent.tsx

* Update AddTableEntityPanel.tsx

* Update AddTableEntityPanel.test.tsx.snap

* Update SplashScreen.tsx

* Update QuickstartCarousel.tsx

* Update RightPaneForm.tsx

* Update fulldatatables.less
2023-10-23 18:04:59 +05:30
vchske
deb0ebcf92 Updating vcore mongo quickstart text based on feedback (#1669)
* Updating quickstart text based on feedback

* Escaping apostrophe
2023-10-20 12:32:22 -07:00
Laurent Nguyen
2d3048eafe Fabric: handle resource tokens (#1667)
* Update contracts for new all resource messages

* Add timestamp to token message signature

* Reconstruct resource tree with databases and collections parsed from token dictionary keys

* Create FabricDatabase and FabricCollection to turn off interaction

* Remove unnecessary FabricCollection derived class

* Handle resource tokens

* Bug fix

* Fix linting issues

* Fix update document

* Fix partitition keys

* Remove special case for FabricDatabase tree node

* Modify readCollections to follow normal flow with Fabric

* Move fabric databases refresh to data access and remove special case in Explorer

* Revert Explorer.tsx changes

* Disable database context menu and delete container context menu

* Remove create database/container button for Fabric

* Fix format

* Renew token logic

* Parallelize read collections calls to speed up

* Disable readDatabaseOffer, because it is too slow for now

* Reduce TOKEN_VALIDITY_MS a bit to make sure renewal happens before expiration. Receving new tokens new refreshes databases

* Add container element for Main app in HTML

* Do not handle "openTab" message anymore

* Fix style of main div

* Simplify conditional load of the fabric .css

* Fix format

* Fix tsc can't find dynamic less import

---------

Co-authored-by: Armando Trejo Oliver <artrejo@microsoft.com>
2023-10-19 23:12:52 +02:00
Asier Isayas
8075ef2847 Upgrade Cosmos SDK to 4.0.0 (#1664)
* upgrade cosmos sdk to 4.0.0

* added explicit any test

* fixed package-lock.json

---------

Co-authored-by: Asier Isayas <aisayas@microsoft.com>
2023-10-19 15:22:12 -04:00
Asier Isayas
94158504a8 Cancel query timeout (#1651)
* cancel query option

* query timeout

* run prettier

* removed comments

* fixed npm run compile errors

* fixed tests

* fixed unit test  errors

* fixed unit test  errors

* fixed unit test  errors

* fixed unit test  errors

* fixed unit test  errors

* increased min timeout

* added automatican cancel query option

* added react string format

* npm run format

* added unless automatic cancellation has been enabled

---------

Co-authored-by: Asier Isayas <aisayas@microsoft.com>
2023-10-19 13:21:39 -04:00
Laurent Nguyen
9b032ecae4 Turn off "New Database" for Fabric (#1663)
* Turn off "New Database" for Fabric

* Fix format
2023-10-18 08:30:45 +00:00
jawelton74
14d7677056 Add feature for disabling connection string login and enable this for aad redirect. (#1660)
* Add redirect for /aad to /?feature.enableAadDataPlane=true

* Add feature to hide the connection string login link. Enable this new
feature for the aad redirect.
2023-10-16 13:18:40 -07:00
sunghyunkang1111
d376a7463c P1, P2 bug fixes for private preview (#1657)
* P1 bug fix for private preview

* Add updated snapshot files

* Fix failing unit test

* Fix unit tests and update snapshot
2023-10-12 21:54:01 -05:00
Laurent Nguyen
9669301d14 Remove obsolete unit test (#1655) 2023-10-12 08:00:08 +02:00
Laurent Nguyen
dcd8d1637b Implement retrieval of authorization token for Fabric via iframe rpc (#1647)
* For Fabric, send message to get Authorization token from iframe parent

* tokenProvider: set date header and return token

* Expect account endpoint on initialize message from Fabric

* Fix format

---------

Co-authored-by: artrejo <artrejo@microsoft.com>
2023-10-10 12:25:58 -07:00
Laurent Nguyen
f36fccd3ef Auto-select first item in DocumentsTab. Fix selection highlighting (#1645)
* Auto-select first document in documentsTab

* Fix style row selection by making selector more specific
2023-10-10 07:19:35 +02:00
jawelton74
8ff9a84004 Add redirect for /aad to /?feature.enableAadDataPlane=true (#1649) 2023-10-09 11:31:36 -07:00
sunghyunkang1111
e42e24b175 Redesign copilot (#1650)
* Add copilot toggle button

* take out toggle button when closing tab

* Update snapshots

* Fix the prettier issue

* fix prettier
2023-10-09 12:42:25 -05:00
Laurent Nguyen
44d6f29edd Revert "For Fabric, send message to get Authorization token from iframe parent"
This reverts commit 9db06af552.
2023-10-06 14:35:30 +00:00
Laurent Nguyen
9db06af552 For Fabric, send message to get Authorization token from iframe parent 2023-10-06 14:33:46 +00:00
Asier Isayas
3754d2c32c Cancel Query Option (#1646)
* cancel query option
---------

Co-authored-by: Asier Isayas <aisayas@microsoft.com>
2023-10-05 10:37:59 -04:00
FAIZ CHACHIYA
ca861a0d77 For connection-string based authentication, the request would be made… (#1644)
* For connection-string based authentication, the request would be made with low priority request header and for AAD based authentication, users can select the priority-request option from the settings panel

* removed unused imports

* prettier checks

---------

Co-authored-by: Faiz Chachiya <faizchachiya@microsoft.com>
2023-10-04 14:18:54 -07:00
JustinKol
07d242f972 Fixed setup save queries within serverless accounts (#1626)
* Fixed setup save queries within serverless accounts

* Fixed format

* ran prettier
2023-10-04 14:23:22 -04:00
sunghyunkang1111
68b45e77a8 clear query results and handle error with no query generated (#1640)
* clear query results and handle error with no query generated

* clear query results and handle error with no query generated

* Update the query guidelines link

* Update the query guidelines link
2023-10-04 12:20:56 -05:00
223 changed files with 41222 additions and 27001 deletions

View File

@@ -145,4 +145,5 @@ src/Explorer/Notebook/temp/inputs/connected-editors/codemirror.tsx
src/Explorer/Tree/ResourceTreeAdapter.tsx
__mocks__/monaco-editor.ts
src/Explorer/Tree/ResourceTree.tsx
src/Utils/EndpointUtils.ts
src/Utils/PriorityBasedExecutionUtils.ts

View File

@@ -53,4 +53,9 @@ module.exports = {
},
],
},
settings: {
react: {
version: "detect",
},
},
};

View File

@@ -14,11 +14,11 @@ jobs:
name: "Log Code Metrics"
if: github.ref == 'refs/heads/master'
steps:
- uses: actions/checkout@v2
- name: Use Node.js 14.x
uses: actions/setup-node@v1
- uses: actions/checkout@v4
- name: Use Node.js 18.x
uses: actions/setup-node@v4
with:
node-version: 14.x
node-version: 18.x
- run: npm ci
- run: node utils/codeMetrics.js
env:
@@ -27,11 +27,11 @@ jobs:
runs-on: ubuntu-latest
name: "Compile TypeScript"
steps:
- uses: actions/checkout@v2
- name: Use Node.js 14.x
uses: actions/setup-node@v1
- uses: actions/checkout@v4
- name: Use Node.js 18.x
uses: actions/setup-node@v4
with:
node-version: 14.x
node-version: 18.x
- run: npm ci
- run: npm run compile
- run: npm run compile:strict
@@ -39,44 +39,44 @@ jobs:
runs-on: ubuntu-latest
name: "Check Format"
steps:
- uses: actions/checkout@v2
- name: Use Node.js 14.x
uses: actions/setup-node@v1
- uses: actions/checkout@v4
- name: Use Node.js 18.x
uses: actions/setup-node@v4
with:
node-version: 14.x
node-version: 18.x
- run: npm ci
- run: npm run format:check
lint:
runs-on: ubuntu-latest
name: "Lint"
steps:
- uses: actions/checkout@v2
- name: Use Node.js 14.x
uses: actions/setup-node@v1
- uses: actions/checkout@v4
- name: Use Node.js 18.x
uses: actions/setup-node@v4
with:
node-version: 14.x
node-version: 18.x
- run: npm ci
- run: npm run lint
unittest:
runs-on: ubuntu-latest
name: "Unit Tests"
steps:
- uses: actions/checkout@v2
- name: Use Node.js 14.x
uses: actions/setup-node@v1
- uses: actions/checkout@v4
- name: Use Node.js 18.x
uses: actions/setup-node@v4
with:
node-version: 14.x
node-version: 18.x
- run: npm ci
- run: npm run test
build:
runs-on: ubuntu-latest
name: "Build"
steps:
- uses: actions/checkout@v2
- name: Use Node.js 14.x
uses: actions/setup-node@v1
- uses: actions/checkout@v4
- name: Use Node.js 18.x
uses: actions/setup-node@v4
with:
node-version: 14.x
node-version: 18.x
- run: npm ci
- run: npm run build:contracts
- name: Restore Build Cache
@@ -86,10 +86,10 @@ jobs:
key: ${{ runner.os }}-build-cache
- run: npm run pack:prod
env:
NODE_OPTIONS: '--max-old-space-size=4096'
NODE_OPTIONS: "--max-old-space-size=4096"
- run: cp -r ./Contracts ./dist/contracts
- run: cp -r ./configs ./dist/configs
- uses: actions/upload-artifact@v2
- uses: actions/upload-artifact@v3
with:
name: dist
path: dist/
@@ -107,11 +107,11 @@ jobs:
if: false
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
- name: Use Node.js 14.x
uses: actions/setup-node@v1
- uses: actions/checkout@v4
- name: Use Node.js 18.x
uses: actions/setup-node@v4
with:
node-version: 14.x
node-version: 18.x
- uses: southpolesteve/cosmos-emulator-github-action@v1
- name: End to End Tests
run: |
@@ -124,7 +124,7 @@ jobs:
DATA_EXPLORER_ENDPOINT: "https://localhost:1234/explorer.html?platform=Emulator"
PLATFORM: "Emulator"
NODE_TLS_REJECT_UNAUTHORIZED: 0
- uses: actions/upload-artifact@v2
- uses: actions/upload-artifact@v3
if: failure()
with:
name: screenshots
@@ -149,11 +149,11 @@ jobs:
- ./test/sql/resourceToken.spec.ts
- ./test/tables/container.spec.ts
steps:
- uses: actions/checkout@v2
- name: Use Node.js 14.x
uses: actions/setup-node@v1
- uses: actions/checkout@v4
- name: Use Node.js 18.x
uses: actions/setup-node@v4
with:
node-version: 14.x
node-version: 18.x
- run: npm ci
- run: npm start &
- run: npm run wait-for-server
@@ -162,7 +162,7 @@ jobs:
# 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@v2
- uses: actions/upload-artifact@v3
if: failure()
with:
name: screenshots
@@ -180,14 +180,14 @@ jobs:
with:
nuget-api-key: ${{ secrets.NUGET_API_KEY }}
- name: Download Dist Folder
uses: actions/download-artifact@v2
uses: actions/download-artifact@v3
with:
name: dist
- run: cp ./configs/prod.json config.json
- run: nuget sources add -Name "ADO" -Source "$NUGET_SOURCE" -UserName "jawelton@microsoft.com" -Password "$AZURE_DEVOPS_PAT"
- run: nuget pack -Version "2.0.0-github-${GITHUB_SHA}"
- run: nuget push -SkipDuplicate -Source "$NUGET_SOURCE" -ApiKey Az *.nupkg
- uses: actions/upload-artifact@v2
- uses: actions/upload-artifact@v3
name: packages
with:
path: "*.nupkg"
@@ -204,7 +204,7 @@ jobs:
with:
nuget-api-key: ${{ secrets.NUGET_API_KEY }}
- name: Download Dist Folder
uses: actions/download-artifact@v2
uses: actions/download-artifact@v3
with:
name: dist
- run: cp ./configs/mpac.json config.json
@@ -212,7 +212,7 @@ jobs:
- run: nuget sources add -Name "ADO" -Source "$NUGET_SOURCE" -UserName "jawelton@microsoft.com" -Password "$AZURE_DEVOPS_PAT"
- run: nuget pack -Version "2.0.0-github-${GITHUB_SHA}"
- run: nuget push -SkipDuplicate -Source "$NUGET_SOURCE" -ApiKey Az *.nupkg
- uses: actions/upload-artifact@v2
- uses: actions/upload-artifact@v3
name: packages
with:
path: "*.nupkg"

View File

@@ -20,9 +20,9 @@ jobs:
NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET: ${{ secrets.NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET }}
steps:
- uses: actions/checkout@v2
- name: Use Node.js 14.x
- name: Use Node.js 18.x
uses: actions/setup-node@v1
with:
node-version: 14.x
node-version: 18.x
- run: npm ci
- run: node utils/cleanupDBs.js

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="M13.3626 11.9771C13.1573 11.9837 13.019 12.0288 12.9215 12.0836C12.817 12.1424 12.7301 12.229 12.6508 12.3542C12.4802 12.6239 12.3837 12.9957 12.252 13.5033L12.2347 13.5699C12.1079 14.0581 11.9424 14.6764 11.5775 15.1594C11.1788 15.687 10.5903 16 9.74835 16H4.63681C3.55423 16 2.75741 15.2435 2.18373 14.3271C1.60629 13.4047 1.17786 12.2045 0.845481 11.0542L0.845003 11.0525C0.464365 9.73512 -0.017355 8.06787 0.000479698 6.72244C0.00951195 6.04103 0.145994 5.34953 0.569528 4.825C1.00712 4.28305 1.66518 4.02294 2.50365 4.02294H2.63739C2.84267 4.01631 2.98102 3.97123 3.07848 3.9164C3.18305 3.85757 3.26995 3.77102 3.34916 3.64584C3.51985 3.37613 3.61631 3.00432 3.74801 2.49671L3.76529 2.43012C3.89213 1.94191 4.05757 1.32365 4.42251 0.840633C4.82119 0.312966 5.40972 0 6.25165 0H11.3632C12.4458 0 13.2426 0.756543 13.8163 1.67295C14.3937 2.59534 14.8221 3.79546 15.1545 4.94582L15.155 4.94748C15.5356 6.26488 16.0174 7.93213 15.9995 9.27756C15.9905 9.95897 15.854 10.6505 15.4305 11.175C14.9929 11.717 14.3348 11.9771 13.4963 11.9771H13.3626ZM14.1354 5.28381C13.8114 4.16251 13.4194 3.09096 12.9303 2.3096C12.4374 1.52225 11.9215 1.14296 11.3632 1.14296H7.95613C8.10274 1.42082 8.22537 1.731 8.33548 2.05199C8.48008 2.47355 8.61427 2.94842 8.75233 3.43699L8.78302 3.54557C9.32758 5.47082 10.008 7.94794 10.4498 9.5646C10.6507 10.2998 11.2693 10.8098 11.9798 10.8333H13.4156C13.4253 10.8333 13.4351 10.8335 13.4447 10.8341H13.4963C14.1298 10.8341 14.4484 10.6443 14.6237 10.4272C14.813 10.1928 14.9255 9.8162 14.9328 9.26133C14.9477 8.13479 14.5323 6.65749 14.1354 5.28381ZM3.06972 13.6904C3.56261 14.4777 4.0785 14.857 4.63681 14.857H8.04387C7.89726 14.5792 7.77463 14.269 7.66452 13.948C7.51992 13.5265 7.38573 13.0516 7.24767 12.563L7.21698 12.4544C6.67242 10.5292 5.99197 8.05206 5.55019 6.4354C5.34929 5.70024 4.73069 5.19015 4.02018 5.16674H2.58445C2.57465 5.16674 2.56492 5.16646 2.55526 5.1659H2.50365C1.87025 5.1659 1.5516 5.35572 1.37634 5.57277C1.18703 5.80723 1.07454 6.1838 1.06719 6.73867C1.05225 7.86521 1.46769 9.34251 1.86459 10.7162C2.18857 11.8375 2.58057 12.909 3.06972 13.6904ZM9.10298 10.8333H9.87304C9.67506 10.5554 9.5217 10.236 9.42598 9.88575C9.233 9.17956 8.99466 8.30985 8.74215 7.39421L8.47331 6.42622C8.26579 5.67902 7.62465 5.16674 6.89702 5.16674H6.12695C6.32494 5.44463 6.4783 5.76395 6.57402 6.11425C6.767 6.82044 7.00534 7.69015 7.25785 8.60579L7.52669 9.57378C7.73421 10.321 8.37535 10.8333 9.10298 10.8333ZM6.89702 4.02378C7.23212 4.02378 7.55609 4.08968 7.85646 4.21152C7.82459 4.0984 7.7931 3.98682 7.76205 3.87706L7.73417 3.77844C7.59304 3.27914 7.46754 2.83514 7.33414 2.44626C7.19129 2.02979 7.05211 1.71602 6.90214 1.4942C6.77014 1.29897 6.56087 1.14296 6.25165 1.14296C5.69009 1.14296 5.42306 1.33297 5.2517 1.55976C5.04661 1.83121 4.92755 2.21889 4.79304 2.73662C4.78284 2.77588 4.77249 2.81622 4.76191 2.85744C4.66966 3.21703 4.55997 3.64464 4.37733 4.02378H6.89702ZM9.10298 11.9762C8.76788 11.9762 8.44391 11.9103 8.14354 11.7885C8.1754 11.9016 8.2069 12.0132 8.23795 12.1229L8.26583 12.2216C8.40696 12.7208 8.53246 13.1649 8.66585 13.5537C8.80871 13.9702 8.94789 14.284 9.09786 14.5058C9.22986 14.701 9.43913 14.857 9.74835 14.857C10.3099 14.857 10.5769 14.667 10.7483 14.4402C10.9534 14.1688 11.0725 13.7811 11.207 13.2634C11.2172 13.2241 11.2275 13.1838 11.2381 13.1426C11.3303 12.783 11.44 12.3554 11.6227 11.9762H9.10298Z" fill="#0078D4"/>
</svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@@ -1,34 +1,40 @@
<svg width="32" height="34" viewBox="0 0 32 34" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13 0.739258L16 1.04436V9.68622L11.2 10.8013L9.60002 12.9967V16.4479C9.60002 18.7669 10.8545 20.9045 12.879 22.0353L17.4036 24.5626L9.6 28.69L6.35592 29.1523L3.27902 27.4337C1.25445 26.3028 0 24.1652 0 21.8462V11.7591C0 9.43952 1.25512 7.30147 3.28055 6.17085L12.3174 1.12639L12.3121 1.13012L13 0.739258Z" fill="url(#paint0_radial_857_143948)"/>
<path d="M13 0.739258L16 1.04436V9.68622L11.2 10.8013L9.60002 12.9967V16.4479C9.60002 18.7669 10.8545 20.9045 12.879 22.0353L17.4036 24.5626L9.6 28.69L6.35592 29.1523L3.27902 27.4337C1.25445 26.3028 0 24.1652 0 21.8462V11.7591C0 9.43952 1.25512 7.30147 3.28055 6.17085L12.3174 1.12639L12.3121 1.13012L13 0.739258Z" fill="url(#paint1_linear_857_143948)"/>
<path d="M22.399 12.001L30.399 16.801L31.999 18.401V21.8452C31.999 24.1642 30.7446 26.3018 28.72 27.4327L19.12 32.7949C17.1804 33.8784 14.8177 33.8784 12.878 32.7949L3.27804 27.4327C3.09146 27.3284 2.91141 27.2157 2.73828 27.095L3.278 27.3965C5.21762 28.4799 7.58035 28.4799 9.51996 27.3965L19.12 22.0342C21.1445 20.9033 22.399 18.7657 22.399 16.4467V12.001Z" fill="url(#paint2_radial_857_143948)"/>
<path d="M22.399 12.001L30.399 16.801L31.999 18.401V21.8452C31.999 24.1642 30.7446 26.3018 28.72 27.4327L19.12 32.7949C17.1804 33.8784 14.8177 33.8784 12.878 32.7949L3.27804 27.4327C3.09146 27.3284 2.91141 27.2157 2.73828 27.095L3.278 27.3965C5.21762 28.4799 7.58035 28.4799 9.51996 27.3965L19.12 22.0342C21.1445 20.9033 22.399 18.7657 22.399 16.4467V12.001Z" fill="url(#paint3_linear_857_143948)"/>
<path d="M28.7191 6.17053L19.119 0.811705C17.1802 -0.270568 14.819 -0.270568 12.8802 0.811704L12.3151 1.1271C10.6239 2.31737 9.59961 4.26472 9.59961 6.36025V13.0461L12.8802 11.2149C14.819 10.1326 17.1802 10.1326 19.119 11.2149L28.7191 16.5737C30.6984 17.6786 31.9421 19.7456 31.9977 22.0039C31.999 21.9514 31.9996 21.8987 31.9996 21.8459V11.7588C31.9996 9.4392 30.7445 7.30115 28.7191 6.17053Z" fill="url(#paint4_radial_857_143948)"/>
<path d="M28.7191 6.17053L19.119 0.811705C17.1802 -0.270568 14.819 -0.270568 12.8802 0.811704L12.3151 1.1271C10.6239 2.31737 9.59961 4.26472 9.59961 6.36025V13.0461L12.8802 11.2149C14.819 10.1326 17.1802 10.1326 19.119 11.2149L28.7191 16.5737C30.6984 17.6786 31.9421 19.7456 31.9977 22.0039C31.999 21.9514 31.9996 21.8987 31.9996 21.8459V11.7588C31.9996 9.4392 30.7445 7.30115 28.7191 6.17053Z" fill="url(#paint5_linear_857_143948)"/>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="34px" height="34px" viewBox="0 0 34 34" version="1.1">
<defs>
<radialGradient id="paint0_radial_857_143948" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(13.5054 9.8837) rotate(112.544) scale(23.4519 19.3067)">
<stop offset="0.206732" stop-color="#4995D0"/>
<stop offset="0.875628" stop-color="#0078D4"/>
<radialGradient id="radial0" gradientUnits="userSpaceOnUse" cx="0" cy="0" fx="0" fy="0" r="1" gradientTransform="matrix(-7.787324,-9.624098,9.028449,-7.305356,26.915817,14.703725)">
<stop offset="0.0955758" style="stop-color:rgb(0%,47.058824%,83.137255%);stop-opacity:1;"/>
<stop offset="0.715277" style="stop-color:rgb(4.705882%,43.921569%,60.784314%);stop-opacity:1;"/>
<stop offset="1" style="stop-color:rgb(3.921569%,31.372549%,47.45098%);stop-opacity:1;"/>
</radialGradient>
<linearGradient id="paint1_linear_857_143948" x1="11.0625" y1="26.6174" x2="9.78695" y2="24.2436" gradientUnits="userSpaceOnUse">
<stop offset="0.9999" stop-color="#0078D4"/>
<stop offset="1" stop-color="#0078D4" stop-opacity="0"/>
<radialGradient id="radial1" gradientUnits="userSpaceOnUse" cx="0" cy="0" fx="0" fy="0" r="1" gradientTransform="matrix(7.955722,8.152531,-8.011265,7.817866,7.897393,23.013892)">
<stop offset="0" style="stop-color:rgb(0%,56.862745%,92.156863%);stop-opacity:1;"/>
<stop offset="0.523516" style="stop-color:rgb(15.294118%,39.215686%,90.588235%);stop-opacity:1;"/>
<stop offset="0.923392" style="stop-color:rgb(2.352941%,21.176471%,76.470588%);stop-opacity:1;"/>
</radialGradient>
<linearGradient id="linear0" gradientUnits="userSpaceOnUse" x1="5.16831" y1="2" x2="7.75605" y2="17.2359" gradientTransform="matrix(1.416667,0,0,1.416667,0,0)">
<stop offset="0.289817" style="stop-color:rgb(0%,64.705882%,85.098039%);stop-opacity:1;"/>
<stop offset="0.662336" style="stop-color:rgb(12.941176%,79.215686%,69.803922%);stop-opacity:1;"/>
<stop offset="0.950002" style="stop-color:rgb(41.568627%,86.27451%,56.470588%);stop-opacity:1;"/>
</linearGradient>
<radialGradient id="paint2_radial_857_143948" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(6.899 24.5214) rotate(-3.9995) scale(24.6197 15.4594)">
<stop offset="0.140029" stop-color="#80C8FF"/>
<stop offset="0.952721" stop-color="#0078D4"/>
</radialGradient>
<linearGradient id="paint3_linear_857_143948" x1="26.8932" y1="15.5508" x2="25.5491" y2="17.8921" gradientUnits="userSpaceOnUse">
<stop offset="0.9999" stop-color="#3F8AC3"/>
<stop offset="1" stop-color="#8C66BA" stop-opacity="0"/>
<linearGradient id="linear1" gradientUnits="userSpaceOnUse" x1="7.25046" y1="2" x2="7.87502" y2="16.4401" gradientTransform="matrix(1.416667,0,0,1.416667,0,0)">
<stop offset="0" style="stop-color:rgb(6.27451%,78.823529%,92.54902%);stop-opacity:1;"/>
<stop offset="0.166667" style="stop-color:rgb(0.392157%,68.235294%,89.411765%);stop-opacity:0;"/>
</linearGradient>
<radialGradient id="paint4_radial_857_143948" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(26.632 16.3878) rotate(-138.33) scale(22.8015 13.0047)">
<stop stop-color="#7BC6FF"/>
<stop offset="0.839255" stop-color="#0078D4"/>
<radialGradient id="radial2" gradientUnits="userSpaceOnUse" cx="0" cy="0" fx="0" fy="0" r="1" gradientTransform="matrix(-9.930588,25.254205,-30.30117,-11.915182,29.269325,8.70791)">
<stop offset="0.154405" style="stop-color:rgb(15.294118%,44.313725%,84.705882%);stop-opacity:1;"/>
<stop offset="0.678875" style="stop-color:rgb(7.843137%,69.411765%,100%);stop-opacity:1;"/>
<stop offset="0.931138" style="stop-color:rgb(8.627451%,74.901961%,87.45098%);stop-opacity:1;"/>
</radialGradient>
<linearGradient id="paint5_linear_857_143948" x1="9.59961" y1="8.71337" x2="12.2131" y2="8.71337" gradientUnits="userSpaceOnUse">
<stop offset="0.9999" stop-color="#0078D4"/>
<stop offset="1" stop-color="#436DCD" stop-opacity="0"/>
<linearGradient id="linear2" gradientUnits="userSpaceOnUse" x1="21.2403" y1="7.01008" x2="20.306" y2="12.5802" gradientTransform="matrix(1.416667,0,0,1.416667,0,0)">
<stop offset="0.0581535" style="stop-color:rgb(7.843137%,69.411765%,100%);stop-opacity:1;"/>
<stop offset="0.708063" style="stop-color:rgb(16.078431%,46.27451%,85.882353%);stop-opacity:0;"/>
</linearGradient>
</defs>
<g id="surface1">
<path style=" stroke:none;fill-rule:nonzero;fill:url(#radial0);" d="M 24.1875 5.1875 C 23.777344 3.792969 22.496094 2.832031 21.039062 2.832031 L 20.078125 2.832031 C 18.496094 2.832031 17.140625 3.960938 16.855469 5.519531 L 15.175781 14.625 L 15.628906 13.066406 C 16.039062 11.664062 17.320312 10.703125 18.777344 10.703125 L 24.335938 10.703125 L 26.667969 11.613281 L 28.917969 10.703125 L 28.261719 10.703125 C 26.804688 10.703125 25.523438 9.746094 25.113281 8.347656 Z M 24.1875 5.1875 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:url(#radial1);" d="M 10.152344 28.796875 C 10.558594 30.199219 11.839844 31.167969 13.300781 31.167969 L 15.359375 31.167969 C 17.128906 31.167969 18.578125 29.765625 18.636719 27.996094 L 18.941406 19.011719 L 18.371094 20.945312 C 17.960938 22.339844 16.679688 23.296875 15.226562 23.296875 L 9.613281 23.296875 L 7.613281 22.210938 L 5.449219 23.296875 L 6.09375 23.296875 C 7.554688 23.296875 8.839844 24.261719 9.246094 25.664062 Z M 10.152344 28.796875 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:url(#linear0);" d="M 20.898438 2.832031 L 9.535156 2.832031 C 6.289062 2.832031 4.339844 7.121094 3.042969 11.410156 C 1.503906 16.492188 -0.507812 23.289062 5.316406 23.289062 L 10.222656 23.289062 C 11.6875 23.289062 12.972656 22.320312 13.375 20.910156 C 14.230469 17.929688 15.722656 12.722656 16.898438 8.761719 C 17.496094 6.75 17.992188 5.019531 18.753906 3.941406 C 19.183594 3.339844 19.894531 2.832031 20.898438 2.832031 Z M 20.898438 2.832031 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:url(#linear1);" d="M 20.898438 2.832031 L 9.535156 2.832031 C 6.289062 2.832031 4.339844 7.121094 3.042969 11.410156 C 1.503906 16.492188 -0.507812 23.289062 5.316406 23.289062 L 10.222656 23.289062 C 11.6875 23.289062 12.972656 22.320312 13.375 20.910156 C 14.230469 17.929688 15.722656 12.722656 16.898438 8.761719 C 17.496094 6.75 17.992188 5.019531 18.753906 3.941406 C 19.183594 3.339844 19.894531 2.832031 20.898438 2.832031 Z M 20.898438 2.832031 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:url(#radial2);" d="M 13.101562 31.167969 L 24.464844 31.167969 C 27.710938 31.167969 29.660156 26.878906 30.957031 22.589844 C 32.496094 17.507812 34.507812 10.710938 28.6875 10.710938 L 23.78125 10.710938 C 22.3125 10.710938 21.027344 11.679688 20.625 13.089844 C 19.769531 16.074219 18.277344 21.277344 17.101562 25.238281 C 16.503906 27.253906 16.007812 28.980469 15.246094 30.058594 C 14.816406 30.660156 14.105469 31.167969 13.101562 31.167969 Z M 13.101562 31.167969 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:url(#linear2);" d="M 13.101562 31.167969 L 24.464844 31.167969 C 27.710938 31.167969 29.660156 26.878906 30.957031 22.589844 C 32.496094 17.507812 34.507812 10.710938 28.6875 10.710938 L 23.78125 10.710938 C 22.3125 10.710938 21.027344 11.679688 20.625 13.089844 C 19.769531 16.074219 18.277344 21.277344 17.101562 25.238281 C 16.503906 27.253906 16.007812 28.980469 15.246094 30.058594 C 14.816406 30.660156 14.105469 31.167969 13.101562 31.167969 Z M 13.101562 31.167969 "/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

@@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 0C8.73438 0 9.44271 0.0963542 10.125 0.289062C10.8073 0.476562 11.4427 0.744792 12.0312 1.09375C12.625 1.44271 13.1641 1.86198 13.6484 2.35156C14.138 2.83594 14.5573 3.375 14.9062 3.96875C15.2552 4.55729 15.5234 5.19271 15.7109 5.875C15.9036 6.55729 16 7.26562 16 8C16 8.73438 15.9036 9.44271 15.7109 10.125C15.5234 10.8073 15.2552 11.4453 14.9062 12.0391C14.5573 12.6276 14.138 13.1667 13.6484 13.6562C13.1641 14.1406 12.625 14.5573 12.0312 14.9062C11.4427 15.2552 10.8073 15.526 10.125 15.7188C9.44271 15.9062 8.73438 16 8 16C7.26562 16 6.55729 15.9062 5.875 15.7188C5.19271 15.526 4.55469 15.2552 3.96094 14.9062C3.3724 14.5573 2.83333 14.1406 2.34375 13.6562C1.85938 13.1667 1.44271 12.6276 1.09375 12.0391C0.744792 11.4453 0.473958 10.8073 0.28125 10.125C0.09375 9.44271 0 8.73438 0 8C0 7.26562 0.09375 6.55729 0.28125 5.875C0.473958 5.19271 0.744792 4.55729 1.09375 3.96875C1.44271 3.375 1.85938 2.83594 2.34375 2.35156C2.83333 1.86198 3.3724 1.44271 3.96094 1.09375C4.55469 0.744792 5.19271 0.476562 5.875 0.289062C6.55729 0.0963542 7.26562 0 8 0ZM8 15C8.64583 15 9.26562 14.9167 9.85938 14.75C10.4583 14.5833 11.0156 14.349 11.5312 14.0469C12.0521 13.7396 12.5234 13.375 12.9453 12.9531C13.3724 12.526 13.737 12.0547 14.0391 11.5391C14.3464 11.0182 14.5833 10.4609 14.75 9.86719C14.9167 9.26823 15 8.64583 15 8C15 7.35417 14.9167 6.73438 14.75 6.14062C14.5833 5.54167 14.3464 4.98438 14.0391 4.46875C13.737 3.94792 13.3724 3.47656 12.9453 3.05469C12.5234 2.6276 12.0521 2.26302 11.5312 1.96094C11.0156 1.65365 10.4583 1.41667 9.85938 1.25C9.26562 1.08333 8.64583 1 8 1C7.35417 1 6.73177 1.08333 6.13281 1.25C5.53906 1.41667 4.98177 1.65365 4.46094 1.96094C3.94531 2.26302 3.47396 2.6276 3.04688 3.05469C2.625 3.47656 2.26042 3.94792 1.95312 4.46875C1.65104 4.98438 1.41667 5.54167 1.25 6.14062C1.08333 6.73438 1 7.35417 1 8C1 8.64583 1.08333 9.26823 1.25 9.86719C1.41667 10.4609 1.65104 11.0182 1.95312 11.5391C2.26042 12.0547 2.625 12.526 3.04688 12.9531C3.47396 13.375 3.94531 13.7396 4.46094 14.0469C4.98177 14.349 5.53906 14.5833 6.13281 14.75C6.73177 14.9167 7.35417 15 8 15ZM11.4609 5.24219L8.71094 8L11.4609 10.7578L10.7578 11.4609L8 8.71094L5.24219 11.4609L4.53906 10.7578L7.28906 8L4.53906 5.24219L5.24219 4.53906L8 7.28906L10.7578 4.53906L11.4609 5.24219Z" fill="#E00B1C"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 B

View File

@@ -147,6 +147,7 @@
// CommandBar
@CommandBarButtonHeight: 40px;
@FabricCommandBarButtonHeight: 34px;
/**********************************************************************************
Portal Consts
@@ -164,7 +165,7 @@
@FabricFont: "Segoe UI", "Segoe UI Web (West European)", "Segoe UI", -apple-system, BlinkMacSystemFont, Roboto, "Helvetica Neue", sans-serif;
@FabricBoxBorderRadius: 8px;
@FabricBoxBorderShadow: 0 0 2px rgba(0, 0, 0, 0.12), 0 2px 4px rgba(0, 0, 0, 0.14);
@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;
@FabricAccentMediumHigh: #0c695a;

View File

@@ -1179,16 +1179,16 @@ menuQuickStart {
}
}
.gridRowSelected {
#tbodycontent tr.gridRowSelected {
.active();
}
.gridRowSelected:hover {
#tbodycontent tr.gridRowSelected:hover {
cursor: default;
.hover();
}
.gridRowHighlighted {
#tbodycontent tr.gridRowHighlighted {
border-style: dotted;
border-width: 2px;
}
@@ -2576,9 +2576,10 @@ a:link {
.querydropdown.placeholderVisible {
font-style: italic;
}
.querydropdown.placeholderVisible::placeholder { /* Chrome, Firefox, Opera, Safari 10.1+ */
.querydropdown.placeholderVisible::placeholder {
/* Chrome, Firefox, Opera, Safari 10.1+ */
color: #767474;
opacity: 1;
opacity: 1;
}
.querydropdown:hover {
@@ -2648,7 +2649,7 @@ a:link {
.nav-tabs > li.active > .tabNavContentContainer > .tab_Content > .tabNavText {
font-weight: bolder;
border-bottom: 2px solid rgba(0,120,212,1);
border-bottom: 2px solid rgba(0, 120, 212, 1);
}
.nav-tabs > li.active:focus > .tabNavContentContainer {
@@ -2896,9 +2897,21 @@ a:link {
padding-left: 8px;
}
.settingsSectionInlineCheckbox {
display: flex;
flex-direction: row-reverse;
justify-content: flex-end;
align-items: center;
gap: 5px;
}
.settingsSectionLabel {
margin-bottom: @DefaultSpace;
margin-right: 5px;
.panelInfoIcon {
margin-left: 5px;
}
}
.pageOptionsPart {
@@ -3096,4 +3109,3 @@ a:link {
background: white;
height: 100%;
}

View File

@@ -25,33 +25,38 @@ a:focus {
}
.resourceTreeAndTabs {
border-radius: @FabricBoxBorderRadius;
border-radius: 0px;
box-shadow: @FabricBoxBorderShadow;
margin: @FabricBoxMargin;
margin-top: 4px;
margin-top: 0px;
margin-bottom: 0px;
background-color: #ffffff;
}
.tabsManagerContainer {
background-color: #fafafa
background-color: #ffffff
}
.nav-tabs-margin {
padding-top: 8px;
background-color: #fafafa
background-color: #ffffff
}
.commandBarContainer {
background-color: #ffffff;
border-bottom: none;
border-radius: @FabricBoxBorderRadius;
border-radius: @FabricBoxBorderRadius @FabricBoxBorderRadius 0px 0px;
box-shadow: @FabricBoxBorderShadow;
margin: @FabricBoxMargin;
margin-top: 0px;
margin-bottom: 0px;
padding-top: 2px;
padding: 0px;
height: 40px;
}
.dividerContainer {
padding: @SmallSpace 0px @SmallSpace 0px;
height: @FabricCommandBarButtonHeight;
.flex-display();
span {
@@ -158,9 +163,10 @@ a:focus {
.dataExplorerErrorConsoleContainer {
border-radius: @FabricBoxBorderRadius;
border-radius: 0px 0px @FabricBoxBorderRadius @FabricBoxBorderRadius;
box-shadow: @FabricBoxBorderShadow;
margin: @FabricBoxMargin;
margin-top: 0px;
width: auto;
align-self: auto;
}

53043
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -5,19 +5,19 @@
"main": "index.js",
"dependencies": {
"@azure/arm-cosmosdb": "9.1.0",
"@azure/cosmos": "3.16.2",
"@azure/cosmos": "4.0.1-beta.2",
"@azure/cosmos-language-service": "0.0.5",
"@azure/identity": "1.2.1",
"@azure/ms-rest-nodeauth": "3.0.7",
"@azure/msal-browser": "2.14.2",
"@babel/plugin-proposal-class-properties": "7.12.1",
"@babel/plugin-proposal-decorators": "7.12.12",
"@fluentui/react": "8.14.3",
"@fluentui/react-components": "9.32.1",
"@fluentui/react": "8.112.1",
"@fluentui/react-components": "9.34.0",
"@jupyterlab/services": "6.0.2",
"@jupyterlab/terminal": "3.0.3",
"@microsoft/applicationinsights-web": "2.6.1",
"@nteract/commutable": "7.4.2",
"@nteract/commutable": "7.5.1",
"@nteract/connected-components": "6.8.2",
"@nteract/core": "15.1.0",
"@nteract/data-explorer": "8.0.3",
@@ -49,20 +49,20 @@
"applicationinsights": "1.8.0",
"bootstrap": "3.4.1",
"canvas": "file:./canvas",
"clean-webpack-plugin": "3.0.0",
"clean-webpack-plugin": "4.0.0",
"clipboard-copy": "4.0.1",
"copy-webpack-plugin": "9.0.1",
"copy-webpack-plugin": "11.0.0",
"crossroads": "0.12.2",
"css-element-queries": "1.1.1",
"d3": "6.1.1",
"datatables.net-colreorder-dt": "1.5.1",
"datatables.net-dt": "1.10.19",
"datatables.net-colreorder-dt": "1.7.0",
"datatables.net-dt": "1.13.8",
"date-fns": "1.29.0",
"dayjs": "1.8.19",
"dom-to-image": "2.6.0",
"dotenv": "8.2.0",
"eslint-plugin-jest": "23.13.2",
"eslint-plugin-react": "7.20.0",
"eslint-plugin-jest": "27.4.2",
"eslint-plugin-react": "7.33.2",
"hasher": "1.2.0",
"html2canvas": "1.0.0-rc.5",
"i18next": "19.8.4",
@@ -71,14 +71,15 @@
"iframe-resizer-react": "1.1.0",
"immutable": "4.0.0-rc.12",
"is-ci": "2.0.0",
"jquery": "3.5.1",
"jquery-typeahead": "2.10.6",
"jquery-ui-dist": "1.12.1",
"jquery": "3.7.1",
"jquery-typeahead": "2.11.1",
"jquery-ui-dist": "1.13.2",
"knockout": "3.5.1",
"mkdirp": "1.0.4",
"monaco-editor": "0.18.1",
"monaco-editor": "0.44.0",
"ms": "2.1.3",
"p-retry": "4.2.0",
"patch-package": "8.0.0",
"p-retry": "4.6.2",
"plotly.js-cartesian-dist-min": "1.52.3",
"post-robot": "10.0.42",
"q": "1.5.1",
@@ -86,21 +87,20 @@
"react-animate-height": "2.0.8",
"react-dnd": "14.0.2",
"react-dnd-html5-backend": "14.0.0",
"react-dom": "16.13.1",
"react-dom": "16.14.0",
"react-hotkeys": "2.0.0",
"react-i18next": "11.8.5",
"react-notification-system": "0.2.17",
"react-redux": "7.1.3",
"react-splitter-layout": "4.0.0",
"react-string-format": "1.0.1",
"react-youtube": "9.0.1",
"redux": "4.0.4",
"reflect-metadata": "0.1.13",
"rx-jupyter": "5.5.12",
"rxjs": "6.6.3",
"sanitize-html": "2.3.3",
"styled-components": "4.3.2",
"styled-components": "5.0.1",
"swr": "0.4.0",
"terser-webpack-plugin": "5.1.4",
"terser-webpack-plugin": "5.3.9",
"underscore": "1.9.1",
"utility-types": "3.10.0",
"zustand": "3.5.0"
@@ -115,11 +115,14 @@
"@types/codemirror": "0.0.56",
"@types/crossroads": "0.0.30",
"@types/d3": "5.9.2",
"@types/datatables.net": "1.10.28",
"@types/datatables.net-colreorder": "1.4.5",
"@types/dom-to-image": "2.6.2",
"@types/enzyme": "3.10.7",
"@types/enzyme-adapter-react-16": "1.0.6",
"@types/hasher": "0.0.31",
"@types/jest": "26.0.20",
"@types/jquery": "3.5.29",
"@types/node": "12.11.1",
"@types/post-robot": "10.0.1",
"@types/q": "1.5.1",
@@ -133,61 +136,62 @@
"@types/styled-components": "5.1.1",
"@types/underscore": "1.7.36",
"@types/youtube-player": "5.5.6",
"@typescript-eslint/eslint-plugin": "4.22.0",
"@typescript-eslint/parser": "4.22.0",
"@webpack-cli/serve": "1.5.2",
"@typescript-eslint/eslint-plugin": "6.7.4",
"@typescript-eslint/parser": "6.7.4",
"@webpack-cli/serve": "2.0.5",
"babel-jest": "24.9.0",
"babel-loader": "8.1.0",
"buffer": "5.1.0",
"case-sensitive-paths-webpack-plugin": "2.3.0",
"case-sensitive-paths-webpack-plugin": "2.4.0",
"create-file-webpack": "1.0.2",
"css-loader": "1.0.0",
"css-loader": "6.8.1",
"enzyme": "3.11.0",
"enzyme-adapter-react-16": "1.15.5",
"enzyme-to-json": "3.6.1",
"eslint": "7.8.1",
"eslint": "8.50.0",
"eslint-cli": "1.1.1",
"eslint-plugin-no-null": "1.0.2",
"eslint-plugin-prefer-arrow": "1.2.2",
"eslint-plugin-react-hooks": "4.2.0",
"eslint-plugin-prefer-arrow": "1.2.3",
"eslint-plugin-react-hooks": "4.6.0",
"expect-playwright": "0.3.3",
"fast-glob": "3.2.5",
"file-loader": "2.0.0",
"fs-extra": "7.0.0",
"html-inline-css-webpack-plugin": "1.11.0",
"html-inline-css-webpack-plugin": "1.11.2",
"html-loader": "0.5.5",
"html-loader-jest": "0.2.1",
"html-webpack-plugin": "5.3.2",
"html-webpack-plugin": "5.5.3",
"jest": "26.6.3",
"jest-canvas-mock": "2.3.1",
"jest-playwright-preset": "1.5.1",
"jest-react-hooks-shallow": "1.5.1",
"jest-trx-results-processor": "0.0.7",
"less": "3.8.1",
"less-loader": "4.1.0",
"less-loader": "11.1.3",
"less-vars-loader": "1.1.0",
"mini-css-extract-plugin": "2.1.0",
"monaco-editor-webpack-plugin": "1.7.0",
"monaco-editor-webpack-plugin": "7.1.0",
"node-fetch": "2.6.1",
"playwright": "1.13.0",
"prettier": "3.0.3",
"process": "0.11.10",
"querystring-es3": "0.2.1",
"raw-loader": "0.5.1",
"react-dev-utils": "11.0.4",
"rimraf": "3.0.0",
"sinon": "3.2.1",
"style-loader": "0.23.0",
"ts-loader": "9.2.4",
"typedoc": "0.20.36",
"typescript": "4.3.4",
"url-loader": "1.1.1",
"typedoc": "0.21.5",
"typescript": "4.3.5",
"url-loader": "4.1.1",
"wait-on": "4.0.2",
"webpack": "5.47.0",
"webpack-bundle-analyzer": "4.4.2",
"webpack-cli": "4.7.2",
"webpack-dev-server": "3.11.2"
"webpack": "5.88.2",
"webpack-bundle-analyzer": "4.9.1",
"webpack-cli": "5.1.4",
"webpack-dev-server": "4.15.1"
},
"scripts": {
"postinstall": "patch-package",
"start": "webpack serve --mode development",
"dev": "echo \"WARNING: npm run dev has been deprecated\" && npm run build",
"build:dataExplorer:ci": "npm run build:ci",

View File

@@ -0,0 +1,22 @@
diff --git a/node_modules/datatables.net-colreorder/types/types.d.ts b/node_modules/datatables.net-colreorder/types/types.d.ts
index e5dc283..1930c2b 100644
--- a/node_modules/datatables.net-colreorder/types/types.d.ts
+++ b/node_modules/datatables.net-colreorder/types/types.d.ts
@@ -7,7 +7,7 @@
/// <reference types="jquery" />
-import DataTables, {Api} from 'datatables.net';
+import DataTables, { Api } from 'datatables.net';
export default DataTables;
@@ -40,6 +40,8 @@ declare module 'datatables.net' {
/**
* Create a new ColReorder instance for the target DataTable
*/
+ // Ignore this error: error TS7013: Construct signature, which lacks return-type annotation, implicitly has an 'any' return type.
+ // @ts-ignore
new (dt: Api<any>, settings: boolean | ConfigColReorder);
/**

View File

@@ -145,6 +145,9 @@ export class Queries {
public static QueryEditorMinHeightRatio: number = 0.1;
public static QueryEditorMaxHeightRatio: number = 0.4;
public static readonly DefaultMaxDegreeOfParallelism = 6;
public static readonly DefaultRetryAttempts = 9;
public static readonly DefaultRetryIntervalInMs = 0;
public static readonly DefaultMaxWaitTimeInSeconds = 30;
}
export class SavedQueries {
@@ -171,6 +174,7 @@ export class Areas {
public static Tab: string = "Tab";
public static ShareDialog: string = "Share Access Dialog";
public static Notebook: string = "Notebook";
public static Copilot: string = "Copilot";
}
export class HttpHeaders {
@@ -207,6 +211,10 @@ export class HttpHeaders {
public static migrateOfferToAutopilot: string = "x-ms-cosmos-migrate-offer-to-autopilot";
}
export class ContentType {
public static applicationJson: string = "application/json";
}
export class ApiType {
// Mapped to hexadecimal values in the backend
public static readonly MongoDB: number = 1;
@@ -427,6 +435,22 @@ export class JunoEndpoints {
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 {
public static readonly High = "high";
public static readonly Low = "low";

View File

@@ -125,7 +125,7 @@ describe("requestPlugin", () => {
const headers = {};
const endpoint = "https://docs.azure.com";
const path = "/dbs/foo";
requestPlugin({ endpoint, headers, path } as any, next as any);
requestPlugin({ endpoint, headers, path } as any, undefined, next as any);
expect(next.mock.calls[0][0]).toMatchSnapshot();
});
});
@@ -137,7 +137,7 @@ describe("requestPlugin", () => {
const headers = {};
const endpoint = "";
const path = "/dbs/foo";
requestPlugin({ endpoint, headers, path } as any, next as any);
requestPlugin({ endpoint, headers, path } as any, undefined, next as any);
expect(next.mock.calls[0][0]).toMatchSnapshot();
});
});

View File

@@ -1,12 +1,17 @@
import * as Cosmos from "@azure/cosmos";
import { configContext, Platform } from "../ConfigContext";
import { sendCachedDataMessage } from "Common/MessageHandler";
import { getAuthorizationTokenUsingResourceTokens } from "Common/getAuthorizationTokenUsingResourceTokens";
import { AuthorizationToken, MessageTypes } from "Contracts/MessageTypes";
import { checkDatabaseResourceTokensValidity } from "Platform/Fabric/FabricUtil";
import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
import { AuthType } from "../AuthType";
import { PriorityLevel } from "../Common/Constants";
import { Platform, configContext } from "../ConfigContext";
import { userContext } from "../UserContext";
import { logConsoleError } from "../Utils/NotificationConsoleUtils";
import * as PriorityBasedExecutionUtils from "../Utils/PriorityBasedExecutionUtils";
import { EmulatorMasterKey, HttpHeaders } from "./Constants";
import { getErrorMessage } from "./ErrorHandlingUtils";
import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
import { PriorityLevel } from "../Common/Constants";
import * as PriorityBasedExecutionUtils from "../Utils/PriorityBasedExecutionUtils";
const _global = typeof self === "undefined" ? window : self;
@@ -25,6 +30,39 @@ export const tokenProvider = async (requestInfo: Cosmos.RequestInfo) => {
return decodeURIComponent(headers.authorization);
}
if (configContext.platform === Platform.Fabric) {
switch (requestInfo.resourceType) {
case Cosmos.ResourceType.conflicts:
case Cosmos.ResourceType.container:
case Cosmos.ResourceType.sproc:
case Cosmos.ResourceType.udf:
case Cosmos.ResourceType.trigger:
case Cosmos.ResourceType.item:
case Cosmos.ResourceType.pkranges:
// User resource tokens
// TODO userContext.fabricContext.databaseConnectionInfo can be undefined
headers[HttpHeaders.msDate] = new Date().toUTCString();
const resourceTokens = userContext.fabricContext.databaseConnectionInfo.resourceTokens;
checkDatabaseResourceTokensValidity(userContext.fabricContext.databaseConnectionInfo.resourceTokensTimestamp);
return getAuthorizationTokenUsingResourceTokens(resourceTokens, requestInfo.path, requestInfo.resourceId);
case Cosmos.ResourceType.none:
case Cosmos.ResourceType.database:
case Cosmos.ResourceType.offer:
case Cosmos.ResourceType.user:
case Cosmos.ResourceType.permission:
// User master tokens
const authorizationToken = await sendCachedDataMessage<AuthorizationToken>(
MessageTypes.GetAuthorizationToken,
[requestInfo],
userContext.fabricContext.connectionId,
);
console.log("Response from Fabric: ", authorizationToken);
headers[HttpHeaders.msDate] = authorizationToken.XDate;
return decodeURIComponent(authorizationToken.PrimaryReadWriteToken);
}
}
if (userContext.masterKey) {
// TODO This SDK method mutates the headers object. Find a better one or fix the SDK.
await Cosmos.setAuthorizationTokenHeaderUsingMasterKey(verb, resourceId, resourceType, headers, EmulatorMasterKey);
@@ -40,7 +78,7 @@ export const tokenProvider = async (requestInfo: Cosmos.RequestInfo) => {
return decodeURIComponent(result.PrimaryReadWriteToken);
};
export const requestPlugin: Cosmos.Plugin<any> = async (requestContext, next) => {
export const requestPlugin: Cosmos.Plugin<any> = async (requestContext, diagnosticNode, next) => {
requestContext.endpoint = new URL(configContext.PROXY_PATH, window.location.href).href;
requestContext.headers["x-ms-proxy-target"] = endpoint();
return next(requestContext);
@@ -55,7 +93,11 @@ export const endpoint = () => {
return userContext.endpoint || userContext?.databaseAccount?.properties?.documentEndpoint;
};
export async function getTokenFromAuthService(verb: string, resourceType: string, resourceId?: string): Promise<any> {
export async function getTokenFromAuthService(
verb: string,
resourceType: string,
resourceId?: string,
): Promise<AuthorizationToken> {
try {
const host = configContext.BACKEND_ENDPOINT;
const response = await _global.fetch(host + "/api/guest/runtimeproxy/authorizationTokens", {
@@ -94,22 +136,38 @@ export function client(): Cosmos.CosmosClient {
_defaultHeaders["x-ms-cosmos-sdk-supportedcapabilities"] =
SDKSupportedCapabilities.None | SDKSupportedCapabilities.PartitionMerge;
if (
userContext.authType === AuthType.ConnectionString ||
userContext.authType === AuthType.EncryptedToken ||
userContext.authType === AuthType.ResourceToken
) {
// Default to low priority. Needed for non-AAD-auth scenarios
// where we cannot use RP API, and thus, cannot detect whether priority
// based execution is enabled.
// The header will be ignored if priority based execution is disabled on the account.
_defaultHeaders["x-ms-cosmos-priority-level"] = PriorityLevel.Default;
}
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
key: userContext.masterKey,
tokenProvider,
connectionPolicy: {
enableEndpointDiscovery: false,
},
userAgentSuffix: "Azure Portal",
defaultHeaders: _defaultHeaders,
connectionPolicy: {
retryOptions: {
maxRetryAttemptCount: LocalStorageUtility.getEntryNumber(StorageKey.RetryAttempts),
fixedRetryIntervalInMilliseconds: LocalStorageUtility.getEntryNumber(StorageKey.RetryInterval),
maxWaitTimeInSeconds: LocalStorageUtility.getEntryNumber(StorageKey.MaxWaitTimeInSeconds),
},
},
};
if (configContext.PROXY_PATH !== undefined) {
(options as any).plugins = [{ on: "request", plugin: requestPlugin }];
}
if (userContext.databaseAccount?.properties?.enablePriorityBasedExecution && userContext.apiType === "SQL") {
if (PriorityBasedExecutionUtils.isFeatureEnabled()) {
const plugins = (options as any).plugins || [];
plugins.push({ on: "request", plugin: PriorityBasedExecutionUtils.requestPlugin });
(options as any).plugins = plugins;

View File

@@ -51,6 +51,11 @@ const replaceKnownError = (errorMessage: string): string => {
return "Database throughput is not supported for internal subscriptions.";
} else if (errorMessage?.indexOf("Partition key paths must contain only valid") >= 0) {
return "Partition key paths must contain only valid characters and not contain a trailing slash or wildcard character.";
} else if (
errorMessage?.indexOf("The user aborted a request") >= 0 ||
errorMessage?.indexOf("The operation was aborted") >= 0
) {
return "User aborted query.";
}
return errorMessage;

View File

@@ -1,3 +1,4 @@
import { QueryOperationOptions } from "@azure/cosmos";
import { QueryResults } from "../Contracts/ViewModels";
interface QueryResponse {
@@ -10,13 +11,17 @@ interface QueryResponse {
}
export interface MinimalQueryIterator {
fetchNext: () => Promise<QueryResponse>;
fetchNext: (queryOperationOptions?: QueryOperationOptions) => Promise<QueryResponse>;
}
// Pick<QueryIterator<any>, "fetchNext">;
export function nextPage(documentsIterator: MinimalQueryIterator, firstItemIndex: number): Promise<QueryResults> {
return documentsIterator.fetchNext().then((response) => {
export function nextPage(
documentsIterator: MinimalQueryIterator,
firstItemIndex: number,
queryOperationOptions?: QueryOperationOptions,
): Promise<QueryResults> {
return documentsIterator.fetchNext(queryOperationOptions).then((response) => {
const documents = response.resources;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const headers = (response as any).headers || {}; // TODO this is a private key. Remove any

View File

@@ -1,7 +1,5 @@
jest.mock("./MessageHandler");
import { LogEntryLevel } from "../Contracts/Diagnostics";
import * as Logger from "./Logger";
import { MessageTypes } from "../Contracts/ExplorerContracts";
import { sendMessage } from "./MessageHandler";
describe("Logger", () => {
@@ -11,16 +9,16 @@ describe("Logger", () => {
it("should log info messages", () => {
Logger.logInfo("Test info", "DocDB");
expect(sendMessage).toBeCalled();
expect(sendMessage).toHaveBeenCalled();
});
it("should log error messages", () => {
Logger.logError("Test error", "DocDB");
expect(sendMessage).toBeCalled();
expect(sendMessage).toHaveBeenCalled();
});
it("should log warnings", () => {
Logger.logWarning("Test warning", "DocDB");
expect(sendMessage).toBeCalled();
expect(sendMessage).toHaveBeenCalled();
});
});

View File

@@ -22,20 +22,29 @@ export function handleCachedDataMessage(message: any): void {
if (messageContent.error != null) {
cachedDataPromise.deferred.reject(messageContent.error);
} else {
cachedDataPromise.deferred.resolve(JSON.parse(messageContent.data));
cachedDataPromise.deferred.resolve(messageContent.data);
}
runGarbageCollector();
}
/**
*
* @param messageType
* @param params
* @param scope Use this string to identify request Useful to distinguish response from different senders
* @param timeoutInMs
* @returns
*/
export function sendCachedDataMessage<TResponseDataModel>(
messageType: MessageTypes,
params: Object[],
scope?: string,
timeoutInMs?: number,
): Q.Promise<TResponseDataModel> {
let cachedDataPromise: CachedDataPromise<TResponseDataModel> = {
deferred: Q.defer<TResponseDataModel>(),
startTime: new Date(),
id: _.uniqueId(),
id: _.uniqueId(scope),
};
RequestMap[cachedDataPromise.id] = cachedDataPromise;
sendMessage({ type: messageType, params: params, id: cachedDataPromise.id });
@@ -47,6 +56,10 @@ export function sendCachedDataMessage<TResponseDataModel>(
);
}
/**
*
* @param data Overwrite the data property of the message
*/
export function sendMessage(data: any): void {
_sendMessage({
signature: "pcIframe",

View File

@@ -1,6 +1,10 @@
import { Constants as CosmosSDKConstants } from "@azure/cosmos";
import {
allowedMongoProxyEndpoints,
allowedMongoProxyEndpoints_ToBeDeprecated,
validateEndpoint,
} from "Utils/EndpointUtils";
import queryString from "querystring";
import { allowedMongoProxyEndpoints, validateEndpoint } from "Utils/EndpointValidation";
import { AuthType } from "../AuthType";
import { configContext } from "../ConfigContext";
import * as DataModels from "../Contracts/DataModels";
@@ -10,7 +14,7 @@ import DocumentId from "../Explorer/Tree/DocumentId";
import { hasFlag } from "../Platform/Hosted/extractFeatures";
import { userContext } from "../UserContext";
import { logConsoleError } from "../Utils/NotificationConsoleUtils";
import { ApiType, HttpHeaders, HttpStatusCodes } from "./Constants";
import { ApiType, ContentType, HttpHeaders, HttpStatusCodes, MongoProxyEndpoints } from "./Constants";
import { MinimalQueryIterator } from "./IteratorUtilities";
import { sendMessage } from "./MessageHandler";
@@ -62,6 +66,73 @@ export function queryDocuments(
isResourceList: boolean,
query: string,
continuationToken?: string,
): Promise<QueryResponse> {
if (!useMongoProxyEndpoint("resourcelist")) {
return queryDocuments_ToBeDeprecated(databaseId, collection, isResourceList, query, continuationToken);
}
const { databaseAccount } = userContext;
const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint;
const params = {
databaseID: databaseId,
collectionID: collection.id(),
resourceUrl: `${resourceEndpoint}dbs/${databaseId}/colls/${collection.id()}/docs/`,
resourceID: collection.rid,
resourceType: "docs",
subscriptionID: userContext.subscriptionId,
resourceGroup: userContext.resourceGroup,
databaseAccountName: databaseAccount.name,
partitionKey:
collection && collection.partitionKey && !collection.partitionKey.systemKey
? collection.partitionKeyProperties?.[0]
: "",
query,
};
const endpoint = getFeatureEndpointOrDefault("resourcelist") || "";
const headers = {
...defaultHeaders,
...authHeaders(),
[CosmosSDKConstants.HttpHeaders.IsQuery]: "true",
[CosmosSDKConstants.HttpHeaders.PopulateQueryMetrics]: "true",
[CosmosSDKConstants.HttpHeaders.EnableScanInQuery]: "true",
[CosmosSDKConstants.HttpHeaders.EnableCrossPartitionQuery]: "true",
[CosmosSDKConstants.HttpHeaders.ParallelizeCrossPartitionQuery]: "true",
[HttpHeaders.contentType]: "application/query+json",
};
if (continuationToken) {
headers[CosmosSDKConstants.HttpHeaders.Continuation] = continuationToken;
}
const path = isResourceList ? "/resourcelist" : "";
return window
.fetch(`${endpoint}${path}`, {
method: "POST",
body: JSON.stringify(params),
headers,
})
.then(async (response) => {
if (response.ok) {
return {
continuationToken: response.headers.get(CosmosSDKConstants.HttpHeaders.Continuation),
documents: (await response.json()).Documents as DataModels.DocumentId[],
headers: response.headers,
};
}
await errorHandling(response, "querying documents", params);
return undefined;
});
}
function queryDocuments_ToBeDeprecated(
databaseId: string,
collection: Collection,
isResourceList: boolean,
query: string,
continuationToken?: string,
): Promise<QueryResponse> {
const { databaseAccount } = userContext;
const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint;
@@ -122,6 +193,54 @@ export function readDocument(
databaseId: string,
collection: Collection,
documentId: DocumentId,
): Promise<DataModels.DocumentId> {
if (!useMongoProxyEndpoint("readDocument")) {
return readDocument_ToBeDeprecated(databaseId, collection, documentId);
}
const { databaseAccount } = userContext;
const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint;
const idComponents = documentId.self.split("/");
const path = idComponents.slice(0, 4).join("/");
const rid = encodeURIComponent(idComponents[5]);
const params = {
databaseID: databaseId,
collectionID: collection.id(),
resourceUrl: `${resourceEndpoint}${path}/${rid}`,
resourceID: rid,
resourceType: "docs",
subscriptionID: userContext.subscriptionId,
resourceGroup: userContext.resourceGroup,
databaseAccountName: databaseAccount.name,
partitionKey:
documentId && documentId.partitionKey && !documentId.partitionKey.systemKey
? documentId.partitionKeyProperties?.[0]
: "",
};
const endpoint = getFeatureEndpointOrDefault("readDocument");
return window
.fetch(endpoint, {
method: "POST",
body: JSON.stringify(params),
headers: {
...defaultHeaders,
...authHeaders(),
[HttpHeaders.contentType]: ContentType.applicationJson,
},
})
.then(async (response) => {
if (response.ok) {
return response.json();
}
return await errorHandling(response, "reading document", params);
});
}
export function readDocument_ToBeDeprecated(
databaseId: string,
collection: Collection,
documentId: DocumentId,
): Promise<DataModels.DocumentId> {
const { databaseAccount } = userContext;
const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint;
@@ -169,6 +288,51 @@ export function createDocument(
collection: Collection,
partitionKeyProperty: string,
documentContent: unknown,
): Promise<DataModels.DocumentId> {
if (!useMongoProxyEndpoint("createDocument")) {
return createDocument_ToBeDeprecated(databaseId, collection, partitionKeyProperty, documentContent);
}
const { databaseAccount } = userContext;
const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint;
const params = {
databaseID: databaseId,
collectionID: collection.id(),
resourceUrl: `${resourceEndpoint}dbs/${databaseId}/colls/${collection.id()}/docs/`,
resourceID: collection.rid,
resourceType: "docs",
subscriptionID: userContext.subscriptionId,
resourceGroup: userContext.resourceGroup,
databaseAccountName: databaseAccount.name,
partitionKey:
collection && collection.partitionKey && !collection.partitionKey.systemKey ? partitionKeyProperty : "",
documentContent: JSON.stringify(documentContent),
};
const endpoint = getFeatureEndpointOrDefault("createDocument");
return window
.fetch(`${endpoint}/createDocument`, {
method: "POST",
body: JSON.stringify(params),
headers: {
...defaultHeaders,
...authHeaders(),
[HttpHeaders.contentType]: ContentType.applicationJson,
},
})
.then(async (response) => {
if (response.ok) {
return response.json();
}
return await errorHandling(response, "creating document", params);
});
}
export function createDocument_ToBeDeprecated(
databaseId: string,
collection: Collection,
partitionKeyProperty: string,
documentContent: unknown,
): Promise<DataModels.DocumentId> {
const { databaseAccount } = userContext;
const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint;
@@ -208,6 +372,56 @@ export function updateDocument(
collection: Collection,
documentId: DocumentId,
documentContent: string,
): Promise<DataModels.DocumentId> {
if (!useMongoProxyEndpoint("updateDocument")) {
return updateDocument_ToBeDeprecated(databaseId, collection, documentId, documentContent);
}
const { databaseAccount } = userContext;
const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint;
const idComponents = documentId.self.split("/");
const path = idComponents.slice(0, 5).join("/");
const rid = encodeURIComponent(idComponents[5]);
const params = {
databaseID: databaseId,
collectionID: collection.id(),
resourceUrl: `${resourceEndpoint}${path}/${rid}`,
resourceID: rid,
resourceType: "docs",
subscriptionID: userContext.subscriptionId,
resourceGroup: userContext.resourceGroup,
databaseAccountName: databaseAccount.name,
partitionKey:
documentId && documentId.partitionKey && !documentId.partitionKey.systemKey
? documentId.partitionKeyProperties?.[0]
: "",
documentContent,
};
const endpoint = getFeatureEndpointOrDefault("updateDocument");
return window
.fetch(endpoint, {
method: "PUT",
body: JSON.stringify(params),
headers: {
...defaultHeaders,
...authHeaders(),
[HttpHeaders.contentType]: ContentType.applicationJson,
[CosmosSDKConstants.HttpHeaders.PartitionKey]: JSON.stringify(documentId.partitionKeyHeader()),
},
})
.then(async (response) => {
if (response.ok) {
return response.json();
}
return await errorHandling(response, "updating document", params);
});
}
export function updateDocument_ToBeDeprecated(
databaseId: string,
collection: Collection,
documentId: DocumentId,
documentContent: string,
): Promise<DataModels.DocumentId> {
const { databaseAccount } = userContext;
const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint;
@@ -237,7 +451,7 @@ export function updateDocument(
headers: {
...defaultHeaders,
...authHeaders(),
[HttpHeaders.contentType]: "application/json",
[HttpHeaders.contentType]: ContentType.applicationJson,
[CosmosSDKConstants.HttpHeaders.PartitionKey]: JSON.stringify(documentId.partitionKeyHeader()),
},
})
@@ -250,6 +464,53 @@ export function updateDocument(
}
export function deleteDocument(databaseId: string, collection: Collection, documentId: DocumentId): Promise<void> {
if (!useMongoProxyEndpoint("deleteDocument")) {
return deleteDocument_ToBeDeprecated(databaseId, collection, documentId);
}
const { databaseAccount } = userContext;
const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint;
const idComponents = documentId.self.split("/");
const path = idComponents.slice(0, 5).join("/");
const rid = encodeURIComponent(idComponents[5]);
const params = {
databaseID: databaseId,
collectionID: collection.id(),
resourceUrl: `${resourceEndpoint}${path}/${rid}`,
resourceID: rid,
resourceType: "docs",
subscriptionID: userContext.subscriptionId,
resourceGroup: userContext.resourceGroup,
databaseAccountName: databaseAccount.name,
partitionKey:
documentId && documentId.partitionKey && !documentId.partitionKey.systemKey
? documentId.partitionKeyProperties?.[0]
: "",
};
const endpoint = getFeatureEndpointOrDefault("deleteDocument");
return window
.fetch(endpoint, {
method: "DELETE",
body: JSON.stringify(params),
headers: {
...defaultHeaders,
...authHeaders(),
[HttpHeaders.contentType]: ContentType.applicationJson,
},
})
.then(async (response) => {
if (response.ok) {
return undefined;
}
return await errorHandling(response, "deleting document", params);
});
}
export function deleteDocument_ToBeDeprecated(
databaseId: string,
collection: Collection,
documentId: DocumentId,
): Promise<void> {
const { databaseAccount } = userContext;
const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint;
const idComponents = documentId.self.split("/");
@@ -277,7 +538,7 @@ export function deleteDocument(databaseId: string, collection: Collection, docum
headers: {
...defaultHeaders,
...authHeaders(),
[HttpHeaders.contentType]: "application/json",
[HttpHeaders.contentType]: ContentType.applicationJson,
[CosmosSDKConstants.HttpHeaders.PartitionKey]: JSON.stringify(documentId.partitionKeyHeader()),
},
})
@@ -291,6 +552,52 @@ export function deleteDocument(databaseId: string, collection: Collection, docum
export function createMongoCollectionWithProxy(
params: DataModels.CreateCollectionParams,
): Promise<DataModels.Collection> {
if (!useMongoProxyEndpoint("createCollectionWithProxy")) {
return createMongoCollectionWithProxy_ToBeDeprecated(params);
}
const { databaseAccount } = userContext;
const shardKey: string = params.partitionKey?.paths[0];
const createCollectionParams = {
databaseID: params.databaseId,
collectionID: params.collectionId,
resourceUrl: databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint,
resourceID: "",
resourceType: "colls",
subscriptionID: userContext.subscriptionId,
resourceGroup: userContext.resourceGroup,
databaseAccountName: databaseAccount.name,
partitionKey: shardKey,
isAutoscale: !!params.autoPilotMaxThroughput,
hasSharedThroughput: params.databaseLevelThroughput,
offerThroughput: params.autoPilotMaxThroughput || params.offerThroughput,
createDatabase: params.createNewDatabase,
isSharded: !!shardKey,
};
const endpoint = getFeatureEndpointOrDefault("createCollectionWithProxy");
return window
.fetch(`${endpoint}/createCollection`, {
method: "POST",
body: JSON.stringify(createCollectionParams),
headers: {
...defaultHeaders,
...authHeaders(),
[HttpHeaders.contentType]: ContentType.applicationJson,
},
})
.then(async (response) => {
if (response.ok) {
return response.json();
}
return await errorHandling(response, "creating collection", createCollectionParams);
});
}
export function createMongoCollectionWithProxy_ToBeDeprecated(
params: DataModels.CreateCollectionParams,
): Promise<DataModels.Collection> {
const { databaseAccount } = userContext;
const shardKey: string = params.partitionKey?.paths[0];
@@ -334,13 +641,20 @@ export function createMongoCollectionWithProxy(
return await errorHandling(response, "creating collection", mongoParams);
});
}
export function getFeatureEndpointOrDefault(feature: string): string {
const endpoint =
hasFlag(userContext.features.mongoProxyAPIs, feature) &&
validateEndpoint(userContext.features.mongoProxyEndpoint, allowedMongoProxyEndpoints)
? userContext.features.mongoProxyEndpoint
: configContext.MONGO_BACKEND_ENDPOINT || configContext.BACKEND_ENDPOINT;
let endpoint;
if (useMongoProxyEndpoint(feature)) {
endpoint = configContext.MONGO_PROXY_ENDPOINT;
} else {
endpoint =
hasFlag(userContext.features.mongoProxyAPIs, feature) &&
validateEndpoint(userContext.features.mongoProxyEndpoint, [
...allowedMongoProxyEndpoints,
...allowedMongoProxyEndpoints_ToBeDeprecated,
])
? userContext.features.mongoProxyEndpoint
: configContext.MONGO_BACKEND_ENDPOINT || configContext.BACKEND_ENDPOINT;
}
return getEndpoint(endpoint);
}
@@ -349,7 +663,11 @@ export function getEndpoint(endpoint: string): string {
let url = endpoint + "/api/mongo/explorer";
if (userContext.authType === AuthType.EncryptedToken) {
url = url.replace("api/mongo", "api/guest/mongo");
if (endpoint === configContext.MONGO_PROXY_ENDPOINT) {
url = url.replace("api/mongo", "api/connectionstring/mongo");
} else {
url = url.replace("api/mongo", "api/guest/mongo");
}
}
return url;
}
@@ -370,3 +688,16 @@ async function errorHandling(response: Response, action: string, params: unknown
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}`;
}
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

@@ -1,3 +1,4 @@
import { isServerlessAccount } from "Utils/CapabilityUtils";
import * as _ from "underscore";
import * as DataModels from "../Contracts/DataModels";
import * as ViewModels from "../Contracts/ViewModels";
@@ -8,11 +9,11 @@ import { useDatabases } from "../Explorer/useDatabases";
import { userContext } from "../UserContext";
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
import { BackendDefaults, HttpStatusCodes, SavedQueries } from "./Constants";
import { handleError } from "./ErrorHandlingUtils";
import { createCollection } from "./dataAccess/createCollection";
import { createDocument } from "./dataAccess/createDocument";
import { deleteDocument } from "./dataAccess/deleteDocument";
import { queryDocuments } from "./dataAccess/queryDocuments";
import { handleError } from "./ErrorHandlingUtils";
export class QueriesClient {
private static readonly PartitionKey: DataModels.PartitionKey = {
@@ -32,25 +33,36 @@ export class QueriesClient {
}
const clearMessage = NotificationConsoleUtils.logConsoleProgress("Setting up account for saving queries");
return createCollection({
collectionId: SavedQueries.CollectionName,
createNewDatabase: true,
databaseId: SavedQueries.DatabaseName,
partitionKey: QueriesClient.PartitionKey,
offerThroughput: SavedQueries.OfferThroughput,
databaseLevelThroughput: false,
})
.then(
(collection: DataModels.Collection) => {
NotificationConsoleUtils.logConsoleInfo("Successfully set up account for saving queries");
return Promise.resolve(collection);
},
(error: any) => {
handleError(error, "setupQueriesCollection", "Failed to set up account for saving queries");
return Promise.reject(error);
},
)
.finally(() => clearMessage());
if (isServerlessAccount()) {
return createCollection({
collectionId: SavedQueries.CollectionName,
createNewDatabase: true,
databaseId: SavedQueries.DatabaseName,
partitionKey: QueriesClient.PartitionKey,
databaseLevelThroughput: false,
});
} else {
return createCollection({
collectionId: SavedQueries.CollectionName,
createNewDatabase: true,
databaseId: SavedQueries.DatabaseName,
partitionKey: QueriesClient.PartitionKey,
offerThroughput: SavedQueries.OfferThroughput,
databaseLevelThroughput: false,
})
.then(
(collection: DataModels.Collection) => {
NotificationConsoleUtils.logConsoleInfo("Successfully set up account for saving queries");
return Promise.resolve(collection);
},
(error: any) => {
handleError(error, "setupQueriesCollection", "Failed to set up account for saving queries");
return Promise.reject(error);
},
)
.finally(() => clearMessage());
}
}
public async saveQuery(query: DataModels.Query): Promise<void> {

View File

@@ -146,6 +146,7 @@ export const TableEntity: FunctionComponent<TableEntityProps> = ({
onClick={onEditEntity}
tabIndex={0}
onKeyPress={handleKeyPress}
role="button"
/>
</div>
</TooltipHost>
@@ -160,6 +161,7 @@ export const TableEntity: FunctionComponent<TableEntityProps> = ({
onClick={onDeleteEntity}
tabIndex={0}
onKeyPress={handleKeyPressdelete}
role="button"
/>
</TooltipHost>
)}

View File

@@ -0,0 +1,188 @@
import { ApiType, userContext } from "UserContext";
import * as NotificationConsoleUtils from "Utils/NotificationConsoleUtils";
import {
cancel,
create,
get,
listByDatabaseAccount,
} from "Utils/arm/generatedClients/dataTransferService/dataTransferJobs";
import {
CosmosCassandraDataTransferDataSourceSink,
CosmosMongoDataTransferDataSourceSink,
CosmosSqlDataTransferDataSourceSink,
CreateJobRequest,
DataTransferJobFeedResults,
DataTransferJobGetResults,
} from "Utils/arm/generatedClients/dataTransferService/types";
import { addToPolling, removeFromPolling, updateDataTransferJob, useDataTransferJobs } from "hooks/useDataTransferJobs";
import promiseRetry, { AbortError, FailedAttemptError } from "p-retry";
export interface DataTransferParams {
jobName: string;
apiType: ApiType;
subscriptionId: string;
resourceGroupName: string;
accountName: string;
sourceDatabaseName: string;
sourceCollectionName: string;
targetDatabaseName: string;
targetCollectionName: string;
}
export const getDataTransferJobs = async (
subscriptionId: string,
resourceGroup: string,
accountName: string,
): Promise<DataTransferJobGetResults[]> => {
let dataTransferJobs: DataTransferJobGetResults[] = [];
let dataTransferFeeds: DataTransferJobFeedResults = await listByDatabaseAccount(
subscriptionId,
resourceGroup,
accountName,
);
dataTransferJobs = [...dataTransferJobs, ...(dataTransferFeeds?.value || [])];
while (dataTransferFeeds?.nextLink) {
const nextResponse = await window.fetch(dataTransferFeeds.nextLink, {
headers: {
Authorization: userContext.authorizationToken,
},
});
if (nextResponse.ok) {
dataTransferFeeds = await nextResponse.json();
dataTransferJobs = [...dataTransferJobs, ...(dataTransferFeeds?.value || [])];
} else {
break;
}
}
return dataTransferJobs;
};
export const initiateDataTransfer = async (params: DataTransferParams): Promise<DataTransferJobGetResults> => {
const {
jobName,
apiType,
subscriptionId,
resourceGroupName,
accountName,
sourceDatabaseName,
sourceCollectionName,
targetDatabaseName,
targetCollectionName,
} = params;
const sourcePayload = createPayload(apiType, sourceDatabaseName, sourceCollectionName);
const targetPayload = createPayload(apiType, targetDatabaseName, targetCollectionName);
const body: CreateJobRequest = {
properties: {
source: sourcePayload,
destination: targetPayload,
},
};
return create(subscriptionId, resourceGroupName, accountName, jobName, body);
};
export const pollDataTransferJob = async (
jobName: string,
subscriptionId: string,
resourceGroupName: string,
accountName: string,
): Promise<unknown> => {
const currentPollingJobs = useDataTransferJobs.getState().pollingDataTransferJobs;
if (currentPollingJobs.has(jobName)) {
return;
}
let clearMessage = NotificationConsoleUtils.logConsoleProgress(`Data transfer job ${jobName} in progress`);
return await promiseRetry(
() => pollDataTransferJobOperation(jobName, subscriptionId, resourceGroupName, accountName, clearMessage),
{
retries: 500,
maxTimeout: 5000,
onFailedAttempt: (error: FailedAttemptError) => {
clearMessage();
clearMessage = NotificationConsoleUtils.logConsoleProgress(error.message);
},
},
);
};
const pollDataTransferJobOperation = async (
jobName: string,
subscriptionId: string,
resourceGroupName: string,
accountName: string,
clearMessage?: () => void,
): Promise<DataTransferJobGetResults> => {
if (!userContext.authorizationToken) {
throw new Error("No authority token provided");
}
addToPolling(jobName);
const body: DataTransferJobGetResults = await get(subscriptionId, resourceGroupName, accountName, jobName);
const status = body?.properties?.status;
updateDataTransferJob(body);
if (status === "Cancelled" || status === "Failed" || status === "Faulted") {
removeFromPolling(jobName);
const errorMessage = body?.properties?.error
? JSON.stringify(body?.properties?.error)
: "Operation could not be completed";
const error = new Error(errorMessage);
clearMessage && clearMessage();
NotificationConsoleUtils.logConsoleError(`Data transfer job ${jobName} Failed`);
throw new AbortError(error);
}
if (status === "Completed") {
removeFromPolling(jobName);
clearMessage && clearMessage();
NotificationConsoleUtils.logConsoleInfo(`Data transfer job ${jobName} completed`);
return body;
}
const processedCount = body.properties.processedCount;
const totalCount = body.properties.totalCount;
const retryMessage = `Data transfer job ${jobName} in progress, total count: ${totalCount}, processed count: ${processedCount}`;
throw new Error(retryMessage);
};
export const cancelDataTransferJob = async (
subscriptionId: string,
resourceGroupName: string,
accountName: string,
jobName: string,
): Promise<void> => {
const cancelResult: DataTransferJobGetResults = await cancel(subscriptionId, resourceGroupName, accountName, jobName);
updateDataTransferJob(cancelResult);
removeFromPolling(cancelResult?.properties?.jobName);
};
const createPayload = (
apiType: ApiType,
databaseName: string,
containerName: string,
):
| CosmosSqlDataTransferDataSourceSink
| CosmosMongoDataTransferDataSourceSink
| CosmosCassandraDataTransferDataSourceSink => {
switch (apiType) {
case "SQL":
return {
component: "CosmosDBSql",
databaseName: databaseName,
containerName: containerName,
} as CosmosSqlDataTransferDataSourceSink;
case "Mongo":
return {
component: "CosmosDBMongo",
databaseName: databaseName,
collectionName: containerName,
} as CosmosMongoDataTransferDataSourceSink;
case "Cassandra":
return {
component: "CosmosDBCassandra",
keyspaceName: databaseName,
tableName: containerName,
};
default:
throw new Error(`Unsupported API type for data transfer: ${apiType}`);
}
};

View File

@@ -1,5 +1,5 @@
import { getCommonQueryOptions } from "./queryDocuments";
import { LocalStorageUtility, StorageKey } from "../../Shared/StorageUtility";
import { getCommonQueryOptions } from "./queryDocuments";
describe("getCommonQueryOptions", () => {
it("builds the correct default options objects", () => {

View File

@@ -26,6 +26,5 @@ export const getCommonQueryOptions = (options: FeedOptions): FeedOptions => {
(storedItemPerPageSetting !== undefined && storedItemPerPageSetting) ||
Queries.itemsPerPage;
options.maxDegreeOfParallelism = LocalStorageUtility.getEntryNumber(StorageKey.MaxDegreeOfParellism);
return options;
};

View File

@@ -1,3 +1,4 @@
import { QueryOperationOptions } from "@azure/cosmos";
import { QueryResults } from "../../Contracts/ViewModels";
import { logConsoleInfo, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { getEntityName } from "../DocumentUtility";
@@ -8,12 +9,13 @@ export const queryDocumentsPage = async (
resourceName: string,
documentsIterator: MinimalQueryIterator,
firstItemIndex: number,
queryOperationOptions?: QueryOperationOptions,
): Promise<QueryResults> => {
const entityName = getEntityName();
const clearMessage = logConsoleProgress(`Querying ${entityName} for container ${resourceName}`);
try {
const result: QueryResults = await nextPage(documentsIterator, firstItemIndex);
const result: QueryResults = await nextPage(documentsIterator, firstItemIndex, queryOperationOptions);
const itemCount = (result.documents && result.documents.length) || 0;
logConsoleInfo(`Successfully fetched ${itemCount} ${entityName} for container ${resourceName}`);
return result;

View File

@@ -1,18 +1,57 @@
import { ContainerResponse } from "@azure/cosmos";
import { Queries } from "Common/Constants";
import { Platform, configContext } from "ConfigContext";
import { AuthType } from "../../AuthType";
import * as DataModels from "../../Contracts/DataModels";
import { userContext } from "../../UserContext";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { listCassandraTables } from "../../Utils/arm/generatedClients/cosmos/cassandraResources";
import { listGremlinGraphs } from "../../Utils/arm/generatedClients/cosmos/gremlinResources";
import { listMongoDBCollections } from "../../Utils/arm/generatedClients/cosmos/mongoDBResources";
import { listSqlContainers } from "../../Utils/arm/generatedClients/cosmos/sqlResources";
import { listTables } from "../../Utils/arm/generatedClients/cosmos/tableResources";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
export async function readCollections(databaseId: string): Promise<DataModels.Collection[]> {
const clearMessage = logConsoleProgress(`Querying containers for database ${databaseId}`);
if (
configContext.platform === Platform.Fabric &&
userContext.fabricContext &&
userContext.fabricContext.databaseConnectionInfo.databaseId === databaseId
) {
const collections: DataModels.Collection[] = [];
const promises: Promise<ContainerResponse>[] = [];
for (const collectionResourceId in userContext.fabricContext.databaseConnectionInfo.resourceTokens) {
// Dictionary key looks like this: dbs/SampleDB/colls/Container
const resourceIdObj = collectionResourceId.split("/");
const tokenDatabaseId = resourceIdObj[1];
const tokenCollectionId = resourceIdObj[3];
if (tokenDatabaseId === databaseId) {
promises.push(client().database(databaseId).container(tokenCollectionId).read());
}
}
try {
const responses = await Promise.all(promises);
responses.forEach((response) => {
collections.push(response.resource as DataModels.Collection);
});
// Sort collections by id before returning
collections.sort((a, b) => a.id.localeCompare(b.id));
return collections;
} catch (error) {
handleError(error, "ReadCollections", `Error while querying containers for database ${databaseId}`);
throw error;
} finally {
clearMessage();
}
}
try {
if (
userContext.authType === AuthType.AAD &&

View File

@@ -1,15 +1,22 @@
import { Platform, configContext } from "ConfigContext";
import { AuthType } from "../../AuthType";
import { Offer, ReadDatabaseOfferParams } from "../../Contracts/DataModels";
import { userContext } from "../../UserContext";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { getCassandraKeyspaceThroughput } from "../../Utils/arm/generatedClients/cosmos/cassandraResources";
import { getGremlinDatabaseThroughput } from "../../Utils/arm/generatedClients/cosmos/gremlinResources";
import { getMongoDBDatabaseThroughput } from "../../Utils/arm/generatedClients/cosmos/mongoDBResources";
import { getSqlDatabaseThroughput } from "../../Utils/arm/generatedClients/cosmos/sqlResources";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { handleError } from "../ErrorHandlingUtils";
import { readOfferWithSDK } from "./readOfferWithSDK";
export const readDatabaseOffer = async (params: ReadDatabaseOfferParams): Promise<Offer> => {
if (configContext.platform === Platform.Fabric) {
// TODO This works, but is very slow, because it requests the token, so we skip for now
console.error("Skiping readDatabaseOffer for Fabric");
return undefined;
}
const clearMessage = logConsoleProgress(`Querying offer for database ${params.databaseId}`);
try {

View File

@@ -1,17 +1,53 @@
import { Platform, configContext } from "ConfigContext";
import { AuthType } from "../../AuthType";
import * as DataModels from "../../Contracts/DataModels";
import { userContext } from "../../UserContext";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { listCassandraKeyspaces } from "../../Utils/arm/generatedClients/cosmos/cassandraResources";
import { listGremlinDatabases } from "../../Utils/arm/generatedClients/cosmos/gremlinResources";
import { listMongoDBDatabases } from "../../Utils/arm/generatedClients/cosmos/mongoDBResources";
import { listSqlDatabases } from "../../Utils/arm/generatedClients/cosmos/sqlResources";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
export async function readDatabases(): Promise<DataModels.Database[]> {
let databases: DataModels.Database[];
const clearMessage = logConsoleProgress(`Querying databases`);
if (configContext.platform === Platform.Fabric && userContext.fabricContext?.databaseConnectionInfo.resourceTokens) {
const tokensData = userContext.fabricContext.databaseConnectionInfo;
const databaseIdsSet = new Set<string>(); // databaseId
for (const collectionResourceId in tokensData.resourceTokens) {
// Dictionary key looks like this: dbs/SampleDB/colls/Container
const resourceIdObj = collectionResourceId.split("/");
if (resourceIdObj.length !== 4) {
handleError(`Resource key not recognized: ${resourceIdObj}`, "ReadDatabases", `Error while querying databases`);
clearMessage();
return [];
}
const databaseId = resourceIdObj[1];
databaseIdsSet.add(databaseId);
}
const databases: DataModels.Database[] = Array.from(databaseIdsSet.values())
.sort((a, b) => a.localeCompare(b))
.map((databaseId) => ({
_rid: "",
_self: "",
_etag: "",
_ts: 0,
id: databaseId,
collections: [],
}));
clearMessage();
return databases;
}
try {
if (
userContext.authType === AuthType.AAD &&

View File

@@ -0,0 +1,66 @@
export function getAuthorizationTokenUsingResourceTokens(
resourceTokens: { [resourceId: string]: string },
path: string,
resourceId: string,
): string {
// console.log(`getting token for path: "${path}" and resourceId: "${resourceId}"`);
if (resourceTokens && Object.keys(resourceTokens).length > 0) {
// For database account access(through getDatabaseAccount API), path and resourceId are "",
// so in this case we return the first token to be used for creating the auth header as the
// service will accept any token in this case
if (!path && !resourceId) {
return resourceTokens[Object.keys(resourceTokens)[0]];
}
// If we have exact resource token for the path use it
if (resourceId && resourceTokens[resourceId]) {
return resourceTokens[resourceId];
}
// minimum valid path /dbs
if (!path || path.length < 4) {
console.error(
`Unable to get authotization token for Path:"${path}" and resourcerId:"${resourceId}". Invalid path.`,
);
return null;
}
path = trimSlashFromLeftAndRight(path);
const pathSegments = (path && path.split("/")) || [];
// Item path
if (pathSegments.length === 6) {
// Look for a container token matching the item path
const containerPath = pathSegments.slice(0, 4).map(decodeURIComponent).join("/");
if (resourceTokens[containerPath]) {
return resourceTokens[containerPath];
}
}
// This is legacy behavior that lets someone use a resource token pointing ONLY at an ID
// It was used when _rid was exposed by the SDK, but now that we are using user provided ids it is not needed
// However removing it now would be a breaking change
// if it's an incomplete path like /dbs/db1/colls/, start from the parent resource
let index = pathSegments.length % 2 === 0 ? pathSegments.length - 1 : pathSegments.length - 2;
for (; index > 0; index -= 2) {
const id = decodeURI(pathSegments[index]);
if (resourceTokens[id]) {
return resourceTokens[id];
}
}
}
console.error(`Unable to get authotization token for Path:"${path}" and resourcerId:"${resourceId}"`);
return null;
}
const trimLeftSlashes = new RegExp("^[/]+");
const trimRightSlashes = new RegExp("[/]+$");
function trimSlashFromLeftAndRight(inputString: string): string {
if (typeof inputString !== "string") {
throw new Error("invalid input: input is not string");
}
return inputString.replace(trimLeftSlashes, "").replace(trimRightSlashes, "");
}

View File

@@ -1,16 +1,19 @@
import { CassandraProxyEndpoints, JunoEndpoints, MongoProxyEndpoints } from "Common/Constants";
import {
allowedAadEndpoints,
allowedArcadiaEndpoints,
allowedCassandraProxyEndpoints,
allowedEmulatorEndpoints,
allowedGraphEndpoints,
allowedHostedExplorerEndpoints,
allowedJunoOrigins,
allowedMongoBackendEndpoints,
allowedMongoProxyEndpoints,
allowedMsalRedirectEndpoints,
defaultAllowedArmEndpoints,
defaultAllowedBackendEndpoints,
validateEndpoint,
} from "Utils/EndpointValidation";
} from "Utils/EndpointUtils";
export enum Platform {
Portal = "Portal",
@@ -37,6 +40,10 @@ export interface ConfigContext {
ARCADIA_LIVY_ENDPOINT_DNS_ZONE: string;
BACKEND_ENDPOINT?: string;
MONGO_BACKEND_ENDPOINT?: string;
MONGO_PROXY_ENDPOINT?: string;
MONGO_PROXY_OUTBOUND_IPS_ALLOWLISTED?: boolean;
NEW_MONGO_APIS?: string[];
CASSANDRA_PROXY_ENDPOINT?: string;
PROXY_PATH?: string;
JUNO_ENDPOINT: string;
GITHUB_CLIENT_ID: string;
@@ -64,6 +71,8 @@ let configContext: Readonly<ConfigContext> = {
`^https:\\/\\/.*\\.fabric\\.microsoft\\.com$`,
`^https:\\/\\/.*\\.powerbi\\.com$`,
`^https:\\/\\/.*\\.analysis-df\\.net$`,
`^https:\\/\\/.*\\.analysis-df\\.windows\\.net$`,
`^https:\\/\\/.*\\.azure-test\\.net$`,
], // Webpack injects this at build time
gitSha: process.env.GIT_SHA,
hostedExplorerURL: "https://cosmos.azure.com/",
@@ -77,8 +86,19 @@ let configContext: Readonly<ConfigContext> = {
ARCADIA_LIVY_ENDPOINT_DNS_ZONE: "dev.azuresynapse.net",
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
JUNO_ENDPOINT: "https://tools.cosmos.azure.com",
JUNO_ENDPOINT: JunoEndpoints.Prod,
BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com",
MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Prod,
NEW_MONGO_APIS: [
// "resourcelist",
// "createDocument",
// "readDocument",
// "updateDocument",
// "deleteDocument",
// "createCollectionWithProxy",
],
MONGO_PROXY_OUTBOUND_IPS_ALLOWLISTED: false,
CASSANDRA_PROXY_ENDPOINT: CassandraProxyEndpoints.Prod,
isTerminalEnabled: false,
isPhoenixEnabled: false,
};
@@ -124,10 +144,18 @@ export function updateConfigContext(newContext: Partial<ConfigContext>): void {
delete newContext.BACKEND_ENDPOINT;
}
if (!validateEndpoint(newContext.MONGO_PROXY_ENDPOINT, allowedMongoProxyEndpoints)) {
delete newContext.MONGO_PROXY_ENDPOINT;
}
if (!validateEndpoint(newContext.MONGO_BACKEND_ENDPOINT, allowedMongoBackendEndpoints)) {
delete newContext.MONGO_BACKEND_ENDPOINT;
}
if (!validateEndpoint(newContext.CASSANDRA_PROXY_ENDPOINT, allowedCassandraProxyEndpoints)) {
delete newContext.CASSANDRA_PROXY_ENDPOINT;
}
if (!validateEndpoint(newContext.JUNO_ENDPOINT, allowedJunoOrigins)) {
delete newContext.JUNO_ENDPOINT;
}
@@ -145,10 +173,7 @@ export function updateConfigContext(newContext: Partial<ConfigContext>): void {
// Injected for local development. These will be removed in the production bundle by webpack
if (process.env.NODE_ENV === "development") {
const port: string = process.env.PORT || "1234";
updateConfigContext({
BACKEND_ENDPOINT: "https://localhost:" + port,
MONGO_BACKEND_ENDPOINT: "https://localhost:" + port,
PROXY_PATH: "/proxy",
EMULATOR_ENDPOINT: "https://localhost:8081",
});

View File

@@ -0,0 +1,13 @@
export interface QueryRequestOptions {
$skipToken?: string;
$top?: number;
subscriptions: string[];
}
export interface QueryResponse {
$skipToken: string;
count: number;
data: any;
resultTruncated: boolean;
totalRecords: number;
}

View File

@@ -0,0 +1,42 @@
import { MessageTypes } from "./MessageTypes";
// This is the current version of these messages
export const DATA_EXPLORER_RPC_VERSION = "2";
// Data Explorer to Fabric
// TODO Remove when upgrading to Fabric v2
export type DataExploreMessageV1 =
| "ready"
| {
type: MessageTypes.GetAuthorizationToken;
id: string;
params: GetCosmosTokenMessageOptions[];
}
| {
type: MessageTypes.GetAllResourceTokens;
id: string;
};
// -----------------------------
export type DataExploreMessageV2 =
| {
type: MessageTypes.Ready;
id: string;
params: [string]; // version
}
| {
type: MessageTypes.GetAuthorizationToken;
id: string;
params: GetCosmosTokenMessageOptions[];
}
| {
type: MessageTypes.GetAllResourceTokens;
id: string;
};
export type GetCosmosTokenMessageOptions = {
verb: "connect" | "delete" | "get" | "head" | "options" | "patch" | "post" | "put" | "trace";
resourceType: "" | "dbs" | "colls" | "docs" | "sprocs" | "pkranges";
resourceId: string;
};

View File

@@ -88,13 +88,13 @@ export interface GenerateTokenResponse {
}
export interface Subscription {
uniqueDisplayName: string;
uniqueDisplayName?: string;
displayName: string;
subscriptionId: string;
tenantId: string;
tenantId?: string;
state: string;
subscriptionPolicies: SubscriptionPolicies;
authorizationSource: string;
subscriptionPolicies?: SubscriptionPolicies;
authorizationSource?: string;
}
export interface SubscriptionPolicies {
@@ -318,7 +318,7 @@ export interface CreateCollectionParams {
collectionId: string;
databaseId: string;
databaseLevelThroughput: boolean;
offerThroughput: number;
offerThroughput?: number;
analyticalStorageTtl?: number;
autoPilotMaxThroughput?: number;
indexingPolicy?: IndexingPolicy;
@@ -457,8 +457,11 @@ export interface ContainerInfo {
}
export interface IProvisionData {
cosmosEndpoint: string;
cosmosEndpoint?: string;
poolId: string;
databaseId?: string;
containerId?: string;
mode?: string;
}
export interface IContainerData {
@@ -601,3 +604,14 @@ export enum PhoenixErrorType {
PhoenixFlightFallback = "PhoenixFlightFallback",
UserMissingPermissionsError = "UserMissingPermissionsError",
}
export interface CopilotEnabledConfiguration {
isEnabled: boolean;
}
export interface FeatureRegistration {
name: string;
properties: {
state: string;
};
}

View File

@@ -1,46 +1,6 @@
import * as ActionContracts from "./ActionContracts";
import * as Diagnostics from "./Diagnostics";
import { MessageTypes } from "./MessageTypes";
import * as Versions from "./Versions";
/**
* Messaging types used with Data Explorer <-> Portal communication
* and Hosted <-> Explorer communication
*/
export enum MessageTypes {
TelemetryInfo,
LogInfo,
RefreshResources,
AllDatabases,
CollectionsForDatabase,
RefreshOffers,
AllOffers,
UpdateLocationHash,
SingleOffer,
RefreshOffer,
UpdateAccountName,
ForbiddenError,
AadSignIn,
GetAccessAadRequest,
GetAccessAadResponse,
UpdateAccountSwitch,
UpdateDirectoryControl,
SwitchAccount,
SendNotification,
ClearNotification,
ExplorerClickEvent,
LoadingStatus,
GetArcadiaToken,
CreateWorkspace,
CreateSparkPool,
RefreshDatabaseAccount,
CloseTab,
OpenQuickstartBlade,
OpenPostgreSQLPasswordReset,
OpenPostgresNetworkingBlade,
OpenCosmosDBNetworkingBlade,
DisplayNPSSurvey,
OpenVCoreMongoNetworkingBlade,
OpenVCoreMongoConnectionStringsBlade,
}
export { ActionContracts, Diagnostics, Versions };
export { ActionContracts, Diagnostics, MessageTypes, Versions };

View File

@@ -1,25 +0,0 @@
export type FabricMessage =
| {
type: "newContainer";
databaseName: string;
}
| {
type: "initialize";
connectionString: string | undefined;
}
| {
type: "openTab";
databaseName: string;
collectionName: string | undefined;
};
export type DataExploreMessage =
| "ready"
| {
type: number;
data: {
action: "LoadDatabases";
actionModifier: "success" | "start";
defaultExperience: "SQL";
};
};

View File

@@ -0,0 +1,94 @@
import { AuthorizationToken } from "./MessageTypes";
// This is the version of these messages
export const FABRIC_RPC_VERSION = "2";
// Fabric to Data Explorer
// TODO Deprecated. Remove this section once DE is updated
export type FabricMessageV1 =
| {
type: "newContainer";
databaseName: string;
}
| {
type: "initialize";
message: {
endpoint: string | undefined;
databaseId: string | undefined;
resourceTokens: unknown | undefined;
resourceTokensTimestamp: number | undefined;
error: string | undefined;
};
}
| {
type: "authorizationToken";
message: {
id: string;
error: string | undefined;
data: AuthorizationToken | undefined;
};
}
| {
type: "allResourceTokens";
message: {
id: string;
error: string | undefined;
endpoint: string | undefined;
databaseId: string | undefined;
resourceTokens: unknown | undefined;
resourceTokensTimestamp: number | undefined;
};
};
// -----------------------------
export type FabricMessageV2 =
| {
type: "newContainer";
databaseName: string;
}
| {
type: "initialize";
version: string;
id: string;
message: {
connectionId: string;
};
}
| {
type: "authorizationToken";
message: {
id: string;
error: string | undefined;
data: AuthorizationToken | undefined;
};
}
| {
type: "allResourceTokens_v2";
message: {
id: string;
error: string | undefined;
data: FabricDatabaseConnectionInfo | undefined;
};
}
| {
type: "setToolbarStatus";
message: {
visible: boolean;
};
};
export type CosmosDBTokenResponse = {
token: string;
date: string;
};
export type CosmosDBConnectionInfoResponse = {
endpoint: string;
databaseId: string;
resourceTokens: { [resourceId: string]: string };
};
export interface FabricDatabaseConnectionInfo extends CosmosDBConnectionInfoResponse {
resourceTokensTimestamp: number;
}

View File

@@ -0,0 +1,55 @@
/**
* Messaging types used with Data Explorer <-> Portal communication,
* Hosted <-> Explorer communication and Data Explorer -> Fabric communication.
*
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
* 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.
*/
export enum MessageTypes {
TelemetryInfo,
LogInfo,
RefreshResources,
AllDatabases,
CollectionsForDatabase,
RefreshOffers,
AllOffers,
UpdateLocationHash,
SingleOffer,
RefreshOffer,
UpdateAccountName,
ForbiddenError,
AadSignIn,
GetAccessAadRequest,
GetAccessAadResponse,
UpdateAccountSwitch,
UpdateDirectoryControl,
SwitchAccount,
SendNotification,
ClearNotification,
ExplorerClickEvent,
LoadingStatus,
GetArcadiaToken,
CreateWorkspace,
CreateSparkPool,
RefreshDatabaseAccount,
CloseTab,
OpenQuickstartBlade,
OpenPostgreSQLPasswordReset,
OpenPostgresNetworkingBlade,
OpenCosmosDBNetworkingBlade,
DisplayNPSSurvey,
OpenVCoreMongoNetworkingBlade,
OpenVCoreMongoConnectionStringsBlade,
GetAuthorizationToken, // Data Explorer -> Fabric
GetAllResourceTokens, // Data Explorer -> Fabric
Ready, // Data Explorer -> Fabric
OpenCESCVAFeedbackBlade,
}
export interface AuthorizationToken {
XDate: string;
PrimaryReadWriteToken: string;
}

View File

@@ -386,9 +386,10 @@ export interface DataExplorerInputsFrame {
dnsSuffix?: string;
serverId?: string;
extensionEndpoint?: string;
mongoProxyEndpoint?: string;
cassandraProxyEndpoint?: string;
subscriptionType?: SubscriptionType;
quotaId?: string;
addCollectionDefaultFlight?: string;
isTryCosmosDBSubscription?: boolean;
loadDatabaseAccountTimestamp?: number;
sharedThroughputMinimum?: number;

View File

@@ -109,6 +109,7 @@ describe("iframe rendering when there is no data", () => {
theme: 4,
},
},
origin: "http://localhost",
};
const divElement = `<div id="${Heatmap.elementId}"></div>`;
@@ -129,6 +130,7 @@ describe("iframe rendering when there is no data", () => {
theme: 2,
},
},
origin: "http://localhost",
};
const divElement = `<div id="${Heatmap.elementId}"></div>`;

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,7 @@
* https://github.com/running-coder/jquery-typeahead/issues/156
* TODO: Replace this minimum definition by the official one when it comes out.
*/
/// <reference path="jquery.d.ts" />
/// <reference types="jquery" />
interface JQueryTypeaheadParam {
input: string;

View File

@@ -3,7 +3,7 @@
// Definitions by: Boris Yankov <https://github.com/borisyankov/>, John Reilly <https://github.com/johnnyreilly>
// Definitions: https://github.com/borisyankov/DefinitelyTyped
/// <reference path="jquery.d.ts"/>
/// <reference types="jquery"/>
declare namespace JQueryUI {
// Accordion //////////////////////////////////////////////////

File diff suppressed because it is too large Load Diff

4
src/Definitions/less.d.ts vendored Normal file
View File

@@ -0,0 +1,4 @@
declare module "*.less" {
const value: string;
export default value;
}

View File

@@ -129,20 +129,22 @@ export const createCollectionContextMenuButton = (
});
}
items.push({
iconSrc: DeleteCollectionIcon,
onClick: () => {
useSelectedNode.getState().setSelectedNode(selectedCollection);
useSidePanel
.getState()
.openSidePanel(
"Delete " + getCollectionName(),
<DeleteCollectionConfirmationPane refreshDatabases={() => container.refreshAllDatabases()} />,
);
},
label: `Delete ${getCollectionName()}`,
styleClass: "deleteCollectionMenuItem",
});
if (configContext.platform !== Platform.Fabric) {
items.push({
iconSrc: DeleteCollectionIcon,
onClick: () => {
useSelectedNode.getState().setSelectedNode(selectedCollection);
useSidePanel
.getState()
.openSidePanel(
"Delete " + getCollectionName(),
<DeleteCollectionConfirmationPane refreshDatabases={() => container.refreshAllDatabases()} />,
);
},
label: `Delete ${getCollectionName()}`,
styleClass: "deleteCollectionMenuItem",
});
}
return items;
};

View File

@@ -27,6 +27,7 @@ export interface AccordionItemComponentProps {
isExpanded?: boolean;
containerStyles?: React.CSSProperties;
styles?: React.CSSProperties;
children: JSX.Element;
}
interface AccordionItemComponentState {

View File

@@ -16,6 +16,7 @@ export interface CollapsiblePanelProps {
isCollapsed: boolean;
onCollapsedChanged: (newValue: boolean) => void;
collapseToLeft?: boolean;
children: JSX.Element | JSX.Element[];
}
export class CollapsiblePanel extends React.Component<CollapsiblePanelProps> {

View File

@@ -7,6 +7,7 @@ describe("CollapsibleSectionComponent", () => {
const props: CollapsibleSectionProps = {
title: "Sample title",
isExpandedByDefault: true,
children: <></>,
};
const wrapper = shallow(<CollapsibleSectionComponent {...props} />);

View File

@@ -7,6 +7,7 @@ export interface CollapsibleSectionProps {
title: string;
isExpandedByDefault: boolean;
onExpand?: () => void;
children: JSX.Element;
}
export interface CollapsibleSectionState {

View File

@@ -99,7 +99,7 @@ export class DiffEditorViewModel {
) {
this.editorContainer = document.getElementById(this.getEditorId());
this.editorContainer.innerHTML = "";
const options: monaco.editor.IDiffEditorConstructionOptions = {
const options: monaco.editor.IStandaloneDiffEditorConstructionOptions = {
lineNumbers: this.params.lineNumbers || "off",
fontSize: 12,
ariaLabel: this.params.ariaLabel,

View File

@@ -52,7 +52,11 @@ class EditorViewModel extends JsonEditorViewModel {
if (EditorViewModel.providerRegistered.indexOf("sql") < 0) {
const { SqlCompletionItemProvider } = await import("@azure/cosmos-language-service");
const monaco = await loadMonaco();
monaco.languages.registerCompletionItemProvider("sql", new SqlCompletionItemProvider());
monaco.languages.registerCompletionItemProvider(
"sql",
// TODO cosmos-language-service could be outdated
new SqlCompletionItemProvider() as unknown as monaco.languages.CompletionItemProvider,
);
EditorViewModel.providerRegistered.push("sql");
}
}

View File

@@ -48,7 +48,7 @@ export class EditorReact extends React.Component<EditorReactProps, EditorReactSt
public componentDidUpdate(previous: EditorReactProps) {
if (this.props.content !== previous.content) {
this.editor.setValue(this.props.content);
this.editor?.setValue(this.props.content);
}
}
@@ -93,7 +93,7 @@ export class EditorReact extends React.Component<EditorReactProps, EditorReactSt
* Create the monaco editor and attach to DOM
*/
private async createEditor(createCallback: (e: monaco.editor.IStandaloneCodeEditor) => void) {
const options: monaco.editor.IEditorConstructionOptions = {
const options: monaco.editor.IStandaloneEditorConstructionOptions = {
language: this.props.language,
value: this.props.content,
readOnly: this.props.isReadOnly,
@@ -111,7 +111,7 @@ export class EditorReact extends React.Component<EditorReactProps, EditorReactSt
this.rootNode.innerHTML = "";
const monaco = await loadMonaco();
createCallback(monaco.editor.create(this.rootNode, options));
createCallback(monaco?.editor?.create(this.rootNode, options));
if (this.rootNode.innerHTML) {
this.setState({

View File

@@ -90,7 +90,7 @@ export class JsonEditorViewModel extends WaitsForTemplateViewModel {
protected async createEditor(content: string, createCallback: (e: monaco.editor.IStandaloneCodeEditor) => void) {
this.registerCompletionItemProvider();
this.editorContainer = document.getElementById(this.getEditorId());
const options: monaco.editor.IEditorConstructionOptions = {
const options: monaco.editor.IStandaloneEditorConstructionOptions = {
value: content,
language: this.getEditorLanguage(),
readOnly: this.params.isReadOnly,

View File

@@ -29,6 +29,6 @@ describe("CodeOfConduct", () => {
const wrapper = shallow(<CodeOfConduct {...codeOfConductProps} />);
wrapper.find(".genericPaneSubmitBtn").first().simulate("click");
await Promise.resolve();
expect(codeOfConductProps.onAcceptCodeOfConduct).toBeCalled();
expect(codeOfConductProps.onAcceptCodeOfConduct).toHaveBeenCalled();
});
});

View File

@@ -9,7 +9,7 @@ import { updateUserContext } from "../../../UserContext";
import Explorer from "../../Explorer";
import { CollectionSettingsTabV2 } from "../../Tabs/SettingsTabV2";
import { SettingsComponent, SettingsComponentProps, SettingsComponentState } from "./SettingsComponent";
import { isDirty, TtlType } from "./SettingsUtils";
import { TtlType, isDirty } from "./SettingsUtils";
import { collection } from "./TestUtils";
jest.mock("../../../Common/dataAccess/getIndexTransformationProgress", () => ({
getIndexTransformationProgress: jest.fn().mockReturnValue(undefined),
@@ -190,8 +190,8 @@ describe("SettingsComponent", () => {
id: "id",
};
await settingsComponentInstance.onSaveClick();
expect(updateCollection).toBeCalled();
expect(updateOffer).toBeCalled();
expect(updateCollection).toHaveBeenCalled();
expect(updateOffer).toHaveBeenCalled();
});
it("revert resets state values", async () => {

View File

@@ -1,5 +1,6 @@
import { IPivotItemProps, IPivotProps, Pivot, PivotItem } from "@fluentui/react";
import { useDatabases } from "Explorer/useDatabases";
import { isRunningOnPublicCloud } from "Utils/CloudUtils";
import * as React from "react";
import DiscardIcon from "../../../../images/discard.svg";
import SaveIcon from "../../../../images/save-cosmos.svg";
@@ -18,6 +19,10 @@ import { userContext } from "../../../UserContext";
import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
import { MongoDBCollectionResource, MongoIndex } from "../../../Utils/arm/generatedClients/cosmos/types";
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
import {
PartitionKeyComponent,
PartitionKeyComponentProps,
} from "../../Controls/Settings/SettingsSubComponents/PartitionKeyComponent";
import { useCommandBar } from "../../Menus/CommandBar/CommandBarComponentAdapter";
import { SettingsTabV2 } from "../../Tabs/SettingsTabV2";
import "./SettingsComponent.less";
@@ -128,6 +133,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
private changeFeedPolicyVisible: boolean;
private isFixedContainer: boolean;
private shouldShowIndexingPolicyEditor: boolean;
private shouldShowPartitionKeyEditor: boolean;
private totalThroughputUsed: number;
public mongoDBCollectionResource: MongoDBCollectionResource;
@@ -140,6 +146,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
this.offer = this.collection?.offer();
this.isAnalyticalStorageEnabled = !!this.collection?.analyticalStorageTtl();
this.shouldShowIndexingPolicyEditor = userContext.apiType !== "Cassandra" && userContext.apiType !== "Mongo";
this.shouldShowPartitionKeyEditor = userContext.apiType === "SQL" && isRunningOnPublicCloud();
this.changeFeedPolicyVisible = userContext.features.enableChangeFeedPolicy;
@@ -1056,6 +1063,12 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
onConflictResolutionDirtyChange: this.onConflictResolutionDirtyChange,
};
const partitionKeyComponentProps: PartitionKeyComponentProps = {
database: useDatabases.getState().findDatabaseWithId(this.collection.databaseId),
collection: this.collection,
explorer: this.props.settingsTab.getContainer(),
};
const tabs: SettingsV2TabInfo[] = [];
if (!hasDatabaseSharedThroughput(this.collection) && this.offer) {
tabs.push({
@@ -1091,6 +1104,13 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
});
}
if (this.shouldShowPartitionKeyEditor) {
tabs.push({
tab: SettingsV2TabTypes.PartitionKeyTab,
content: <PartitionKeyComponent {...partitionKeyComponentProps} />,
});
}
const pivotProps: IPivotProps = {
onLinkClick: this.onPivotChange,
selectedKey: SettingsV2TabTypes[this.state.selectedTab],

View File

@@ -1,8 +1,8 @@
import { shallow } from "enzyme";
import React from "react";
import { renderToString } from "react-dom/server";
import { MongoIndexTypes, MongoNotificationMessage, MongoNotificationType } from "../../SettingsUtils";
import { MongoIndexingPolicyComponent, MongoIndexingPolicyComponentProps } from "./MongoIndexingPolicyComponent";
import { renderToString } from "react-dom/server";
describe("MongoIndexingPolicyComponent", () => {
const baseProps: MongoIndexingPolicyComponentProps = {
@@ -84,7 +84,7 @@ describe("MongoIndexingPolicyComponent", () => {
];
test.each(cases)(
"",
"mongo indexing policy saveable and discardable",
(
notification: MongoNotificationMessage,
indexToDropIsPresent: boolean,
@@ -111,8 +111,10 @@ describe("MongoIndexingPolicyComponent", () => {
);
if (mongoWarningNotificationMessage) {
const elementAsString = renderToString(mongoIndexingPolicyComponent.getMongoWarningNotificationMessage());
// eslint-disable-next-line jest/no-conditional-expect
expect(elementAsString).toContain(mongoWarningNotificationMessage);
} else {
// eslint-disable-next-line jest/no-conditional-expect
expect(mongoIndexingPolicyComponent.getMongoWarningNotificationMessage()).toBeUndefined();
}
},

View File

@@ -0,0 +1,216 @@
import {
DefaultButton,
FontWeights,
Link,
MessageBar,
MessageBarType,
PrimaryButton,
ProgressIndicator,
Stack,
Text,
} from "@fluentui/react";
import * as React from "react";
import * as ViewModels from "../../../../Contracts/ViewModels";
import { handleError } from "Common/ErrorHandlingUtils";
import { cancelDataTransferJob, pollDataTransferJob } from "Common/dataAccess/dataTransfers";
import Explorer from "Explorer/Explorer";
import { ChangePartitionKeyPane } from "Explorer/Panes/ChangePartitionKeyPane/ChangePartitionKeyPane";
import {
CosmosSqlDataTransferDataSourceSink,
DataTransferJobGetResults,
} from "Utils/arm/generatedClients/dataTransferService/types";
import { refreshDataTransferJobs, useDataTransferJobs } from "hooks/useDataTransferJobs";
import { useSidePanel } from "hooks/useSidePanel";
import { userContext } from "../../../../UserContext";
export interface PartitionKeyComponentProps {
database: ViewModels.Database;
collection: ViewModels.Collection;
explorer: Explorer;
}
export const PartitionKeyComponent: React.FC<PartitionKeyComponentProps> = ({ database, collection, explorer }) => {
const { dataTransferJobs } = useDataTransferJobs();
const [portalDataTransferJob, setPortalDataTransferJob] = React.useState<DataTransferJobGetResults>(null);
React.useEffect(() => {
const loadDataTransferJobs = refreshDataTransferOperations;
loadDataTransferJobs();
}, []);
React.useEffect(() => {
const currentJob = findPortalDataTransferJob();
setPortalDataTransferJob(currentJob);
startPollingforUpdate(currentJob);
}, [dataTransferJobs]);
const isHierarchicalPartitionedContainer = (): boolean => collection.partitionKey?.kind === "MultiHash";
const getPartitionKeyValue = (): string => {
return (collection.partitionKeyProperties || []).map((property) => "/" + property).join(", ");
};
const partitionKeyName = "Partition key";
const partitionKeyValue = getPartitionKeyValue();
const textHeadingStyle = {
root: { fontWeight: FontWeights.semibold, fontSize: 16 },
};
const textSubHeadingStyle = {
root: { fontWeight: FontWeights.semibold },
};
const startPollingforUpdate = (currentJob: DataTransferJobGetResults) => {
if (isCurrentJobInProgress(currentJob)) {
const jobName = currentJob?.properties?.jobName;
try {
pollDataTransferJob(
jobName,
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
);
} catch (error) {
handleError(error, "ChangePartitionKey", `Failed to complete data transfer job ${jobName}`);
}
}
};
const cancelRunningDataTransferJob = async (currentJob: DataTransferJobGetResults) => {
await cancelDataTransferJob(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
currentJob?.properties?.jobName,
);
};
const isCurrentJobInProgress = (currentJob: DataTransferJobGetResults) => {
const jobStatus = currentJob?.properties?.status;
return (
jobStatus &&
jobStatus !== "Completed" &&
jobStatus !== "Cancelled" &&
jobStatus !== "Failed" &&
jobStatus !== "Faulted"
);
};
const refreshDataTransferOperations = async () => {
await refreshDataTransferJobs(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
);
};
const findPortalDataTransferJob = (): DataTransferJobGetResults => {
return dataTransferJobs.find((feed: DataTransferJobGetResults) => {
const sourceSink: CosmosSqlDataTransferDataSourceSink = feed?.properties
?.source as CosmosSqlDataTransferDataSourceSink;
return sourceSink.databaseName === collection.databaseId && sourceSink.containerName === collection.id();
});
};
const getProgressDescription = (): string => {
const processedCount = portalDataTransferJob?.properties?.processedCount;
const totalCount = portalDataTransferJob?.properties?.totalCount;
const processedCountString = totalCount > 0 ? `(${processedCount} of ${totalCount} documents processed)` : "";
return `${portalDataTransferJob?.properties?.status} ${processedCountString}`;
};
const startPartitionkeyChangeWorkflow = () => {
useSidePanel
.getState()
.openSidePanel(
"Change partition key",
<ChangePartitionKeyPane
sourceDatabase={database}
sourceCollection={collection}
explorer={explorer}
onClose={refreshDataTransferOperations}
/>,
);
};
const getPercentageComplete = () => {
const processedCount = portalDataTransferJob?.properties?.processedCount;
const totalCount = portalDataTransferJob?.properties?.totalCount;
const jobStatus = portalDataTransferJob?.properties?.status;
const isCancelled = jobStatus === "Cancelled";
const isCompleted = jobStatus === "Completed";
if (totalCount <= 0 && !isCompleted) {
return isCancelled ? 0 : null;
}
return isCompleted ? 1 : processedCount / totalCount;
};
return (
<Stack tokens={{ childrenGap: 20 }} styles={{ root: { maxWidth: 600 } }}>
<Stack tokens={{ childrenGap: 10 }}>
<Text styles={textHeadingStyle}>Change {partitionKeyName.toLowerCase()}</Text>
<Stack horizontal tokens={{ childrenGap: 20 }}>
<Stack tokens={{ childrenGap: 5 }}>
<Text styles={textSubHeadingStyle}>Current {partitionKeyName.toLowerCase()}</Text>
<Text styles={textSubHeadingStyle}>Partitioning</Text>
</Stack>
<Stack tokens={{ childrenGap: 5 }}>
<Text>{partitionKeyValue}</Text>
<Text>{isHierarchicalPartitionedContainer() ? "Hierarchical" : "Non-hierarchical"}</Text>
</Stack>
</Stack>
</Stack>
<MessageBar messageBarType={MessageBarType.warning}>
To safeguard the integrity of the data being copied to the new container, ensure that no updates are made to the
source container for the entire duration of the partition key change process.
<Link
href="https://learn.microsoft.com/azure/cosmos-db/container-copy#how-does-container-copy-work"
target="_blank"
underline
>
Learn more
</Link>
</MessageBar>
<Text>
To change the partition key, a new destination container must be created or an existing destination container
selected. Data will then be copied to the destination container.
</Text>
<PrimaryButton
styles={{ root: { width: "fit-content" } }}
text="Change"
onClick={startPartitionkeyChangeWorkflow}
disabled={isCurrentJobInProgress(portalDataTransferJob)}
/>
{portalDataTransferJob && (
<Stack>
<Text styles={textHeadingStyle}>{partitionKeyName} change job</Text>
<Stack
horizontal
tokens={{ childrenGap: 20 }}
styles={{
root: {
alignItems: "center",
},
}}
>
<ProgressIndicator
label={portalDataTransferJob?.properties?.jobName}
description={getProgressDescription()}
percentComplete={getPercentageComplete()}
styles={{
root: {
width: "85%",
},
}}
></ProgressIndicator>
{isCurrentJobInProgress(portalDataTransferJob) && (
<DefaultButton text="Cancel" onClick={() => cancelRunningDataTransferJob(portalDataTransferJob)} />
)}
</Stack>
</Stack>
)}
</Stack>
);
};

View File

@@ -306,7 +306,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
};
const costElement = (): JSX.Element => {
const prices: PriceBreakdown = getRuPriceBreakdown(throughput, serverId, numberOfRegions, isMultimaster, true);
const prices: PriceBreakdown = getRuPriceBreakdown(throughput, serverId, numberOfRegions, isMultimaster, false);
return (
<Stack {...checkBoxAndInputStackProps} style={{ marginTop: 15 }}>
{newThroughput && newThroughputCostElement()}

View File

@@ -917,7 +917,7 @@ exports[`ThroughputInputAutoPilotV3Component spendAck checkbox visible 1`] = `
>
$
0.012
0.0080
/hr
</Text>
<Text
@@ -929,7 +929,7 @@ exports[`ThroughputInputAutoPilotV3Component spendAck checkbox visible 1`] = `
>
$
0.29
0.19
/day
</Text>
<Text
@@ -941,7 +941,7 @@ exports[`ThroughputInputAutoPilotV3Component spendAck checkbox visible 1`] = `
>
$
8.76
5.84
/mo
</Text>
</Stack>
@@ -1354,7 +1354,7 @@ exports[`ThroughputInputAutoPilotV3Component throughput input visible 1`] = `
>
$
0.012
0.0080
/hr
</Text>
<Text
@@ -1366,7 +1366,7 @@ exports[`ThroughputInputAutoPilotV3Component throughput input visible 1`] = `
>
$
0.29
0.19
/day
</Text>
<Text
@@ -1378,7 +1378,7 @@ exports[`ThroughputInputAutoPilotV3Component throughput input visible 1`] = `
>
$
8.76
5.84
/mo
</Text>
</Stack>

View File

@@ -45,6 +45,7 @@ export enum SettingsV2TabTypes {
ConflictResolutionTab,
SubSettingsTab,
IndexingPolicyTab,
PartitionKeyTab,
}
export interface IsComponentDirtyResult {
@@ -146,6 +147,8 @@ export const getTabTitle = (tab: SettingsV2TabTypes): string => {
return "Settings";
case SettingsV2TabTypes.IndexingPolicyTab:
return "Indexing Policy";
case SettingsV2TabTypes.PartitionKeyTab:
return "Partition Keys";
default:
throw new Error(`Unknown tab ${tab}`);
}
@@ -199,3 +202,49 @@ export const getMongoIndexTypeText = (index: MongoIndexTypes): string => {
export const isIndexTransforming = (indexTransformationProgress: number): boolean =>
// index transformation progress can be 0
indexTransformationProgress !== undefined && indexTransformationProgress !== 100;
export const getPartitionKeyName = (apiType: string, isLowerCase?: boolean): string => {
const partitionKeyName = apiType === "Mongo" ? "Shard key" : "Partition key";
return isLowerCase ? partitionKeyName.toLocaleLowerCase() : partitionKeyName;
};
export const getPartitionKeyTooltipText = (apiType: string): string => {
if (apiType === "Mongo") {
return "The shard key (field) is used to split your data across many replica sets (shards) to achieve unlimited scalability. Its critical to choose a field that will evenly distribute your data.";
}
let tooltipText = `The ${getPartitionKeyName(
apiType,
true,
)} is used to automatically distribute data across partitions for scalability. Choose a property in your JSON document that has a wide range of values and evenly distributes request volume.`;
if (apiType === "SQL") {
tooltipText += " For small read-heavy workloads or write-heavy workloads of any size, id is often a good choice.";
}
return tooltipText;
};
export const getPartitionKeySubtext = (partitionKeyDefault: boolean, apiType: string): string => {
if (partitionKeyDefault && (apiType === "SQL" || apiType === "Mongo")) {
const subtext = "For small workloads, the item ID is a suitable choice for the partition key.";
return subtext;
}
return "";
};
export const getPartitionKeyPlaceHolder = (apiType: string, index?: number): string => {
switch (apiType) {
case "Mongo":
return "e.g., categoryId";
case "Gremlin":
return "e.g., /address";
case "SQL":
return `${
index === undefined
? "Required - first partition key e.g., /TenantId"
: index === 0
? "second partition key e.g., /UserId"
: "third partition key e.g., /SessionId"
}`;
default:
return "e.g., /address/zipCode";
}
};

View File

@@ -204,6 +204,98 @@ exports[`SettingsComponent renders 1`] = `
shouldDiscardIndexingPolicy={false}
/>
</PivotItem>
<PivotItem
headerText="Partition Keys"
itemKey="PartitionKeyTab"
key="PartitionKeyTab"
style={
Object {
"marginTop": 20,
}
}
>
<PartitionKeyComponent
collection={
Object {
"analyticalStorageTtl": [Function],
"changeFeedPolicy": [Function],
"conflictResolutionPolicy": [Function],
"container": Explorer {
"_isInitializingNotebooks": false,
"_resetNotebookWorkspace": [Function],
"isFixedCollectionWithSharedThroughputSupported": [Function],
"isTabsContentExpanded": [Function],
"onRefreshDatabasesKeyPress": [Function],
"onRefreshResourcesClick": [Function],
"phoenixClient": PhoenixClient {
"armResourceId": undefined,
"retryOptions": Object {
"maxTimeout": 5000,
"minTimeout": 5000,
"retries": 3,
},
},
"provideFeedbackEmail": [Function],
"queriesClient": QueriesClient {
"container": [Circular],
},
"refreshNotebookList": [Function],
"resourceTree": ResourceTreeAdapter {
"container": [Circular],
"copyNotebook": [Function],
"parameters": [Function],
},
},
"databaseId": "test",
"defaultTtl": [Function],
"geospatialConfig": [Function],
"getDatabase": [Function],
"id": [Function],
"indexingPolicy": [Function],
"offer": [Function],
"partitionKey": Object {
"kind": "hash",
"paths": Array [],
"version": 2,
},
"partitionKeyProperties": Array [
"partitionKey",
],
"readSettings": [Function],
"uniqueKeyPolicy": Object {},
"usageSizeInKB": [Function],
}
}
explorer={
Explorer {
"_isInitializingNotebooks": false,
"_resetNotebookWorkspace": [Function],
"isFixedCollectionWithSharedThroughputSupported": [Function],
"isTabsContentExpanded": [Function],
"onRefreshDatabasesKeyPress": [Function],
"onRefreshResourcesClick": [Function],
"phoenixClient": PhoenixClient {
"armResourceId": undefined,
"retryOptions": Object {
"maxTimeout": 5000,
"minTimeout": 5000,
"retries": 3,
},
},
"provideFeedbackEmail": [Function],
"queriesClient": QueriesClient {
"container": [Circular],
},
"refreshNotebookList": [Function],
"resourceTree": ResourceTreeAdapter {
"container": [Circular],
"copyNotebook": [Function],
"parameters": [Function],
},
}
}
/>
</PivotItem>
</StyledPivot>
</div>
</div>

View File

@@ -23,12 +23,12 @@ describe("ThroughputInput Pane", () => {
});
it("should switch mode properly", () => {
wrapper.find('[aria-label="Manual database throughput"]').simulate("change");
wrapper.find('[id="Manual-input"]').simulate("change");
expect(wrapper.find('[aria-label="Throughput header"]').at(0).text()).toBe(
"Container throughput (400 - unlimited RU/s)",
);
wrapper.find('[aria-label="Autoscale database throughput"]').simulate("change");
wrapper.find('[id="Autoscale-input"]').simulate("change");
expect(wrapper.find('[aria-label="Throughput header"]').at(0).text()).toBe("Container throughput (autoscale)");
});
});

View File

@@ -189,7 +189,7 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
<input
id="Autoscale-input"
className="throughputInputRadioBtn"
aria-label="Autoscale database throughput"
aria-label={`${getThroughputLabelText()} Autoscale`}
aria-required={true}
checked={isAutoscaleSelected}
type="radio"
@@ -204,7 +204,7 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
<input
id="Manual-input"
className="throughputInputRadioBtn"
aria-label="Manual database throughput"
aria-label={`${getThroughputLabelText()} Manual`}
checked={!isAutoscaleSelected}
type="radio"
aria-required={true}
@@ -271,11 +271,17 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
<Stack className="throughputInputSpacing">
<Text variant="small" aria-label="ruDescription">
Estimate your required RU/s with&nbsp;
<Link target="_blank" href="https://cosmos.azure.com/capacitycalculator/" aria-label="capacityLink">
<Link target="_blank" href="https://cosmos.azure.com/capacitycalculator/" aria-label="Capacity calculator">
capacity calculator
</Link>
.
</Text>
<Stack horizontal>
<Text variant="small" style={{ lineHeight: "20px", fontWeight: 600 }} aria-label="maxRUDescription">
{isDatabase ? "Database" : getCollectionName()} Required RU/s
</Text>
<InfoTooltip>{getAutoScaleTooltip()}</InfoTooltip>
</Stack>
<TooltipHost
directionalHint={DirectionalHint.topLeftEdge}
@@ -296,7 +302,7 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
min={SharedConstants.CollectionCreation.DefaultCollectionRUs400}
max={userContext.isTryCosmosDBSubscription ? Constants.TryCosmosExperience.maxRU : Infinity}
value={throughput.toString()}
aria-label="Max request units per second"
ariaLabel={`${isDatabase ? "Database" : getCollectionName()} Required RU/s`}
required={true}
errorMessage={throughputError}
/>

View File

@@ -18,17 +18,17 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
horizontal={true}
>
<div
className="ms-Stack css-53"
className="ms-Stack css-109"
>
<span
className="mandatoryStar"
key=".0:$.0"
key=".0:$.$.0"
>
* 
</span>
<Text
aria-label="Throughput header"
key=".0:$.1"
key=".0:$.$.1"
style={
Object {
"fontWeight": 600,
@@ -39,7 +39,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
>
<span
aria-label="Throughput header"
className="css-54"
className="css-110"
style={
Object {
"fontWeight": 600,
@@ -51,7 +51,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
</span>
</Text>
<InfoTooltip
key=".0:$.2"
key=".0:$.$.2"
>
<span>
<StyledTooltipHostBase
@@ -336,12 +336,13 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
}
>
<div
className="ms-TooltipHost root-55"
className="ms-TooltipHost root-111"
onBlurCapture={[Function]}
onFocusCapture={[Function]}
onKeyDown={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
role="none"
>
<StyledIconBase
ariaLabel="Set the throughput — Request Units per second (RU/s) — required for the workload. A read of a 1 KB document uses 1 RU. Select manual if you plan to scale RU/s yourself. Select autoscale to allow the system to scale RU/s based on usage."
@@ -631,7 +632,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
>
<i
aria-label="Set the throughput — Request Units per second (RU/s) — required for the workload. A read of a 1 KB document uses 1 RU. Select manual if you plan to scale RU/s yourself. Select autoscale to allow the system to scale RU/s based on usage."
className="panelInfoIcon root-57"
className="panelInfoIcon root-114"
data-icon-name="Info"
role="img"
tabIndex={0}
@@ -640,6 +641,24 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
</i>
</IconBase>
</StyledIconBase>
<div
hidden={true}
id="tooltip0"
style={
Object {
"border": 0,
"height": 1,
"margin": -1,
"overflow": "hidden",
"padding": 0,
"position": "absolute",
"whiteSpace": "nowrap",
"width": 1,
}
}
>
Set the throughput — Request Units per second (RU/s) — required for the workload. A read of a 1 KB document uses 1 RU. Select manual if you plan to scale RU/s yourself. Select autoscale to allow the system to scale RU/s based on usage.
</div>
</div>
</TooltipHostBase>
</StyledTooltipHostBase>
@@ -652,14 +671,14 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
verticalAlign="center"
>
<div
className="ms-Stack css-58"
className="ms-Stack css-115"
>
<div
key=".0:$.0"
key=".0:$.$.0"
role="radiogroup"
>
<input
aria-label="Autoscale database throughput"
aria-label="Container throughput (autoscale) Autoscale"
aria-required={true}
checked={true}
className="throughputInputRadioBtn"
@@ -676,7 +695,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
Autoscale
</label>
<input
aria-label="Manual database throughput"
aria-label="Container throughput (autoscale) Manual"
aria-required={true}
checked={false}
className="throughputInputRadioBtn"
@@ -699,16 +718,16 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
className="throughputInputSpacing"
>
<div
className="ms-Stack throughputInputSpacing css-59"
className="ms-Stack throughputInputSpacing css-116"
>
<Text
aria-label="capacity calculator of azure cosmos db"
key=".0:$.0"
key=".0:$.$.0"
variant="small"
>
<span
aria-label="capacity calculator of azure cosmos db"
className="css-54"
className="css-110"
>
Estimate your required RU/s with
@@ -998,7 +1017,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
>
<a
aria-label="capacity calculator of azure cosmos db"
className="ms-Link root-60"
className="ms-Link root-117"
href="https://cosmos.azure.com/capacitycalculator/"
onClick={[Function]}
target="_blank"
@@ -1012,14 +1031,14 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
</Text>
<Stack
horizontal={true}
key=".0:$.1"
key=".0:$.$.1"
>
<div
className="ms-Stack css-53"
className="ms-Stack css-109"
>
<Text
aria-label="maxRUDescription"
key=".0:$.0"
key=".0:$.$.0"
style={
Object {
"fontWeight": 600,
@@ -1030,7 +1049,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
>
<span
aria-label="maxRUDescription"
className="css-54"
className="css-110"
style={
Object {
"fontWeight": 600,
@@ -1043,7 +1062,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
</span>
</Text>
<InfoTooltip
key=".0:$.1"
key=".0:$.$.1"
>
<span>
<StyledTooltipHostBase
@@ -1328,12 +1347,13 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
}
>
<div
className="ms-TooltipHost root-55"
className="ms-TooltipHost root-111"
onBlurCapture={[Function]}
onFocusCapture={[Function]}
onKeyDown={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
role="none"
>
<StyledIconBase
ariaLabel="Set the max RU/s to the highest RU/s you want your container to scale to. The container will scale between 10% of max RU/s to the max RU/s based on usage."
@@ -1623,7 +1643,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
>
<i
aria-label="Set the max RU/s to the highest RU/s you want your container to scale to. The container will scale between 10% of max RU/s to the max RU/s based on usage."
className="panelInfoIcon root-57"
className="panelInfoIcon root-114"
data-icon-name="Info"
role="img"
tabIndex={0}
@@ -1632,6 +1652,24 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
</i>
</IconBase>
</StyledIconBase>
<div
hidden={true}
id="tooltip1"
style={
Object {
"border": 0,
"height": 1,
"margin": -1,
"overflow": "hidden",
"padding": 0,
"position": "absolute",
"whiteSpace": "nowrap",
"width": 1,
}
}
>
Set the max RU/s to the highest RU/s you want your container to scale to. The container will scale between 10% of max RU/s to the max RU/s based on usage.
</div>
</div>
</TooltipHostBase>
</StyledTooltipHostBase>
@@ -1643,7 +1681,7 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
ariaLabel="Container max RU/s"
errorMessage=""
id="autoscaleRUValueField"
key=".0:$.2"
key=".0:$.$.2"
max="9007199254740991"
min={1000}
onChange={[Function]}
@@ -1953,18 +1991,18 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
value="4000"
>
<div
className="ms-TextField is-required root-62"
className="ms-TextField is-required root-119"
>
<div
className="ms-TextField-wrapper"
>
<div
className="ms-TextField-fieldGroup fieldGroup-63"
className="ms-TextField-fieldGroup fieldGroup-120"
>
<input
aria-invalid={false}
aria-label="Container max RU/s"
className="ms-TextField-field field-64"
className="ms-TextField-field field-121"
id="autoscaleRUValueField"
max="9007199254740991"
min={1000}
@@ -1983,11 +2021,11 @@ exports[`ThroughputInput Pane should render Default properly 1`] = `
</TextFieldBase>
</StyledTextFieldBase>
<Text
key=".0:$.3"
key=".0:$.$.3"
variant="small"
>
<span
className="css-54"
className="css-110"
>
Your
container

View File

@@ -59,6 +59,10 @@
}
}
[data-test="Gallery"]{
outline-offset: -1px;
}
.selected {
& > .treeNodeHeader {
background-color: @AccentExtra;

View File

@@ -123,19 +123,6 @@ describe("ContainerSampleGenerator", () => {
await generator.createSampleContainerAsync();
});
it("should not create any sample for Mongo API account", async () => {
const experience = "Sample generation not supported for this API Mongo";
updateUserContext({
databaseAccount: {
properties: {
capabilities: [{ name: "EnableMongo" }],
},
} as DatabaseAccount,
});
expect(ContainerSampleGenerator.createSampleGeneratorAsync(explorerStub)).rejects.toMatch(experience);
});
it("should not create any sample for Table API account", async () => {
const experience = "Sample generation not supported for this API Tables";
updateUserContext({

View File

@@ -3,8 +3,11 @@ import { isPublicInternetAccessAllowed } from "Common/DatabaseAccountUtility";
import { sendMessage } from "Common/MessageHandler";
import { Platform, configContext } from "ConfigContext";
import { MessageTypes } from "Contracts/ExplorerContracts";
import { getCopilotEnabled, isCopilotFeatureRegistered } from "Explorer/QueryCopilot/Shared/QueryCopilotClient";
import { IGalleryItem } from "Juno/JunoClient";
import { allowedNotebookServerUrls, validateEndpoint } from "Utils/EndpointValidation";
import { scheduleRefreshDatabaseResourceToken } from "Platform/Fabric/FabricUtil";
import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
import { allowedNotebookServerUrls, validateEndpoint } from "Utils/EndpointUtils";
import { useQueryCopilot } from "hooks/useQueryCopilot";
import * as ko from "knockout";
import React from "react";
@@ -91,7 +94,7 @@ export default class Explorer {
};
private static readonly MaxNbDatabasesToAutoExpand = 5;
private phoenixClient: PhoenixClient;
public phoenixClient: PhoenixClient;
constructor() {
const startKey: number = TelemetryProcessor.traceStart(Action.InitializeDataExplorer, {
dataExplorerArea: Constants.Areas.ResourceTree,
@@ -262,58 +265,33 @@ export default class Explorer {
// TODO: return result
}
private getRandomInt(max: number) {
return Math.floor(Math.random() * max);
}
public openNPSSurveyDialog(): void {
if (!Platform.Portal) {
return;
}
const NINETY_DAYS_IN_MS = 7776000000;
const ONE_DAY_IN_MS = 86400000;
const isAccountNewerThanNinetyDays = isAccountNewerThanThresholdInMs(
userContext.databaseAccount?.systemData?.createdAt || "",
NINETY_DAYS_IN_MS,
);
const lastSubmitted: string = localStorage.getItem("lastSubmitted");
const SEVEN_DAYS_IN_MS = 604800000;
if (lastSubmitted !== null) {
let lastSubmittedDate: number = parseInt(lastSubmitted);
if (isNaN(lastSubmittedDate)) {
lastSubmittedDate = 0;
}
const nowMs: number = Date.now();
const millisecsSinceLastSubmitted = nowMs - lastSubmittedDate;
if (millisecsSinceLastSubmitted < NINETY_DAYS_IN_MS) {
return;
}
}
// Try Cosmos DB subscription - survey shown to random 25% of users at day 1 in Data Explorer.
// Try Cosmos DB subscription - survey shown to 100% of users at day 1 in Data Explorer.
if (userContext.isTryCosmosDBSubscription) {
if (
isAccountNewerThanThresholdInMs(userContext.databaseAccount?.systemData?.createdAt || "", ONE_DAY_IN_MS) &&
this.getRandomInt(100) < 25
) {
if (isAccountNewerThanThresholdInMs(userContext.databaseAccount?.systemData?.createdAt || "", ONE_DAY_IN_MS)) {
Logger.logInfo(
`Sending message to Portal to check if NPS Survey can be displayed in Try Cosmos DB ${userContext.apiType}`,
"Explorer/openNPSSurveyDialog",
);
sendMessage({ type: MessageTypes.DisplayNPSSurvey });
localStorage.setItem("lastSubmitted", Date.now().toString());
}
} else {
// An existing account is lesser than 90 days old. For existing account show to random 10 % of users in Data Explorer.
if (isAccountNewerThanNinetyDays) {
if (this.getRandomInt(100) < 10) {
sendMessage({ type: MessageTypes.DisplayNPSSurvey });
localStorage.setItem("lastSubmitted", Date.now().toString());
}
} else {
// An existing account is greater than 90 days. For existing account show to random 25 % of users in Data Explorer.
if (this.getRandomInt(100) < 25) {
sendMessage({ type: MessageTypes.DisplayNPSSurvey });
localStorage.setItem("lastSubmitted", Date.now().toString());
}
// Show survey when an existing account is older than 7 days
if (
!isAccountNewerThanThresholdInMs(userContext.databaseAccount?.systemData?.createdAt || "", SEVEN_DAYS_IN_MS)
) {
Logger.logInfo(
`Sending message to Portal to check if NPS Survey can be displayed for existing ${userContext.apiType} account older than 7 days`,
"Explorer/openNPSSurveyDialog",
);
sendMessage({ type: MessageTypes.DisplayNPSSurvey });
}
}
}
@@ -379,6 +357,11 @@ export default class Explorer {
};
public onRefreshResourcesClick = (): void => {
if (configContext.platform === Platform.Fabric) {
scheduleRefreshDatabaseResourceToken(true).then(() => this.refreshAllDatabases());
return;
}
userContext.authType === AuthType.ResourceToken
? this.refreshDatabaseForResourceToken()
: this.refreshAllDatabases();
@@ -403,7 +386,7 @@ export default class Explorer {
this._isInitializingNotebooks = false;
}
public async allocateContainer(poolId: PoolIdType): Promise<void> {
public async allocateContainer(poolId: PoolIdType, mode?: string): Promise<void> {
const shouldUseNotebookStates = poolId === PoolIdType.DefaultPoolId ? true : false;
const notebookServerInfo = shouldUseNotebookStates
? useNotebook.getState().notebookServerInfo
@@ -417,10 +400,6 @@ export default class Explorer {
(notebookServerInfo === undefined ||
(notebookServerInfo && notebookServerInfo.notebookServerEndpoint === undefined))
) {
const provisionData: IProvisionData = {
cosmosEndpoint: userContext?.databaseAccount?.properties?.documentEndpoint,
poolId: shouldUseNotebookStates ? undefined : poolId,
};
const connectionStatus: ContainerConnectionInfo = {
status: ConnectionStatusType.Connecting,
};
@@ -428,14 +407,26 @@ export default class Explorer {
shouldUseNotebookStates && useNotebook.getState().setConnectionInfo(connectionStatus);
let connectionInfo;
let provisionData: IProvisionData;
try {
TelemetryProcessor.traceStart(Action.PhoenixConnection, {
dataExplorerArea: Areas.Notebook,
});
shouldUseNotebookStates
? useNotebook.getState().setIsAllocating(true)
: useQueryCopilot.getState().setIsAllocatingContainer(true);
if (shouldUseNotebookStates) {
useNotebook.getState().setIsAllocating(true);
provisionData = {
cosmosEndpoint: userContext?.databaseAccount?.properties?.documentEndpoint,
poolId: undefined,
};
} else {
useQueryCopilot.getState().setIsAllocatingContainer(true);
provisionData = {
poolId: poolId,
databaseId: useTabs.getState().activeTab.collection.databaseId,
containerId: useTabs.getState().activeTab.collection.id(),
mode: mode,
};
}
connectionInfo = await this.phoenixClient.allocateContainer(provisionData);
if (!connectionInfo?.data?.phoenixServiceUrl) {
throw new Error(`PhoenixServiceUrl is invalid!`);
@@ -451,19 +442,21 @@ export default class Explorer {
error: getErrorMessage(error),
errorStack: getErrorStack(error),
});
connectionStatus.status = ConnectionStatusType.Failed;
shouldUseNotebookStates
? useNotebook.getState().resetContainerConnection(connectionStatus)
: useQueryCopilot.getState().resetContainerConnection();
if (error?.status === HttpStatusCodes.Forbidden && error.message) {
useDialog.getState().showOkModalDialog("Connection Failed", `${error.message}`);
} else {
useDialog
.getState()
.showOkModalDialog(
"Connection Failed",
"We are unable to connect to the temporary workspace. Please try again in a few minutes. If the error persists, file a support ticket.",
);
if (shouldUseNotebookStates) {
connectionStatus.status = ConnectionStatusType.Failed;
shouldUseNotebookStates
? useNotebook.getState().resetContainerConnection(connectionStatus)
: useQueryCopilot.getState().resetContainerConnection();
if (error?.status === HttpStatusCodes.Forbidden && error.message) {
useDialog.getState().showOkModalDialog("Connection Failed", `${error.message}`);
} else {
useDialog
.getState()
.showOkModalDialog(
"Connection Failed",
"We are unable to connect to the temporary workspace. Please try again in a few minutes. If the error persists, file a support ticket.",
);
}
}
throw error;
} finally {
@@ -477,11 +470,11 @@ export default class Explorer {
}
}
private async setNotebookInfo(
public async setNotebookInfo(
shouldUseNotebookStates: boolean,
connectionInfo: IResponse<IPhoenixServiceInfo>,
connectionStatus: DataModels.ContainerConnectionInfo,
) {
): Promise<void> {
const containerData = {
forwardingId: connectionInfo.data.forwardingId,
dbAccountName: userContext.databaseAccount.name,
@@ -502,6 +495,7 @@ export default class Explorer {
shouldUseNotebookStates
? useNotebook.getState().setNotebookServerInfo(noteBookServerInfo)
: useQueryCopilot.getState().setNotebookServerInfo(noteBookServerInfo);
shouldUseNotebookStates &&
this.notebookManager?.notebookClient
.getMemoryUsage()
@@ -1364,6 +1358,24 @@ export default class Explorer {
await this.refreshSampleData();
}
public async configureCopilot(): Promise<void> {
if (userContext.apiType !== "SQL" || !userContext.subscriptionId) {
return;
}
const copilotEnabledPromise = getCopilotEnabled();
const copilotUserDBEnabledPromise = isCopilotFeatureRegistered(userContext.subscriptionId);
const [copilotEnabled, copilotUserDBEnabled] = await Promise.all([
copilotEnabledPromise,
copilotUserDBEnabledPromise,
]);
const copilotSampleDBEnabled = LocalStorageUtility.getEntryString(StorageKey.CopilotSampleDBEnabled) === "true";
useQueryCopilot.getState().setCopilotEnabled(copilotEnabled && copilotUserDBEnabled);
useQueryCopilot.getState().setCopilotUserDBEnabled(copilotUserDBEnabled);
useQueryCopilot
.getState()
.setCopilotSampleDBEnabled(copilotEnabled && copilotUserDBEnabled && copilotSampleDBEnabled);
}
public async refreshSampleData(): Promise<void> {
if (!userContext.sampleDataConnectionInfo) {
return;

View File

@@ -367,7 +367,7 @@ describe("GraphExplorer", () => {
});
it("should submit g.V() as docdb query with proper parameters", () => {
expect(queryDocuments).toBeCalledWith("databaseId", "collectionId", DOCDB_G_DOT_V_QUERY, {
expect(queryDocuments).toHaveBeenCalledWith("databaseId", "collectionId", DOCDB_G_DOT_V_QUERY, {
maxItemCount: GraphExplorer.ROOT_LIST_PAGE_SIZE,
enableCrossPartitionQuery: true,
});
@@ -404,7 +404,7 @@ describe("GraphExplorer", () => {
});
it("should submit g.V() as docdb query with proper parameters", () => {
expect(queryDocuments).toBeCalledWith("databaseId", "collectionId", DOCDB_G_DOT_V_QUERY, {
expect(queryDocuments).toHaveBeenCalledWith("databaseId", "collectionId", DOCDB_G_DOT_V_QUERY, {
maxItemCount: GraphExplorer.ROOT_LIST_PAGE_SIZE,
enableCrossPartitionQuery: true,
});

View File

@@ -1163,15 +1163,12 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
)}"`,
).then(
(documents: DataModels.DocumentId[]) => {
$.each(
documents,
(index: number, doc: { _graph_icon_property_value: string; icon: string; format: string }) => {
newIconsMap[doc["_graph_icon_property_value"]] = {
data: doc["icon"],
format: doc["format"],
};
},
);
$.each(documents, (index: number, doc: any) => {
newIconsMap[doc["_graph_icon_property_value"]] = {
data: doc["icon"],
format: doc["format"],
};
});
// Update graph configuration
this.setState({

View File

@@ -43,7 +43,7 @@ describe("Graph Style Component", () => {
expect(asFragment).toMatchSnapshot();
});
it("should render node properties dropdown list ", () => {
it("should render node properties dropdown list", () => {
const dropDownList = screen.getByText("Show vertex (node) as");
expect(dropDownList).toBeDefined();
});

View File

@@ -24,16 +24,21 @@ interface Props {
export interface CommandBarStore {
contextButtons: CommandButtonComponentProps[];
setContextButtons: (contextButtons: CommandButtonComponentProps[]) => void;
isHidden: boolean;
setIsHidden: (isHidden: boolean) => void;
}
export const useCommandBar: UseStore<CommandBarStore> = create((set) => ({
contextButtons: [],
setContextButtons: (contextButtons: CommandButtonComponentProps[]) => set((state) => ({ ...state, contextButtons })),
isHidden: false,
setIsHidden: (isHidden: boolean) => set((state) => ({ ...state, isHidden })),
}));
export const CommandBar: React.FC<Props> = ({ container }: Props) => {
const selectedNodeState = useSelectedNode();
const buttons = useCommandBar((state) => state.contextButtons);
const isHidden = useCommandBar((state) => state.isHidden);
const backgroundColor = StyleConstants.BaseLight;
if (userContext.apiType === "Postgres" || userContext.apiType === "VCoreMongo") {
@@ -42,7 +47,7 @@ export const CommandBar: React.FC<Props> = ({ container }: Props) => {
? CommandBarComponentButtonFactory.createPostgreButtons(container)
: CommandBarComponentButtonFactory.createVCoreMongoButtons(container);
return (
<div className="commandBarContainer">
<div className="commandBarContainer" style={{ display: isHidden ? "none" : "initial" }}>
<FluentCommandBar
ariaLabel="Use left and right arrow keys to navigate between commands"
items={CommandBarUtil.convertButton(buttons, backgroundColor)}
@@ -91,7 +96,7 @@ export const CommandBar: React.FC<Props> = ({ container }: Props) => {
? {
root: {
backgroundColor: "transparent",
padding: "0px 14px 0px 14px",
padding: "2px 8px 0px 8px",
},
}
: {
@@ -101,7 +106,7 @@ export const CommandBar: React.FC<Props> = ({ container }: Props) => {
};
return (
<div className="commandBarContainer">
<div className="commandBarContainer" style={{ display: isHidden ? "none" : "initial" }}>
<FluentCommandBar
ariaLabel="Use left and right arrow keys to navigate between commands"
items={uiFabricStaticButtons.concat(uiFabricTabsButtons)}

View File

@@ -345,7 +345,7 @@ describe("CommandBarComponentButtonFactory tests", () => {
describe("Open Postgres and vCore Mongo buttons", () => {
const openPostgresShellButtonLabel = "Open PSQL shell";
const openVCoreMongoShellButtonLabel = "Open MongoDB (vcore) shell";
const openVCoreMongoShellButtonLabel = "Open MongoDB (vCore) shell";
beforeAll(() => {
mockExplorer = {} as Explorer;

View File

@@ -1,6 +1,3 @@
import { Action } from "Shared/Telemetry/TelemetryConstants";
import { traceOpen } from "Shared/Telemetry/TelemetryProcessor";
import { ReactTabKind, useTabs } from "hooks/useTabs";
import * as React from "react";
import AddCollectionIcon from "../../../../images/AddCollection.svg";
import AddDatabaseIcon from "../../../../images/AddDatabase.svg";
@@ -50,31 +47,36 @@ export function createStaticCommandBarButtons(
return createStaticCommandBarButtonsForResourceToken(container, selectedNodeState);
}
const newCollectionBtn = createNewCollectionGroup(container);
const buttons: CommandButtonComponentProps[] = [];
buttons.push(newCollectionBtn);
if (
configContext.platform !== Platform.Fabric &&
userContext.apiType !== "Tables" &&
userContext.apiType !== "Cassandra"
) {
const addSynapseLink = createOpenSynapseLinkDialogButton(container);
if (addSynapseLink) {
// Avoid starting with a divider
const addDivider = () => {
if (buttons.length > 0) {
buttons.push(createDivider());
buttons.push(addSynapseLink);
}
};
if (configContext.platform !== Platform.Fabric) {
const newCollectionBtn = createNewCollectionGroup(container);
buttons.push(newCollectionBtn);
if (userContext.apiType !== "Tables" && userContext.apiType !== "Cassandra") {
const addSynapseLink = createOpenSynapseLinkDialogButton(container);
if (addSynapseLink) {
addDivider();
buttons.push(addSynapseLink);
}
}
if (userContext.apiType !== "Tables") {
newCollectionBtn.children = [createNewCollectionGroup(container)];
const newDatabaseBtn = createNewDatabase(container);
newCollectionBtn.children.push(newDatabaseBtn);
}
}
if (userContext.apiType !== "Tables") {
newCollectionBtn.children = [createNewCollectionGroup(container)];
const newDatabaseBtn = createNewDatabase(container);
newCollectionBtn.children.push(newDatabaseBtn);
}
if (useNotebook.getState().isNotebookEnabled) {
buttons.push(createDivider());
addDivider();
const notebookButtons: CommandButtonComponentProps[] = [];
const newNotebookButton = createNewNotebookButton(container);
@@ -128,12 +130,12 @@ export function createStaticCommandBarButtons(
const isQuerySupported = userContext.apiType === "SQL" || userContext.apiType === "Gremlin";
if (isQuerySupported) {
buttons.push(createDivider());
addDivider();
const newSqlQueryBtn = createNewSQLQueryButton(selectedNodeState);
buttons.push(newSqlQueryBtn);
}
if (isQuerySupported && selectedNodeState.findSelectedCollection()) {
if (isQuerySupported && selectedNodeState.findSelectedCollection() && configContext.platform !== Platform.Fabric) {
const openQueryBtn = createOpenQueryButton(container);
openQueryBtn.children = [createOpenQueryButton(container), createOpenQueryFromDiskButton()];
buttons.push(openQueryBtn);
@@ -194,18 +196,22 @@ export function createContextCommandBarButtons(
}
export function createControlCommandBarButtons(container: Explorer): CommandButtonComponentProps[] {
const buttons: CommandButtonComponentProps[] = [
{
iconSrc: SettingsIcon,
iconAlt: "Settings",
onCommandClick: () => useSidePanel.getState().openSidePanel("Settings", <SettingsPane />),
commandButtonLabel: undefined,
ariaLabel: "Settings",
tooltipText: "Settings",
hasPopup: true,
disabled: false,
},
];
const buttons: CommandButtonComponentProps[] =
configContext.platform === Platform.Fabric && userContext.fabricContext?.isReadOnly
? []
: [
{
iconSrc: SettingsIcon,
iconAlt: "Settings",
onCommandClick: () =>
useSidePanel.getState().openSidePanel("Settings", <SettingsPane explorer={container} />),
commandButtonLabel: undefined,
ariaLabel: "Settings",
tooltipText: "Settings",
hasPopup: true,
disabled: false,
},
];
const showOpenFullScreen =
configContext.platform === Platform.Portal && !isRunningOnNationalCloud() && userContext.apiType !== "Gremlin";
@@ -332,13 +338,8 @@ function createNewSQLQueryButton(selectedNodeState: SelectedNodeState): CommandB
iconSrc: AddSqlQueryIcon,
iconAlt: label,
onCommandClick: () => {
if (useSelectedNode.getState().isQueryCopilotCollectionSelected()) {
useTabs.getState().openAndActivateReactTab(ReactTabKind.QueryCopilot);
traceOpen(Action.OpenQueryCopilotFromNewQuery, { apiType: userContext.apiType });
} else {
const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
selectedCollection && selectedCollection.onNewQueryClick(selectedCollection);
}
const selectedCollection: ViewModels.Collection = selectedNodeState.findSelectedCollection();
selectedCollection && selectedCollection.onNewQueryClick(selectedCollection);
},
commandButtonLabel: label,
ariaLabel: label,
@@ -518,7 +519,7 @@ function createOpenTerminalButtonByKind(
case ViewModels.TerminalKind.Postgres:
return "PSQL";
case ViewModels.TerminalKind.VCoreMongo:
return "MongoDB (vcore)";
return "MongoDB (vCore)";
default:
return "";
}

View File

@@ -35,7 +35,7 @@ describe("CommandBarUtil tests", () => {
// Click gets called
converted.onClick();
expect(btn.onCommandClick).toBeCalled();
expect(btn.onCommandClick).toHaveBeenCalled();
});
it("should convert NavbarButtonConfig to split button", () => {

View File

@@ -6,6 +6,7 @@ import {
IDropdownOption,
IDropdownStyles,
} from "@fluentui/react";
import { useQueryCopilot } from "hooks/useQueryCopilot";
import * as React from "react";
import _ from "underscore";
import ChevronDownIcon from "../../../../images/Chevron_down.svg";
@@ -24,7 +25,10 @@ import { MemoryTracker } from "./MemoryTrackerComponent";
* @param btns
*/
export const convertButton = (btns: CommandButtonComponentProps[], backgroundColor: string): ICommandBarItemProps[] => {
const buttonHeightPx = StyleConstants.CommandBarButtonHeight;
const buttonHeightPx =
configContext.platform == Platform.Fabric
? StyleConstants.FabricCommandBarButtonHeight
: StyleConstants.CommandBarButtonHeight;
const hoverColor =
configContext.platform == Platform.Fabric ? StyleConstants.FabricAccentLight : StyleConstants.AccentLight;
@@ -57,7 +61,11 @@ export const convertButton = (btns: CommandButtonComponentProps[], backgroundCol
},
onClick: (ev?: React.MouseEvent<HTMLElement, MouseEvent> | React.KeyboardEvent<HTMLElement>) => {
btn.onCommandClick(ev);
TelemetryProcessor.trace(Action.ClickCommandBarButton, ActionModifiers.Mark, { label });
let copilotEnabled = false;
if (useQueryCopilot.getState().copilotEnabled && useQueryCopilot.getState().copilotUserDBEnabled) {
copilotEnabled = useQueryCopilot.getState().copilotEnabledforExecution;
}
TelemetryProcessor.trace(Action.ClickCommandBarButton, ActionModifiers.Mark, { label, copilotEnabled });
},
key: `${btn.commandButtonLabel}${index}`,
text: label,
@@ -107,6 +115,7 @@ export const convertButton = (btns: CommandButtonComponentProps[], backgroundCol
splitButtonContainer: {
marginLeft: 5,
marginRight: 5,
height: buttonHeightPx,
},
},
className: btn.className,

View File

@@ -1,14 +1,14 @@
import { makeNotebookRecord } from "@nteract/commutable";
import { actions, state } from "@nteract/core";
import * as Immutable from "immutable";
import { StateObservable } from "redux-observable";
import { Subject, of } from "rxjs";
import { toArray } from "rxjs/operators";
import { makeNotebookRecord } from "@nteract/commutable";
import { actions, state, epics } from "@nteract/core";
import * as sinon from "sinon";
import { CdbAppState, makeCdbRecord } from "./types";
import { launchWebSocketKernelEpic } from "./epics";
import { NotebookUtil } from "../NotebookUtil";
import { launchWebSocketKernelEpic } from "./epics";
import { CdbAppState, makeCdbRecord } from "./types";
import { sessions } from "rx-jupyter";
@@ -117,7 +117,7 @@ describe("launchWebSocketKernelEpic", () => {
const kernelRef = "fake";
it("launches remote kernels", async () => {
const state$ = new StateObservable(new Subject<CdbAppState>(), initialState);
const state$ = new StateObservable(new Subject<CdbAppState>() as any, initialState);
const cwd = "/";
const kernelId = "123";
@@ -183,7 +183,7 @@ describe("launchWebSocketKernelEpic", () => {
});
it("launches any kernel with no kernelspecs in the state", async () => {
const state$ = new StateObservable(new Subject<CdbAppState>(), initialState);
const state$ = new StateObservable(new Subject<CdbAppState>() as any, initialState);
const cwd = "/";
const kernelId = "123";
@@ -236,7 +236,7 @@ describe("launchWebSocketKernelEpic", () => {
});
it("launches no kernel if no kernel is specified and state has no kernelspecs", async () => {
const state$ = new StateObservable(new Subject<CdbAppState>(), initialState);
const state$ = new StateObservable(new Subject<CdbAppState>() as any, initialState);
const cwd = "/";
const kernelId = "123";
@@ -289,7 +289,7 @@ describe("launchWebSocketKernelEpic", () => {
});
it("emits an error if backend returns an error", async () => {
const state$ = new StateObservable(new Subject<CdbAppState>(), initialState);
const state$ = new StateObservable(new Subject<CdbAppState>() as any, initialState);
const cwd = "/";
const action$ = of(
@@ -388,7 +388,7 @@ describe("launchWebSocketKernelEpic", () => {
});
it("launches supported kernel in kernelspecs", async () => {
const state$ = new StateObservable(new Subject<CdbAppState>(), initialState);
const state$ = new StateObservable(new Subject<CdbAppState>() as any, initialState);
const action$ = of(
actions.launchKernelByName({
@@ -409,7 +409,7 @@ describe("launchWebSocketKernelEpic", () => {
});
it("launches undefined kernel uses default kernel from kernelspecs", async () => {
const state$ = new StateObservable(new Subject<CdbAppState>(), initialState);
const state$ = new StateObservable(new Subject<CdbAppState>() as any, initialState);
const action$ = of(
actions.launchKernelByName({
@@ -431,7 +431,7 @@ describe("launchWebSocketKernelEpic", () => {
});
it("launches unsupported kernel uses default kernel from kernelspecs", async () => {
const state$ = new StateObservable(new Subject<CdbAppState>(), initialState);
const state$ = new StateObservable(new Subject<CdbAppState>() as any, initialState);
const action$ = of(
actions.launchKernelByName({
@@ -453,7 +453,7 @@ describe("launchWebSocketKernelEpic", () => {
});
it("launches unsupported kernel uses kernelspecs with similar name", async () => {
const state$ = new StateObservable(new Subject<CdbAppState>(), initialState);
const state$ = new StateObservable(new Subject<CdbAppState>() as any, initialState);
const action$ = of(
actions.launchKernelByName({

View File

@@ -69,8 +69,8 @@ const addInitialCodeCellEpic = (
state$: StateObservable<AppState>,
): Observable<{} | actions.CreateCellBelow> => {
return action$.pipe(
ofType(actions.FETCH_CONTENT_FULFILLED),
mergeMap((action) => {
ofType(actions.FETCH_CONTENT_FULFILLED) as any,
mergeMap((action: any) => {
const state = state$.value;
const contentRef = action.payload.contentRef;
const model = selectors.model(state, { contentRef });
@@ -116,7 +116,7 @@ const formWebSocketURL = (serverConfig: NotebookServiceConfig, kernelId: string,
*/
export const acquireKernelInfoEpic = (action$: Observable<actions.NewKernelAction>) => {
return action$.pipe(
ofType(actions.LAUNCH_KERNEL_SUCCESSFUL),
ofType(actions.LAUNCH_KERNEL_SUCCESSFUL) as any,
switchMap((action: actions.NewKernelAction) => {
const {
payload: {
@@ -271,9 +271,9 @@ export const launchWebSocketKernelEpic = (
state$: StateObservable<CdbAppState>,
) => {
return action$.pipe(
ofType(actions.LAUNCH_KERNEL_BY_NAME),
ofType(actions.LAUNCH_KERNEL_BY_NAME) as any,
// Only accept jupyter servers for the host with this epic
filter(() => selectors.isCurrentHostJupyter(state$.value)),
filter(() => selectors.isCurrentHostJupyter(state$.value as never)),
switchMap((action: actions.LaunchKernelByNameAction) => {
const state = state$.value;
const host = selectors.currentHost(state);
@@ -382,7 +382,7 @@ export const restartWebSocketKernelEpic = (
state$: StateObservable<AppState>,
) =>
action$.pipe(
ofType(actions.RESTART_KERNEL),
ofType(actions.RESTART_KERNEL) as any,
concatMap((action: actions.RestartKernel) => {
const state = state$.value;
@@ -449,7 +449,7 @@ export const restartWebSocketKernelEpic = (
});
const awaitKernelReady = action$.pipe(
ofType(actions.LAUNCH_KERNEL_SUCCESSFUL),
ofType(actions.LAUNCH_KERNEL_SUCCESSFUL) as any,
filter((action: actions.NewKernelAction | actions.RestartKernel) => action.payload.kernelRef === newKernelRef),
take(1),
timeout(60000), // If kernel doesn't come up within this interval we will abort follow-on actions.
@@ -492,9 +492,9 @@ const changeWebSocketKernelEpic = (
state$: StateObservable<AppState>,
) => {
return action$.pipe(
ofType(actions.CHANGE_KERNEL_BY_NAME),
ofType(actions.CHANGE_KERNEL_BY_NAME) as any,
// Only accept jupyter servers for the host with this epic
filter(() => selectors.isCurrentHostJupyter(state$.value)),
filter(() => selectors.isCurrentHostJupyter(state$.value as never)),
switchMap((action: actions.ChangeKernelByName) => {
const {
payload: { contentRef, oldKernelRef, kernelSpecName },
@@ -574,8 +574,8 @@ const focusInitialCodeCellEpic = (
state$: StateObservable<AppState>,
): Observable<{} | actions.FocusCell> => {
return action$.pipe(
ofType(actions.CREATE_CELL_APPEND),
mergeMap((action) => {
ofType(actions.CREATE_CELL_APPEND) as any,
mergeMap((action: any) => {
const state = state$.value;
const contentRef = action.payload.contentRef;
const model = selectors.model(state, { contentRef });
@@ -616,8 +616,8 @@ const notificationsToUserEpic = (action$: Observable<any>, state$: StateObservab
actions.SAVE_FULFILLED,
actions.SAVE_FAILED,
actions.FETCH_CONTENT_FAILED,
),
mergeMap((action) => {
) as any,
mergeMap((action: any) => {
switch (action.type) {
case actions.RESTART_KERNEL_SUCCESSFUL: {
const title = "Kernel restart";
@@ -662,8 +662,8 @@ const handleKernelConnectionLostEpic = (
state$: StateObservable<CdbAppState>,
): Observable<CdbActions.UpdateKernelRestartDelayAction | actions.RestartKernel | {}> => {
return action$.pipe(
ofType(actions.UPDATE_DISPLAY_FAILED),
mergeMap((action) => {
ofType(actions.UPDATE_DISPLAY_FAILED) as any,
mergeMap((action: any) => {
const state = state$.value;
const msg = "Notebook was disconnected from kernel";
@@ -721,8 +721,8 @@ export const cleanKernelOnConnectionLostEpic = (
state$: StateObservable<AppState>,
): Observable<actions.KillKernelSuccessful> => {
return action$.pipe(
ofType(actions.UPDATE_DISPLAY_FAILED),
switchMap((action) => {
ofType(actions.UPDATE_DISPLAY_FAILED) as any,
switchMap((action: any) => {
const contentRef = action.payload.contentRef;
const kernelRef = selectors.kernelRefByContentRef(state$.value, { contentRef });
return of(
@@ -744,8 +744,8 @@ const executeFocusedCellAndFocusNextEpic = (
state$: StateObservable<AppState>,
): Observable<{} | actions.FocusNextCellEditor> => {
return action$.pipe(
ofType(CdbActions.EXECUTE_FOCUSED_CELL_AND_FOCUS_NEXT),
mergeMap((action) => {
ofType(CdbActions.EXECUTE_FOCUSED_CELL_AND_FOCUS_NEXT) as any,
mergeMap((action: any) => {
const contentRef = action.payload.contentRef;
return concat(
of(actions.executeFocusedCell({ contentRef })),
@@ -765,8 +765,8 @@ const closeUnsupportedMimetypesEpic = (
state$: StateObservable<AppState>,
): Observable<{}> => {
return action$.pipe(
ofType(actions.FETCH_CONTENT_FULFILLED),
mergeMap((action) => {
ofType(actions.FETCH_CONTENT_FULFILLED) as any,
mergeMap((action: any) => {
const mimetype = action.payload.model.mimetype;
if (!TextFile.handles(mimetype)) {
const filepath = action.payload.filepath;
@@ -796,8 +796,8 @@ const closeContentFailedToFetchEpic = (
state$: StateObservable<AppState>,
): Observable<{}> => {
return action$.pipe(
ofType(actions.FETCH_CONTENT_FAILED),
mergeMap((action) => {
ofType(actions.FETCH_CONTENT_FAILED) as any,
mergeMap((action: any) => {
const filepath = action.payload.filepath;
// Close tab and show error message
useTabs
@@ -818,7 +818,7 @@ const traceNotebookTelemetryEpic = (
state$: StateObservable<CdbAppState>,
): Observable<{}> => {
return action$.pipe(
ofType(cdbActions.TRACE_NOTEBOOK_TELEMETRY),
ofType(cdbActions.TRACE_NOTEBOOK_TELEMETRY) as any,
mergeMap((action: cdbActions.TraceNotebookTelemetryAction) => {
const state = state$.value;
@@ -844,7 +844,7 @@ const traceNotebookInfoEpic = (
state$: StateObservable<AppState>,
): Observable<{} | cdbActions.TraceNotebookTelemetryAction> => {
return action$.pipe(
ofType(actions.FETCH_CONTENT_FULFILLED),
ofType(actions.FETCH_CONTENT_FULFILLED) as any,
mergeMap((action: { payload: any }) => {
const state = state$.value;
const contentRef = action.payload.contentRef;
@@ -897,7 +897,7 @@ const traceNotebookKernelEpic = (
state$: StateObservable<AppState>,
): Observable<cdbActions.TraceNotebookTelemetryAction> => {
return action$.pipe(
ofType(actions.LAUNCH_KERNEL_SUCCESSFUL),
ofType(actions.LAUNCH_KERNEL_SUCCESSFUL) as any,
mergeMap((action: { payload: any; type: string }) => {
return of(
cdbActions.traceNotebookTelemetry({
@@ -917,8 +917,8 @@ const resetCellStatusOnExecuteCanceledEpic = (
state$: StateObservable<AppState>,
): Observable<actions.UpdateCellStatus> => {
return action$.pipe(
ofType(actions.EXECUTE_CANCELED),
mergeMap((action) => {
ofType(actions.EXECUTE_CANCELED) as any,
mergeMap((action: any) => {
const contentRef = action.payload.contentRef;
const model = state$.value.core.entities.contents.byRef.get(contentRef).model;
let busyCellIds: string[] = [];
@@ -960,8 +960,8 @@ export function autoSaveCurrentContentEpic(
state$: StateObservable<AppState>,
): Observable<actions.Save> {
return state$.pipe(
map((state) => autoSaveInterval(state)),
switchMap((time) => interval(time)),
map((state) => autoSaveInterval(state)) as any,
switchMap((time) => interval(time as number)) as any,
mergeMap(() => {
const state = state$.value;
return from(
@@ -976,7 +976,7 @@ export function autoSaveCurrentContentEpic(
)
.keys(),
);
}),
}) as any,
filter((contentRef: ContentRef) => {
const model = selectors.model(state$.value, { contentRef });
const content = selectors.content(state$.value, { contentRef });
@@ -985,12 +985,12 @@ export function autoSaveCurrentContentEpic(
model.type === "notebook" &&
NotebookUtil.getContentProviderType(content.filepath) !== NotebookContentProviderType.JupyterContentProviderType
) {
return selectors.notebook.isDirty(model);
return selectors.notebook.isDirty(model as never);
}
return false;
}),
map((contentRef: ContentRef) => actions.save({ contentRef })),
);
}) as any,
map((contentRef: ContentRef) => actions.save({ contentRef })) as any,
) as any;
}
export const allEpics = [

View File

@@ -34,11 +34,11 @@ export default function configureStore(
const protect = (epic: Epic) => {
return (action$: Observable<any>, state$: any, dependencies: any) =>
epic(action$, state$, dependencies).pipe(
epic(action$ as any, state$, dependencies).pipe(
catchError((error, caught) => {
traceFailure("Epic failure", error);
return caught;
}),
}) as any,
);
};
@@ -52,7 +52,7 @@ export default function configureStore(
};
const protectEpics = (epics: Epic[]): Epic[] => {
return epics.map((epic) => protect(epic));
return epics.map((epic) => protect(epic)) as any;
};
const filteredCoreEpics = getCoreEpics(autoStartKernelOnNotebookOpen);
@@ -64,7 +64,7 @@ export default function configureStore(
core: coreReducer as any,
cdb: cdbReducer,
},
epics: protectEpics([...filteredCoreEpics, ...allEpics]),
epics: protectEpics([...filteredCoreEpics, ...allEpics] as any),
epicDependencies: { contentProvider },
epicMiddleware: customMiddlewares.concat(catchErrorMiddleware),
enhancer: composeEnhancers,
@@ -106,5 +106,5 @@ export const getCoreEpics = (autoStartKernelOnNotebookOpen: boolean): Epic[] =>
filteredCoreEpics.push(coreEpics.launchKernelWhenNotebookSetEpic);
}
return filteredCoreEpics;
return filteredCoreEpics as any;
};

View File

@@ -154,7 +154,7 @@ function openPane(action: ActionContracts.OpenPane, explorer: Explorer) {
action.paneKind === ActionContracts.PaneKind.GlobalSettings ||
action.paneKind === ActionContracts.PaneKind[ActionContracts.PaneKind.GlobalSettings]
) {
useSidePanel.getState().openSidePanel("Settings", <SettingsPane />);
useSidePanel.getState().openSidePanel("Settings", <SettingsPane explorer={explorer} />);
}
}

View File

@@ -114,7 +114,8 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
super(props);
this.state = {
createNewDatabase: userContext.apiType !== "Tables" && !this.props.databaseId,
createNewDatabase:
userContext.apiType !== "Tables" && configContext.platform !== Platform.Fabric && !this.props.databaseId,
newDatabaseId: props.isQuickstart ? this.getSampleDBName() : "",
isSharedThroughputChecked: this.getSharedThroughputDefault(),
selectedDatabaseId:
@@ -274,36 +275,38 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
</TooltipHost>
</Stack>
<Stack horizontal verticalAlign="center">
<div role="radiogroup">
<input
className="panelRadioBtn"
checked={this.state.createNewDatabase}
aria-label="Create new database"
aria-checked={this.state.createNewDatabase}
name="databaseType"
type="radio"
role="radio"
id="databaseCreateNew"
tabIndex={0}
onChange={this.onCreateNewDatabaseRadioBtnChange.bind(this)}
/>
<span className="panelRadioBtnLabel">Create new</span>
{configContext.platform !== Platform.Fabric && (
<Stack horizontal verticalAlign="center">
<div role="radiogroup">
<input
className="panelRadioBtn"
checked={this.state.createNewDatabase}
aria-label="Create new database"
aria-checked={this.state.createNewDatabase}
name="databaseType"
type="radio"
role="radio"
id="databaseCreateNew"
tabIndex={0}
onChange={this.onCreateNewDatabaseRadioBtnChange.bind(this)}
/>
<span className="panelRadioBtnLabel">Create new</span>
<input
className="panelRadioBtn"
checked={!this.state.createNewDatabase}
aria-label="Use existing database"
aria-checked={!this.state.createNewDatabase}
name="databaseType"
type="radio"
role="radio"
tabIndex={0}
onChange={this.onUseExistingDatabaseRadioBtnChange.bind(this)}
/>
<span className="panelRadioBtnLabel">Use existing</span>
</div>
</Stack>
<input
className="panelRadioBtn"
checked={!this.state.createNewDatabase}
aria-label="Use existing database"
aria-checked={!this.state.createNewDatabase}
name="databaseType"
type="radio"
role="radio"
tabIndex={0}
onChange={this.onUseExistingDatabaseRadioBtnChange.bind(this)}
/>
<span className="panelRadioBtnLabel">Use existing</span>
</div>
</Stack>
)}
{this.state.createNewDatabase && (
<Stack className="panelGroupSpacing">
@@ -1428,8 +1431,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
this.setState({ isExecuting: false });
TelemetryProcessor.traceSuccess(Action.CreateCollection, telemetryData, startKey);
useSidePanel.getState().closeSidePanel();
// open NPS Survey Dialog once the collection is created
this.props.explorer.openNPSSurveyDialog();
} catch (error) {
const errorMessage: string = getErrorMessage(error);
this.setState({ isExecuting: false, errorMessage, showErrorDetails: true });

View File

@@ -1,12 +1,11 @@
import { Checkbox, Stack, Text, TextField } from "@fluentui/react";
import React, { FunctionComponent, useEffect, useState } from "react";
import * as Constants from "../../../Common/Constants";
import { createDatabase } from "../../../Common/dataAccess/createDatabase";
import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils";
import { InfoTooltip } from "../../../Common/Tooltip/InfoTooltip";
import { createDatabase } from "../../../Common/dataAccess/createDatabase";
import * as DataModels from "../../../Contracts/DataModels";
import { SubscriptionType } from "../../../Contracts/SubscriptionType";
import { useSidePanel } from "../../../hooks/useSidePanel";
import * as SharedConstants from "../../../Shared/Constants";
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
@@ -14,6 +13,7 @@ import { userContext } from "../../../UserContext";
import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
import { isServerlessAccount } from "../../../Utils/CapabilityUtils";
import { getUpsellMessage } from "../../../Utils/PricingUtils";
import { useSidePanel } from "../../../hooks/useSidePanel";
import { ThroughputInput } from "../../Controls/ThroughputInput/ThroughputInput";
import Explorer from "../../Explorer";
import { useDatabases } from "../../useDatabases";
@@ -63,9 +63,6 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
},
subscriptionType: SubscriptionType[subscriptionType],
subscriptionQuotaId: userContext.quotaId,
defaultsCheck: {
flight: userContext.addCollectionFlight,
},
dataExplorerArea: Constants.Areas.ContextualPane,
};
@@ -75,7 +72,6 @@ export const AddDatabasePanel: FunctionComponent<AddDatabasePaneProps> = ({
subscriptionQuotaId: userContext.quotaId,
defaultsCheck: {
throughput,
flight: userContext.addCollectionFlight,
},
dataExplorerArea: Constants.Areas.ContextualPane,
};

View File

@@ -23,7 +23,7 @@ describe("Cassandra add collection pane test", () => {
expect(screen.getByRole("combobox", { name: "Choose existing keyspace id" })).toBeDefined();
});
it("enter Keyspace name ", () => {
it("enter Keyspace name", () => {
fireEvent.change(screen.getByRole("textbox", { name: "Keyspace id" }), { target: { value: "table1" } });
expect(screen.getByText("CREATE TABLE table1.")).toBeDefined();
});

View File

@@ -59,7 +59,6 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
defaultsCheck: {
storage: "u",
throughput: newKeySpaceThroughput || tableThroughput,
flight: userContext.addCollectionFlight,
},
dataExplorerArea: Constants.Areas.ContextualPane,
};
@@ -314,7 +313,7 @@ export const CassandraAddCollectionPane: FunctionComponent<CassandraAddCollectio
</Stack>
{!isServerlessAccount() && isKeyspaceShared && !keyspaceCreateNew && (
<Stack>
<Stack horizontal verticalAlign="center">
<input
type="checkbox"
id="tableSharedThroughput"

View File

@@ -0,0 +1,396 @@
import {
DefaultButton,
DirectionalHint,
Dropdown,
IDropdownOption,
Icon,
IconButton,
Link,
Stack,
Text,
TooltipHost,
} from "@fluentui/react";
import * as Constants from "Common/Constants";
import { handleError } from "Common/ErrorHandlingUtils";
import { createCollection } from "Common/dataAccess/createCollection";
import { DataTransferParams, initiateDataTransfer } from "Common/dataAccess/dataTransfers";
import * as DataModels from "Contracts/DataModels";
import * as ViewModels from "Contracts/ViewModels";
import {
getPartitionKeyName,
getPartitionKeyPlaceHolder,
getPartitionKeySubtext,
getPartitionKeyTooltipText,
} from "Explorer/Controls/Settings/SettingsUtils";
import Explorer from "Explorer/Explorer";
import { RightPaneForm } from "Explorer/Panes/RightPaneForm/RightPaneForm";
import { useDatabases } from "Explorer/useDatabases";
import { userContext } from "UserContext";
import { getCollectionName } from "Utils/APITypeUtils";
import { useSidePanel } from "hooks/useSidePanel";
import * as React from "react";
export interface ChangePartitionKeyPaneProps {
sourceDatabase: ViewModels.Database;
sourceCollection: ViewModels.Collection;
explorer: Explorer;
onClose: () => Promise<void>;
}
export const ChangePartitionKeyPane: React.FC<ChangePartitionKeyPaneProps> = ({
sourceDatabase,
sourceCollection,
explorer,
onClose,
}) => {
const [targetCollectionId, setTargetCollectionId] = React.useState<string>();
const [createNewContainer, setCreateNewContainer] = React.useState<boolean>(true);
const [formError, setFormError] = React.useState<string>();
const [isExecuting, setIsExecuting] = React.useState<boolean>(false);
const [subPartitionKeys, setSubPartitionKeys] = React.useState<string[]>([]);
const [partitionKey, setPartitionKey] = React.useState<string>();
const getCollectionOptions = (): IDropdownOption[] => {
return sourceDatabase
.collections()
.filter((collection) => collection.id !== sourceCollection.id)
.map((collection) => ({
key: collection.id(),
text: collection.id(),
}));
};
const submit = async () => {
if (!validateInputs()) {
return;
}
setIsExecuting(true);
try {
createNewContainer && (await createContainer());
await createDataTransferJob();
await onClose();
} catch (error) {
handleError(error, "ChangePartitionKey", "Failed to start data transfer job");
}
setIsExecuting(false);
useSidePanel.getState().closeSidePanel();
};
const validateInputs = (): boolean => {
if (!createNewContainer && !targetCollectionId) {
setFormError("Choose an existing container");
return false;
}
return true;
};
const createDataTransferJob = async () => {
const jobName = `Portal_${targetCollectionId}_${Math.floor(Date.now() / 1000)}`;
const dataTransferParams: DataTransferParams = {
jobName,
apiType: userContext.apiType,
subscriptionId: userContext.subscriptionId,
resourceGroupName: userContext.resourceGroup,
accountName: userContext.databaseAccount.name,
sourceDatabaseName: sourceDatabase.id(),
sourceCollectionName: sourceCollection.id(),
targetDatabaseName: sourceDatabase.id(),
targetCollectionName: targetCollectionId,
};
await initiateDataTransfer(dataTransferParams);
};
const createContainer = async () => {
const partitionKeyString = partitionKey.trim();
const partitionKeyData: DataModels.PartitionKey = partitionKeyString
? {
paths: [partitionKeyString, ...(subPartitionKeys.length > 0 ? subPartitionKeys : [])],
kind: subPartitionKeys.length > 0 ? "MultiHash" : "Hash",
version: 2,
}
: undefined;
const createCollectionParams: DataModels.CreateCollectionParams = {
createNewDatabase: false,
collectionId: targetCollectionId,
databaseId: sourceDatabase.id(),
databaseLevelThroughput: isSelectedDatabaseSharedThroughput(),
offerThroughput: sourceCollection.offer()?.manualThroughput,
autoPilotMaxThroughput: sourceCollection.offer()?.autoscaleMaxThroughput,
partitionKey: partitionKeyData,
};
await createCollection(createCollectionParams);
await explorer.refreshAllDatabases();
};
const isSelectedDatabaseSharedThroughput = (): boolean => {
const selectedDatabase = useDatabases
.getState()
.databases?.find((database) => database.id() === sourceDatabase.id());
return !!selectedDatabase?.offer();
};
return (
<RightPaneForm formError={formError} isExecuting={isExecuting} onSubmit={submit} submitButtonText="OK">
<Stack tokens={{ childrenGap: 10 }} className="panelMainContent">
<Text variant="small">
When changing a containers partition key, you will need to create a destination container with the correct
partition key. You may also select an existing destination container.&nbsp;
<Link
href="https://learn.microsoft.com/en-us/azure/cosmos-db/container-copy#container-copy-within-an-azure-cosmos-db-account"
target="_blank"
underline
>
Learn more
</Link>
</Text>
<Stack>
<Stack horizontal>
<span className="mandatoryStar">*&nbsp;</span>
<Text className="panelTextBold" variant="small">
Database id
</Text>
<TooltipHost
directionalHint={DirectionalHint.bottomLeftEdge}
content={`A database is analogous to a namespace. It is the unit of management for a set of ${getCollectionName(
true,
).toLocaleLowerCase()}.`}
>
<Icon
iconName="Info"
className="panelInfoIcon"
tabIndex={0}
aria-label={`A database is analogous to a namespace. It is the unit of management for a set of ${getCollectionName(
true,
).toLocaleLowerCase()}.`}
/>
</TooltipHost>
</Stack>
<Dropdown
styles={{ title: { height: 27, lineHeight: 27 }, dropdownItem: { fontSize: 12 } }}
style={{ width: 300, fontSize: 12 }}
options={[]}
placeholder={sourceDatabase.id()}
responsiveMode={999}
disabled={true}
/>
</Stack>
<Stack className="panelGroupSpacing" horizontal verticalAlign="center">
<div role="radiogroup">
<input
className="panelRadioBtn"
checked={createNewContainer}
aria-label="Create new container"
aria-checked={createNewContainer}
name="containerType"
type="radio"
role="radio"
id="containerCreateNew"
tabIndex={0}
onChange={() => setCreateNewContainer(true)}
/>
<span className="panelRadioBtnLabel">New container</span>
<input
className="panelRadioBtn"
checked={!createNewContainer}
aria-label="Use existing container"
aria-checked={!createNewContainer}
name="containerType"
type="radio"
role="radio"
tabIndex={0}
onChange={() => setCreateNewContainer(false)}
/>
<span className="panelRadioBtnLabel">Existing container</span>
</div>
</Stack>
{createNewContainer ? (
<Stack>
<Stack className="panelGroupSpacing">
<Stack horizontal>
<span className="mandatoryStar">*&nbsp;</span>
<Text className="panelTextBold" variant="small">
{`${getCollectionName()} id`}
</Text>
<TooltipHost
directionalHint={DirectionalHint.bottomLeftEdge}
content={`Unique identifier for the ${getCollectionName().toLocaleLowerCase()} and used for id-based routing through REST and all SDKs.`}
>
<Icon
role="button"
iconName="Info"
className="panelInfoIcon"
tabIndex={0}
ariaLabel={`Unique identifier for the ${getCollectionName().toLocaleLowerCase()} and used for id-based routing through REST and all SDKs.`}
/>
</TooltipHost>
</Stack>
<input
name="collectionId"
id="collectionId"
type="text"
aria-required
required
autoComplete="off"
pattern="[^/?#\\]*[^/?# \\]"
title="May not end with space nor contain characters '\' '/' '#' '?'"
placeholder={`e.g., ${getCollectionName()}1`}
size={40}
className="panelTextField"
aria-label={`${getCollectionName()} id, Example ${getCollectionName()}1`}
value={targetCollectionId}
onChange={(event: React.ChangeEvent<HTMLInputElement>) => setTargetCollectionId(event.target.value)}
/>
</Stack>
<Stack tokens={{ childrenGap: 10 }}>
<Stack horizontal>
<span className="mandatoryStar">*&nbsp;</span>
<Text className="panelTextBold" variant="small">
{getPartitionKeyName(userContext.apiType)}
</Text>
<TooltipHost
directionalHint={DirectionalHint.bottomLeftEdge}
content={getPartitionKeyTooltipText(userContext.apiType)}
>
<Icon
iconName="Info"
className="panelInfoIcon"
tabIndex={0}
aria-label={getPartitionKeyTooltipText(userContext.apiType)}
/>
</TooltipHost>
</Stack>
<Text variant="small" aria-label="pkDescription">
{getPartitionKeySubtext(userContext.features.partitionKeyDefault, userContext.apiType)}
</Text>
<input
type="text"
id="addCollection-partitionKeyValue"
aria-required
required
size={40}
className="panelTextField"
placeholder={getPartitionKeyPlaceHolder(userContext.apiType)}
aria-label={getPartitionKeyName(userContext.apiType)}
pattern={".*"}
title={""}
value={partitionKey}
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
if (!partitionKey && !event.target.value.startsWith("/")) {
setPartitionKey("/" + event.target.value);
} else {
setPartitionKey(event.target.value);
}
}}
/>
{subPartitionKeys.map((subPartitionKey: string, index: number) => {
return (
<Stack style={{ marginBottom: 8 }} key={`uniqueKey${index}`} horizontal>
<div
style={{
width: "20px",
border: "solid",
borderWidth: "0px 0px 1px 1px",
marginRight: "5px",
}}
></div>
<input
type="text"
id="addCollection-partitionKeyValue"
key={`addCollection-partitionKeyValue_${index}`}
aria-required
required
size={40}
tabIndex={index > 0 ? 1 : 0}
className="panelTextField"
autoComplete="off"
placeholder={getPartitionKeyPlaceHolder(userContext.apiType, index)}
aria-label={getPartitionKeyName(userContext.apiType)}
pattern={".*"}
title={""}
value={subPartitionKey}
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
const keys = [...subPartitionKeys];
if (!keys[index] && !event.target.value.startsWith("/")) {
keys[index] = "/" + event.target.value.trim();
setSubPartitionKeys(keys);
} else {
keys[index] = event.target.value.trim();
setSubPartitionKeys(keys);
}
}}
/>
<IconButton
iconProps={{ iconName: "Delete" }}
style={{ height: 27 }}
onClick={() => {
const keys = subPartitionKeys.filter((uniqueKey, j) => index !== j);
setSubPartitionKeys(keys);
}}
/>
</Stack>
);
})}
<Stack className="panelGroupSpacing">
<DefaultButton
styles={{ root: { padding: 0, width: 200, height: 30 }, label: { fontSize: 12 } }}
disabled={subPartitionKeys.length >= Constants.BackendDefaults.maxNumMultiHashPartition}
onClick={() => setSubPartitionKeys([...subPartitionKeys, ""])}
>
Add hierarchical partition key
</DefaultButton>
{subPartitionKeys.length > 0 && (
<Text variant="small">
<Icon iconName="InfoSolid" className="removeIcon" tabIndex={0} /> This feature allows you to
partition your data with up to three levels of keys for better data distribution. Requires .NET V3,
Java V4 SDK, or preview JavaScript V3 SDK.{" "}
<Link href="https://aka.ms/cosmos-hierarchical-partitioning" target="_blank">
Learn more
</Link>
</Text>
)}
</Stack>
</Stack>
</Stack>
) : (
<Stack>
<Stack horizontal>
<span className="mandatoryStar">*&nbsp;</span>
<Text className="panelTextBold" variant="small">
{`${getCollectionName()}`}
</Text>
<TooltipHost
directionalHint={DirectionalHint.bottomLeftEdge}
content={`Unique identifier for the ${getCollectionName().toLocaleLowerCase()} and used for id-based routing through REST and all SDKs.`}
>
<Icon
role="button"
iconName="Info"
className="panelInfoIcon"
tabIndex={0}
ariaLabel={`Unique identifier for the ${getCollectionName().toLocaleLowerCase()} and used for id-based routing through REST and all SDKs.`}
/>
</TooltipHost>
</Stack>
<Dropdown
styles={{ title: { height: 27, lineHeight: 27 }, dropdownItem: { fontSize: 12 } }}
style={{ width: 300, fontSize: 12 }}
placeholder="Choose an existing container"
options={getCollectionOptions()}
onChange={(event: React.FormEvent<HTMLDivElement>, collection: IDropdownOption) => {
setTargetCollectionId(collection.key as string);
setFormError("");
}}
defaultSelectedKey={targetCollectionId}
responsiveMode={999}
/>
</Stack>
)}
</Stack>
</RightPaneForm>
);
};

View File

@@ -35,10 +35,11 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
const collectionName = getCollectionName().toLocaleLowerCase();
const paneTitle = "Delete " + collectionName;
const onSubmit = async (): Promise<void> => {
const collection = useSelectedNode.getState().findSelectedCollection();
if (!collection || inputCollectionName !== collection.id()) {
const errorMessage = "Input " + collectionName + " id does not match the selected " + collectionName;
const errorMessage = "Input id " + inputCollectionName + " does not match the selected " + collection.id();
setFormError(errorMessage);
NotificationConsoleUtils.logConsoleError(
`Error while deleting ${collectionName} ${collection.id()}: ${errorMessage}`,

View File

@@ -32,7 +32,7 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
variant="small"
>
<span
className="css-53"
className="css-109"
>
Confirm by typing the
container
@@ -340,19 +340,19 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
value=""
>
<div
className="ms-TextField is-required root-55"
className="ms-TextField is-required root-111"
>
<div
className="ms-TextField-wrapper"
>
<div
className="ms-TextField-fieldGroup fieldGroup-56"
className="ms-TextField-fieldGroup fieldGroup-112"
>
<input
aria-invalid={false}
aria-label="Confirm by typing the container id"
autoFocus={true}
className="ms-TextField-field field-57"
className="ms-TextField-field field-113"
id="confirmCollectionId"
onBlur={[Function]}
onChange={[Function]}
@@ -1259,7 +1259,7 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
"iconDisabled": Object {
"color": "#a19f9d",
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"color": "GrayText",
},
},
@@ -1285,7 +1285,7 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
"menuIconDisabled": Object {
"color": "#a19f9d",
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"color": "GrayText",
},
},
@@ -1297,14 +1297,16 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
"selectors": Object {
".ms-Fabric--isFocusVisible &:focus:after": Object {
"border": "1px solid transparent",
"borderRadius": undefined,
"bottom": 2,
"content": "\\"\\"",
"left": 2,
"outline": "1px solid #605e5c",
"pointerEvents": undefined,
"position": "absolute",
"right": 2,
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"bottom": -2,
"left": -2,
"outlineColor": "ButtonText",
@@ -1335,7 +1337,7 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
"display": "inline-block",
"padding": "0 16px",
"selectors": Object {
":active > *": Object {
":active > span": Object {
"left": 0,
"position": "relative",
"top": 0,
@@ -1362,7 +1364,7 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
},
},
},
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"MsHighContrastAdjust": "none",
"backgroundColor": "WindowText",
"borderColor": "WindowText",
@@ -1387,14 +1389,16 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
"selectors": Object {
".ms-Fabric--isFocusVisible &:focus:after": Object {
"border": "1px solid transparent",
"borderRadius": undefined,
"bottom": 2,
"content": "\\"\\"",
"left": 2,
"outline": "1px solid #605e5c",
"pointerEvents": undefined,
"position": "absolute",
"right": 2,
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"bottom": -2,
"left": -2,
"outlineColor": "ButtonText",
@@ -1428,7 +1432,7 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
"backgroundColor": "#f3f2f1",
"color": "#d2d0ce",
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"backgroundColor": "Window",
"borderColor": "GrayText",
"color": "GrayText",
@@ -1445,7 +1449,7 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
"border": "1px solid #106ebe",
"color": "#ffffff",
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"backgroundColor": "Highlight",
"borderColor": "Highlight",
"color": "Window",
@@ -1457,7 +1461,7 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
"border": "1px solid #005a9e",
"color": "#ffffff",
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"MsHighContrastAdjust": "none",
"backgroundColor": "WindowText",
"borderColor": "WindowText",
@@ -1473,12 +1477,13 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
"overflow": "hidden",
"padding": 0,
"position": "absolute",
"whiteSpace": "nowrap",
"width": 1,
},
"splitButtonContainer": Array [
Object {
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"border": "none",
},
},
@@ -1489,14 +1494,16 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
"selectors": Object {
".ms-Fabric--isFocusVisible &:focus:after": Object {
"border": "1px solid #ffffff",
"borderRadius": undefined,
"bottom": 3,
"content": "\\"\\"",
"left": 3,
"outline": "1px solid #605e5c",
"pointerEvents": "none",
"position": "absolute",
"right": 3,
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"border": "none",
"bottom": -2,
"left": -2,
@@ -1519,13 +1526,21 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
"borderBottomRightRadius": "0",
"borderRight": "none",
"borderTopRightRadius": "0",
"flexGrow": "1",
},
".ms-Button--primary": Object {
"border": "none",
"borderBottomRightRadius": "0",
"borderTopRightRadius": "0",
"flexGrow": "1",
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
":active": Object {
"border": "none",
},
":hover": Object {
"border": "none",
},
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"MsHighContrastAdjust": "none",
"backgroundColor": "Window",
"border": "1px solid WindowText",
@@ -1538,7 +1553,7 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
".ms-Button--primary + .ms-Button": Object {
"border": "none",
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"border": "1px solid WindowText",
"borderLeftWidth": "0",
},
@@ -1551,7 +1566,7 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
"selectors": Object {
".ms-Button--primary": Object {
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"MsHighContrastAdjust": "none",
"backgroundColor": "WindowText",
"color": "Window",
@@ -1565,7 +1580,7 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
"selectors": Object {
".ms-Button--primary": Object {
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"MsHighContrastAdjust": "none",
"backgroundColor": "WindowText",
"color": "Window",
@@ -1579,7 +1594,7 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
"border": "none",
"outline": "none",
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"MsHighContrastAdjust": "none",
"backgroundColor": "Window",
"borderColor": "GrayText",
@@ -1595,7 +1610,7 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
"selectors": Object {
".ms-Button--primary": Object {
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"backgroundColor": "Highlight",
"color": "Window",
},
@@ -1604,7 +1619,7 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
".ms-Button.is-disabled": Object {
"color": "#a19f9d",
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"backgroundColor": "Window",
"borderColor": "GrayText",
"color": "GrayText",
@@ -1620,7 +1635,7 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
"position": "absolute",
"right": 31,
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"backgroundColor": "Window",
},
},
@@ -1632,7 +1647,7 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
"position": "absolute",
"right": 31,
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"backgroundColor": "WindowText",
},
},
@@ -1645,7 +1660,7 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
"position": "absolute",
"right": 31,
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"backgroundColor": "GrayText",
},
},
@@ -1667,18 +1682,18 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
":hover": Object {
"backgroundColor": "#106ebe",
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"color": "Highlight",
},
},
},
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"backgroundColor": "WindowText",
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"backgroundColor": "Canvas",
},
},
},
Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
".ms-Button-menuIcon": Object {
"color": "WindowText",
},
@@ -1728,7 +1743,7 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
"selectors": Object {
".ms-Button--primary": Object {
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"backgroundColor": "Window",
"borderColor": "GrayText",
"color": "GrayText",
@@ -1737,7 +1752,7 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
},
".ms-Button-menuIcon": Object {
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"color": "GrayText",
},
},
@@ -1745,7 +1760,7 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
":hover": Object {
"cursor": "default",
},
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"backgroundColor": "Window",
"border": "1px solid GrayText",
"color": "GrayText",
@@ -1767,14 +1782,16 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
"selectors": Object {
".ms-Fabric--isFocusVisible &:focus:after": Object {
"border": "1px solid #ffffff",
"borderRadius": undefined,
"bottom": 3,
"content": "\\"\\"",
"left": 3,
"outline": "1px solid #605e5c",
"pointerEvents": undefined,
"position": "absolute",
"right": 3,
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"border": "none",
"bottom": -2,
"left": -2,
@@ -1796,7 +1813,7 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
"splitButtonMenuIconDisabled": Object {
"color": "#a19f9d",
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"color": "GrayText",
},
},
@@ -2086,7 +2103,7 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
>
<button
aria-label="OK"
className="ms-Button ms-Button--primary root-66"
className="ms-Button ms-Button--primary root-122"
data-is-focusable={true}
id="sidePanelOkButton"
onClick={[Function]}
@@ -2098,16 +2115,16 @@ exports[`Delete Collection Confirmation Pane submit() should call delete collect
type="submit"
>
<span
className="ms-Button-flexContainer flexContainer-67"
className="ms-Button-flexContainer flexContainer-123"
data-automationid="splitbuttonprimary"
>
<span
className="ms-Button-textContainer textContainer-68"
className="ms-Button-textContainer textContainer-124"
>
<span
className="ms-Button-label label-70"
id="id__3"
key="id__3"
className="ms-Button-label label-126"
id="id__5"
key="id__5"
>
OK
</span>

View File

@@ -306,7 +306,7 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
}
>
<label
className="ms-Label root-53"
className="ms-Label root-109"
>
Partition key value
</label>
@@ -316,11 +316,11 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
horizontal={true}
>
<div
className="ms-Stack css-54"
className="ms-Stack css-110"
>
<Dropdown
defaultSelectedKey="string"
key=".0:$.0"
key=".0:$.$.0"
label="Key"
onChange={[Function]}
options={
@@ -661,7 +661,7 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
},
]
}
responsiveMode={3}
responsiveMode={0}
styles={[Function]}
tabIndex={0}
theme={
@@ -1225,7 +1225,7 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
}
>
<label
className="ms-Label ms-Dropdown-label root-71"
className="ms-Label ms-Dropdown-label root-129"
id="Dropdown0-label"
>
Key
@@ -1236,7 +1236,7 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
aria-expanded="false"
aria-haspopup="listbox"
aria-labelledby="Dropdown0-label Dropdown0-option"
className="ms-Dropdown dropdown-55"
className="ms-Dropdown dropdown-111"
data-is-focusable={true}
data-ktp-target={true}
id="Dropdown0"
@@ -1251,25 +1251,23 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
tabIndex={0}
>
<span
aria-atomic={true}
aria-invalid={false}
aria-live="polite"
className="ms-Dropdown-title title-97"
className="ms-Dropdown-title title-156"
id="Dropdown0-option"
>
String
</span>
<span
className="ms-Dropdown-caretDownWrapper caretDownWrapper-57"
className="ms-Dropdown-caretDownWrapper caretDownWrapper-113"
>
<StyledIconBase
aria-hidden={true}
className="ms-Dropdown-caretDown caretDown-58"
className="ms-Dropdown-caretDown caretDown-114"
iconName="ChevronDown"
>
<IconBase
aria-hidden={true}
className="ms-Dropdown-caretDown caretDown-58"
className="ms-Dropdown-caretDown caretDown-114"
iconName="ChevronDown"
styles={[Function]}
theme={
@@ -1548,7 +1546,7 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
>
<i
aria-hidden={true}
className="ms-Dropdown-caretDown caretDown-73"
className="ms-Dropdown-caretDown caretDown-132"
data-icon-name="ChevronDown"
>
@@ -1563,7 +1561,7 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
</Dropdown>
<StyledTextFieldBase
id="confirmCollectionId"
key=".0:$.1"
key=".0:$.$.1"
label="Value"
onChange={[Function]}
tabIndex={0}
@@ -1852,7 +1850,7 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
validateOnLoad={true}
>
<div
className="ms-TextField root-75"
className="ms-TextField root-134"
>
<div
className="ms-TextField-wrapper"
@@ -2141,7 +2139,7 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
}
>
<label
className="ms-Label root-53"
className="ms-Label root-109"
htmlFor="confirmCollectionId"
id="TextFieldLabel3"
>
@@ -2150,12 +2148,12 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
</LabelBase>
</StyledLabelBase>
<div
className="ms-TextField-fieldGroup fieldGroup-76"
className="ms-TextField-fieldGroup fieldGroup-135"
>
<input
aria-invalid={false}
aria-labelledby="TextFieldLabel3"
className="ms-TextField-field field-77"
className="ms-TextField-field field-136"
id="confirmCollectionId"
onBlur={[Function]}
onChange={[Function]}
@@ -2464,7 +2462,7 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
}
>
<label
className="ms-Label root-53"
className="ms-Label root-109"
>
Enter input parameters (if any)
</label>
@@ -2474,11 +2472,11 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
horizontal={true}
>
<div
className="ms-Stack css-54"
className="ms-Stack css-110"
>
<Dropdown
defaultSelectedKey="string"
key=".0:$.0"
key=".0:$.$.0"
label="Key"
onChange={[Function]}
options={
@@ -2819,7 +2817,7 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
},
]
}
responsiveMode={3}
responsiveMode={0}
styles={[Function]}
tabIndex={0}
theme={
@@ -3101,12 +3099,12 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
>
<StyledLabelBase
className="ms-Dropdown-label"
id="Dropdown4-label"
id="Dropdown6-label"
styles={[Function]}
>
<LabelBase
className="ms-Dropdown-label"
id="Dropdown4-label"
id="Dropdown6-label"
styles={[Function]}
theme={
Object {
@@ -3383,8 +3381,8 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
}
>
<label
className="ms-Label ms-Dropdown-label root-71"
id="Dropdown4-label"
className="ms-Label ms-Dropdown-label root-129"
id="Dropdown6-label"
>
Key
</label>
@@ -3393,11 +3391,11 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
<div
aria-expanded="false"
aria-haspopup="listbox"
aria-labelledby="Dropdown4-label Dropdown4-option"
className="ms-Dropdown dropdown-55"
aria-labelledby="Dropdown6-label Dropdown6-option"
className="ms-Dropdown dropdown-111"
data-is-focusable={true}
data-ktp-target={true}
id="Dropdown4"
id="Dropdown6"
onBlur={[Function]}
onChange={[Function]}
onClick={[Function]}
@@ -3409,25 +3407,23 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
tabIndex={0}
>
<span
aria-atomic={true}
aria-invalid={false}
aria-live="polite"
className="ms-Dropdown-title title-97"
id="Dropdown4-option"
className="ms-Dropdown-title title-156"
id="Dropdown6-option"
>
String
</span>
<span
className="ms-Dropdown-caretDownWrapper caretDownWrapper-57"
className="ms-Dropdown-caretDownWrapper caretDownWrapper-113"
>
<StyledIconBase
aria-hidden={true}
className="ms-Dropdown-caretDown caretDown-58"
className="ms-Dropdown-caretDown caretDown-114"
iconName="ChevronDown"
>
<IconBase
aria-hidden={true}
className="ms-Dropdown-caretDown caretDown-58"
className="ms-Dropdown-caretDown caretDown-114"
iconName="ChevronDown"
styles={[Function]}
theme={
@@ -3706,7 +3702,7 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
>
<i
aria-hidden={true}
className="ms-Dropdown-caretDown caretDown-73"
className="ms-Dropdown-caretDown caretDown-132"
data-icon-name="ChevronDown"
>
@@ -3722,7 +3718,7 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
<StyledTextFieldBase
defaultValue=""
id="confirmCollectionId"
key=".0:$.1"
key=".0:$.$.1"
label="Param"
onChange={[Function]}
tabIndex={0}
@@ -4012,19 +4008,19 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
validateOnLoad={true}
>
<div
className="ms-TextField root-75"
className="ms-TextField root-134"
>
<div
className="ms-TextField-wrapper"
>
<StyledLabelBase
htmlFor="confirmCollectionId"
id="TextFieldLabel7"
id="TextFieldLabel9"
styles={[Function]}
>
<LabelBase
htmlFor="confirmCollectionId"
id="TextFieldLabel7"
id="TextFieldLabel9"
styles={[Function]}
theme={
Object {
@@ -4301,21 +4297,21 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
}
>
<label
className="ms-Label root-53"
className="ms-Label root-109"
htmlFor="confirmCollectionId"
id="TextFieldLabel7"
id="TextFieldLabel9"
>
Param
</label>
</LabelBase>
</StyledLabelBase>
<div
className="ms-TextField-fieldGroup fieldGroup-76"
className="ms-TextField-fieldGroup fieldGroup-135"
>
<input
aria-invalid={false}
aria-labelledby="TextFieldLabel7"
className="ms-TextField-field field-77"
aria-labelledby="TextFieldLabel9"
className="ms-TextField-field field-136"
id="confirmCollectionId"
onBlur={[Function]}
onChange={[Function]}
@@ -4331,6 +4327,7 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
</TextFieldBase>
</StyledTextFieldBase>
<div
key=".0:$.$.2/.$.$.0"
tabIndex={0}
>
<StyledImageBase
@@ -4628,7 +4625,7 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
width={20}
>
<div
className="ms-Image addRemoveIconLabel root-86"
className="ms-Image addRemoveIconLabel root-145"
style={
Object {
"height": 30,
@@ -4638,7 +4635,7 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
>
<img
alt="Delete param"
className="ms-Image-image ms-Image-image--portrait is-notLoaded is-fadeIn image-87"
className="ms-Image-image ms-Image-image--portrait is-notLoaded is-fadeIn image-146"
id="deleteparam"
key="fabricImage[object Object]"
onClick={[Function]}
@@ -4652,6 +4649,7 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
</StyledImageBase>
</div>
<div
key=".0:$.$.2/.$.$.1"
tabIndex={0}
>
<StyledImageBase
@@ -4949,7 +4947,7 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
width={20}
>
<div
className="ms-Image addRemoveIconLabel root-86"
className="ms-Image addRemoveIconLabel root-145"
style={
Object {
"height": 30,
@@ -4959,7 +4957,7 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
>
<img
alt="Add param"
className="ms-Image-image ms-Image-image--portrait is-notLoaded is-fadeIn image-87"
className="ms-Image-image ms-Image-image--portrait is-notLoaded is-fadeIn image-146"
id="addparam"
key="fabricImage[object Object]"
onClick={[Function]}
@@ -4981,14 +4979,14 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
tabIndex={0}
>
<div
className="ms-Stack css-54"
className="ms-Stack css-110"
onClick={[Function]}
tabIndex={0}
>
<StyledImageBase
alt="Add param"
height={30}
key=".0:$.0"
key=".0:$.$.0"
src={Object {}}
width={20}
>
@@ -5273,7 +5271,7 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
width={20}
>
<div
className="ms-Image root-86"
className="ms-Image root-145"
style={
Object {
"height": 30,
@@ -5283,7 +5281,7 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
>
<img
alt="Add param"
className="ms-Image-image ms-Image-image--portrait is-notLoaded is-fadeIn image-87"
className="ms-Image-image ms-Image-image--portrait is-notLoaded is-fadeIn image-146"
key="fabricImage[object Object]"
onError={[Function]}
onLoad={[Function]}
@@ -5294,10 +5292,10 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
</StyledImageBase>
<Text
className="addNewParamStyle"
key=".0:$.1"
key=".0:$.$.1"
>
<span
className="addNewParamStyle css-88"
className="addNewParamStyle css-147"
>
Add New Param
</span>
@@ -6194,7 +6192,7 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
"iconDisabled": Object {
"color": "#a19f9d",
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"color": "GrayText",
},
},
@@ -6220,7 +6218,7 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
"menuIconDisabled": Object {
"color": "#a19f9d",
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"color": "GrayText",
},
},
@@ -6232,14 +6230,16 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
"selectors": Object {
".ms-Fabric--isFocusVisible &:focus:after": Object {
"border": "1px solid transparent",
"borderRadius": undefined,
"bottom": 2,
"content": "\\"\\"",
"left": 2,
"outline": "1px solid #605e5c",
"pointerEvents": undefined,
"position": "absolute",
"right": 2,
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"bottom": -2,
"left": -2,
"outlineColor": "ButtonText",
@@ -6270,7 +6270,7 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
"display": "inline-block",
"padding": "0 16px",
"selectors": Object {
":active > *": Object {
":active > span": Object {
"left": 0,
"position": "relative",
"top": 0,
@@ -6297,7 +6297,7 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
},
},
},
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"MsHighContrastAdjust": "none",
"backgroundColor": "WindowText",
"borderColor": "WindowText",
@@ -6322,14 +6322,16 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
"selectors": Object {
".ms-Fabric--isFocusVisible &:focus:after": Object {
"border": "1px solid transparent",
"borderRadius": undefined,
"bottom": 2,
"content": "\\"\\"",
"left": 2,
"outline": "1px solid #605e5c",
"pointerEvents": undefined,
"position": "absolute",
"right": 2,
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"bottom": -2,
"left": -2,
"outlineColor": "ButtonText",
@@ -6363,7 +6365,7 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
"backgroundColor": "#f3f2f1",
"color": "#d2d0ce",
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"backgroundColor": "Window",
"borderColor": "GrayText",
"color": "GrayText",
@@ -6380,7 +6382,7 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
"border": "1px solid #106ebe",
"color": "#ffffff",
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"backgroundColor": "Highlight",
"borderColor": "Highlight",
"color": "Window",
@@ -6392,7 +6394,7 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
"border": "1px solid #005a9e",
"color": "#ffffff",
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"MsHighContrastAdjust": "none",
"backgroundColor": "WindowText",
"borderColor": "WindowText",
@@ -6408,12 +6410,13 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
"overflow": "hidden",
"padding": 0,
"position": "absolute",
"whiteSpace": "nowrap",
"width": 1,
},
"splitButtonContainer": Array [
Object {
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"border": "none",
},
},
@@ -6424,14 +6427,16 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
"selectors": Object {
".ms-Fabric--isFocusVisible &:focus:after": Object {
"border": "1px solid #ffffff",
"borderRadius": undefined,
"bottom": 3,
"content": "\\"\\"",
"left": 3,
"outline": "1px solid #605e5c",
"pointerEvents": "none",
"position": "absolute",
"right": 3,
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"border": "none",
"bottom": -2,
"left": -2,
@@ -6454,13 +6459,21 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
"borderBottomRightRadius": "0",
"borderRight": "none",
"borderTopRightRadius": "0",
"flexGrow": "1",
},
".ms-Button--primary": Object {
"border": "none",
"borderBottomRightRadius": "0",
"borderTopRightRadius": "0",
"flexGrow": "1",
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
":active": Object {
"border": "none",
},
":hover": Object {
"border": "none",
},
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"MsHighContrastAdjust": "none",
"backgroundColor": "Window",
"border": "1px solid WindowText",
@@ -6473,7 +6486,7 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
".ms-Button--primary + .ms-Button": Object {
"border": "none",
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"border": "1px solid WindowText",
"borderLeftWidth": "0",
},
@@ -6486,7 +6499,7 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
"selectors": Object {
".ms-Button--primary": Object {
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"MsHighContrastAdjust": "none",
"backgroundColor": "WindowText",
"color": "Window",
@@ -6500,7 +6513,7 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
"selectors": Object {
".ms-Button--primary": Object {
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"MsHighContrastAdjust": "none",
"backgroundColor": "WindowText",
"color": "Window",
@@ -6514,7 +6527,7 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
"border": "none",
"outline": "none",
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"MsHighContrastAdjust": "none",
"backgroundColor": "Window",
"borderColor": "GrayText",
@@ -6530,7 +6543,7 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
"selectors": Object {
".ms-Button--primary": Object {
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"backgroundColor": "Highlight",
"color": "Window",
},
@@ -6539,7 +6552,7 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
".ms-Button.is-disabled": Object {
"color": "#a19f9d",
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"backgroundColor": "Window",
"borderColor": "GrayText",
"color": "GrayText",
@@ -6555,7 +6568,7 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
"position": "absolute",
"right": 31,
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"backgroundColor": "Window",
},
},
@@ -6567,7 +6580,7 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
"position": "absolute",
"right": 31,
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"backgroundColor": "WindowText",
},
},
@@ -6580,7 +6593,7 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
"position": "absolute",
"right": 31,
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"backgroundColor": "GrayText",
},
},
@@ -6602,18 +6615,18 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
":hover": Object {
"backgroundColor": "#106ebe",
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"color": "Highlight",
},
},
},
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"backgroundColor": "WindowText",
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"backgroundColor": "Canvas",
},
},
},
Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
".ms-Button-menuIcon": Object {
"color": "WindowText",
},
@@ -6663,7 +6676,7 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
"selectors": Object {
".ms-Button--primary": Object {
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"backgroundColor": "Window",
"borderColor": "GrayText",
"color": "GrayText",
@@ -6672,7 +6685,7 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
},
".ms-Button-menuIcon": Object {
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"color": "GrayText",
},
},
@@ -6680,7 +6693,7 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
":hover": Object {
"cursor": "default",
},
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"backgroundColor": "Window",
"border": "1px solid GrayText",
"color": "GrayText",
@@ -6702,14 +6715,16 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
"selectors": Object {
".ms-Fabric--isFocusVisible &:focus:after": Object {
"border": "1px solid #ffffff",
"borderRadius": undefined,
"bottom": 3,
"content": "\\"\\"",
"left": 3,
"outline": "1px solid #605e5c",
"pointerEvents": undefined,
"position": "absolute",
"right": 3,
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"border": "none",
"bottom": -2,
"left": -2,
@@ -6731,7 +6746,7 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
"splitButtonMenuIconDisabled": Object {
"color": "#a19f9d",
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"color": "GrayText",
},
},
@@ -7021,7 +7036,7 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
>
<button
aria-label="Execute"
className="ms-Button ms-Button--primary root-89"
className="ms-Button ms-Button--primary root-148"
data-is-focusable={true}
id="sidePanelOkButton"
onClick={[Function]}
@@ -7033,16 +7048,16 @@ exports[`Excute Sproc Param Pane should render Default properly 1`] = `
type="submit"
>
<span
className="ms-Button-flexContainer flexContainer-90"
className="ms-Button-flexContainer flexContainer-149"
data-automationid="splitbuttonprimary"
>
<span
className="ms-Button-textContainer textContainer-91"
className="ms-Button-textContainer textContainer-150"
>
<span
className="ms-Button-label label-93"
id="id__8"
key="id__8"
className="ms-Button-label label-152"
id="id__12"
key="id__12"
>
Execute
</span>

View File

@@ -112,8 +112,9 @@
margin-top: 28px;
margin-left: 4px !important;
}
.addRemoveIcon [alt="editEntity"]:focus,.addRemoveIconLabel [alt="editEntity"]:focus{
border:1px dashed #605E5C
.addRemoveIcon [alt="editEntity"]:focus,
.addRemoveIconLabel [alt="editEntity"]:focus {
border: 1px dashed #605e5c;
}
.addNewParamStyle {
margin-top: 5px;
@@ -153,6 +154,9 @@
.backImageIcon {
margin-top: 8px;
}
[alt="back"]:focus {
border: 1px solid #605e5c;
}
.addEntityDatePicker {
max-width: 145px;
}

View File

@@ -29,7 +29,7 @@ export class PanelContainerComponent extends React.Component<PanelContainerProps
};
}
omponentDidMount(): void {
componentDidMount(): void {
window.addEventListener("resize", () => this.setState({ height: this.getPanelHeight() }));
}
@@ -62,12 +62,12 @@ export class PanelContainerComponent extends React.Component<PanelContainerProps
customWidth={this.props.panelWidth ? this.props.panelWidth : "440px"}
headerClassName="panelHeader"
onRenderNavigationContent={this.props.onRenderNavigationContent}
isFooterAtBottom={true}
styles={{
navigation: { borderBottom: "1px solid #cccccc" },
content: { padding: 0, height: "100%" },
scrollableContent: { height: "100%" },
content: { padding: 0 },
header: { padding: "0 0 8px 34px" },
commands: { marginTop: 8 },
commands: { marginTop: 8, paddingTop: 0 },
}}
style={{ height: this.state.height }}
>

View File

@@ -32,14 +32,8 @@ export const PanelInfoErrorComponent: React.FunctionComponent<PanelInfoErrorProp
return (
<Stack className="panelInfoErrorContainer" horizontal verticalAlign="center">
{icon}
<span className="panelWarningErrorDetailsLinkContainer">
<Text
role="alert"
aria-live="assertive"
aria-label={message}
className="panelWarningErrorMessage"
variant="small"
>
<span className="panelWarningErrorDetailsLinkContainer" role="alert" aria-live="assertive">
<Text aria-label={message} className="panelWarningErrorMessage" variant="small">
{message}
{link && linkText && (
<Link target="_blank" href={link}>

View File

@@ -1,5 +1,5 @@
import { fireEvent, render, screen } from "@testing-library/react";
import { mount, ReactWrapper } from "enzyme";
import { ReactWrapper, mount } from "enzyme";
import React from "react";
import { RightPaneForm } from "./RightPaneForm";
@@ -34,6 +34,6 @@ describe("Right Pane Form", () => {
it("should render error in header", () => {
render(<RightPaneForm {...props} formError="file already Exist" />);
expect(screen.getByLabelText("error")).toBeDefined();
expect(screen.getByRole("alert").innerHTML).toEqual("file already Exist");
expect(screen.getByRole("alert").innerHTML).toContain("file already Exist");
});
});

View File

@@ -901,7 +901,7 @@ exports[`Right Pane Form should render Default properly 1`] = `
"iconDisabled": Object {
"color": "#a19f9d",
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"color": "GrayText",
},
},
@@ -927,7 +927,7 @@ exports[`Right Pane Form should render Default properly 1`] = `
"menuIconDisabled": Object {
"color": "#a19f9d",
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"color": "GrayText",
},
},
@@ -939,14 +939,16 @@ exports[`Right Pane Form should render Default properly 1`] = `
"selectors": Object {
".ms-Fabric--isFocusVisible &:focus:after": Object {
"border": "1px solid transparent",
"borderRadius": undefined,
"bottom": 2,
"content": "\\"\\"",
"left": 2,
"outline": "1px solid #605e5c",
"pointerEvents": undefined,
"position": "absolute",
"right": 2,
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"bottom": -2,
"left": -2,
"outlineColor": "ButtonText",
@@ -977,7 +979,7 @@ exports[`Right Pane Form should render Default properly 1`] = `
"display": "inline-block",
"padding": "0 16px",
"selectors": Object {
":active > *": Object {
":active > span": Object {
"left": 0,
"position": "relative",
"top": 0,
@@ -1004,7 +1006,7 @@ exports[`Right Pane Form should render Default properly 1`] = `
},
},
},
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"MsHighContrastAdjust": "none",
"backgroundColor": "WindowText",
"borderColor": "WindowText",
@@ -1029,14 +1031,16 @@ exports[`Right Pane Form should render Default properly 1`] = `
"selectors": Object {
".ms-Fabric--isFocusVisible &:focus:after": Object {
"border": "1px solid transparent",
"borderRadius": undefined,
"bottom": 2,
"content": "\\"\\"",
"left": 2,
"outline": "1px solid #605e5c",
"pointerEvents": undefined,
"position": "absolute",
"right": 2,
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"bottom": -2,
"left": -2,
"outlineColor": "ButtonText",
@@ -1070,7 +1074,7 @@ exports[`Right Pane Form should render Default properly 1`] = `
"backgroundColor": "#f3f2f1",
"color": "#d2d0ce",
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"backgroundColor": "Window",
"borderColor": "GrayText",
"color": "GrayText",
@@ -1087,7 +1091,7 @@ exports[`Right Pane Form should render Default properly 1`] = `
"border": "1px solid #106ebe",
"color": "#ffffff",
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"backgroundColor": "Highlight",
"borderColor": "Highlight",
"color": "Window",
@@ -1099,7 +1103,7 @@ exports[`Right Pane Form should render Default properly 1`] = `
"border": "1px solid #005a9e",
"color": "#ffffff",
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"MsHighContrastAdjust": "none",
"backgroundColor": "WindowText",
"borderColor": "WindowText",
@@ -1115,12 +1119,13 @@ exports[`Right Pane Form should render Default properly 1`] = `
"overflow": "hidden",
"padding": 0,
"position": "absolute",
"whiteSpace": "nowrap",
"width": 1,
},
"splitButtonContainer": Array [
Object {
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"border": "none",
},
},
@@ -1131,14 +1136,16 @@ exports[`Right Pane Form should render Default properly 1`] = `
"selectors": Object {
".ms-Fabric--isFocusVisible &:focus:after": Object {
"border": "1px solid #ffffff",
"borderRadius": undefined,
"bottom": 3,
"content": "\\"\\"",
"left": 3,
"outline": "1px solid #605e5c",
"pointerEvents": "none",
"position": "absolute",
"right": 3,
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"border": "none",
"bottom": -2,
"left": -2,
@@ -1161,13 +1168,21 @@ exports[`Right Pane Form should render Default properly 1`] = `
"borderBottomRightRadius": "0",
"borderRight": "none",
"borderTopRightRadius": "0",
"flexGrow": "1",
},
".ms-Button--primary": Object {
"border": "none",
"borderBottomRightRadius": "0",
"borderTopRightRadius": "0",
"flexGrow": "1",
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
":active": Object {
"border": "none",
},
":hover": Object {
"border": "none",
},
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"MsHighContrastAdjust": "none",
"backgroundColor": "Window",
"border": "1px solid WindowText",
@@ -1180,7 +1195,7 @@ exports[`Right Pane Form should render Default properly 1`] = `
".ms-Button--primary + .ms-Button": Object {
"border": "none",
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"border": "1px solid WindowText",
"borderLeftWidth": "0",
},
@@ -1193,7 +1208,7 @@ exports[`Right Pane Form should render Default properly 1`] = `
"selectors": Object {
".ms-Button--primary": Object {
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"MsHighContrastAdjust": "none",
"backgroundColor": "WindowText",
"color": "Window",
@@ -1207,7 +1222,7 @@ exports[`Right Pane Form should render Default properly 1`] = `
"selectors": Object {
".ms-Button--primary": Object {
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"MsHighContrastAdjust": "none",
"backgroundColor": "WindowText",
"color": "Window",
@@ -1221,7 +1236,7 @@ exports[`Right Pane Form should render Default properly 1`] = `
"border": "none",
"outline": "none",
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"MsHighContrastAdjust": "none",
"backgroundColor": "Window",
"borderColor": "GrayText",
@@ -1237,7 +1252,7 @@ exports[`Right Pane Form should render Default properly 1`] = `
"selectors": Object {
".ms-Button--primary": Object {
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"backgroundColor": "Highlight",
"color": "Window",
},
@@ -1246,7 +1261,7 @@ exports[`Right Pane Form should render Default properly 1`] = `
".ms-Button.is-disabled": Object {
"color": "#a19f9d",
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"backgroundColor": "Window",
"borderColor": "GrayText",
"color": "GrayText",
@@ -1262,7 +1277,7 @@ exports[`Right Pane Form should render Default properly 1`] = `
"position": "absolute",
"right": 31,
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"backgroundColor": "Window",
},
},
@@ -1274,7 +1289,7 @@ exports[`Right Pane Form should render Default properly 1`] = `
"position": "absolute",
"right": 31,
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"backgroundColor": "WindowText",
},
},
@@ -1287,7 +1302,7 @@ exports[`Right Pane Form should render Default properly 1`] = `
"position": "absolute",
"right": 31,
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"backgroundColor": "GrayText",
},
},
@@ -1309,18 +1324,18 @@ exports[`Right Pane Form should render Default properly 1`] = `
":hover": Object {
"backgroundColor": "#106ebe",
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"color": "Highlight",
},
},
},
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"backgroundColor": "WindowText",
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"backgroundColor": "Canvas",
},
},
},
Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
".ms-Button-menuIcon": Object {
"color": "WindowText",
},
@@ -1370,7 +1385,7 @@ exports[`Right Pane Form should render Default properly 1`] = `
"selectors": Object {
".ms-Button--primary": Object {
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"backgroundColor": "Window",
"borderColor": "GrayText",
"color": "GrayText",
@@ -1379,7 +1394,7 @@ exports[`Right Pane Form should render Default properly 1`] = `
},
".ms-Button-menuIcon": Object {
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"color": "GrayText",
},
},
@@ -1387,7 +1402,7 @@ exports[`Right Pane Form should render Default properly 1`] = `
":hover": Object {
"cursor": "default",
},
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"backgroundColor": "Window",
"border": "1px solid GrayText",
"color": "GrayText",
@@ -1409,14 +1424,16 @@ exports[`Right Pane Form should render Default properly 1`] = `
"selectors": Object {
".ms-Fabric--isFocusVisible &:focus:after": Object {
"border": "1px solid #ffffff",
"borderRadius": undefined,
"bottom": 3,
"content": "\\"\\"",
"left": 3,
"outline": "1px solid #605e5c",
"pointerEvents": undefined,
"position": "absolute",
"right": 3,
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"border": "none",
"bottom": -2,
"left": -2,
@@ -1438,7 +1455,7 @@ exports[`Right Pane Form should render Default properly 1`] = `
"splitButtonMenuIconDisabled": Object {
"color": "#a19f9d",
"selectors": Object {
"@media screen and (-ms-high-contrast: active), (forced-colors: active)": Object {
"@media screen and (-ms-high-contrast: active), screen and (forced-colors: active)": Object {
"color": "GrayText",
},
},
@@ -1728,7 +1745,7 @@ exports[`Right Pane Form should render Default properly 1`] = `
>
<button
aria-label="Load"
className="ms-Button ms-Button--primary root-53"
className="ms-Button ms-Button--primary root-109"
data-is-focusable={true}
id="sidePanelOkButton"
onClick={[Function]}
@@ -1740,14 +1757,14 @@ exports[`Right Pane Form should render Default properly 1`] = `
type="submit"
>
<span
className="ms-Button-flexContainer flexContainer-54"
className="ms-Button-flexContainer flexContainer-110"
data-automationid="splitbuttonprimary"
>
<span
className="ms-Button-textContainer textContainer-55"
className="ms-Button-textContainer textContainer-111"
>
<span
className="ms-Button-label label-57"
className="ms-Button-label label-113"
id="id__0"
key="id__0"
>

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