Compare commits

...

65 Commits

Author SHA1 Message Date
Sung-Hyun Kang
82f50f6b1f Added VNext feature matrix 2025-08-14 20:46:45 -05:00
Sung-Hyun Kang
d385764027 Rebase master 2025-08-06 12:56:36 -05:00
jawelton74
0ef4399ba4 Support data plane RBAC for E2E tests. (#2176)
* Acquire token for NoSQL account prior to running tests.

* Change client id to user assigned managed identity.

* Change to use managed identity. Add token variables for gremlin and
tables.

* Add RBAC details to test README.

* Add token for SQL readonly database. Skip resource token tests when RBAC
enabled.

* Use hardcoded account name for sql readonly.

* Use specific tag for sql readonly.

* Remove comment.
2025-08-05 10:59:57 -07:00
BChoudhury-ms
870863a723 Disabling telemetry for mongo shell (#2195)
* Disabling telemetry for mongo shell

* fix formatting issues

* removed empty spaces from terminal and segregated disableTelemetry command
2025-07-31 10:03:09 +05:30
JustinKol
e3815734db Min/Max UX changes for Scale & Settings tab (#2192)
* master pull

* changed min max text boxes for autoscale

* test update

* Update .npmrc

* Update settings.json

* Prettier run
2025-07-28 13:24:19 -04:00
BChoudhury-ms
5ea78f9abf Add VCore MongoDB support for VS Code extension integration (#2181)
* support for vCore mongodb support for vscode extension

* added comment for future referance on the double encoded connection string
2025-07-28 22:52:23 +05:30
sindhuba
8a56214ec2 Upgrade SDK version to 4.5.0 (#2194) 2025-07-21 12:44:42 -07:00
Laurent Nguyen
e3ae006100 feat: Disable few checks if Fabric native (#2188)
* Disable few checks if Fabric native

* Fix typo in error message
2025-07-17 08:47:56 +02:00
asier-isayas
589b61afaf Upgrade Cosmos SDK to v4.4.0 (#2187)
* Upgrade Cosmos SDK to v4.4.0

* fix unit tests

* explain crypto.subtle

---------

Co-authored-by: Asier Isayas <aisayas@microsoft.com>
2025-07-15 11:28:28 -07:00
Laurent Nguyen
eb3f6bc93f Fabric: set default throughput for new container to 5k (#2190) 2025-07-15 20:22:06 +02:00
bogercraig
6ec909a97b Use Config.json to Set Environment Specific AAD Auth Settings (#2184)
* Force hosted explorer to load config.json before calling useAADAuth.

* Ensure AAD endpoint from config.json loads before useAADAuth.

* Fix immediate linting errors and warnings.

* Remove separate spinner for waiting on config to load.

* Simplifying auth and reintroducing "No tokens for given scope, and no authorization code was passed to acquireToken." error.  Blocking on login if incorrect AAD_ENDPOINT provided.

* Fix linting errors.

* Add error handling to prevent unhandled errors thrown when login prompt is cancelled prematurely.
2025-07-11 15:34:10 -07:00
asier-isayas
08a51ca6b1 Remove unneeded and misleading console log (#2186)
Co-authored-by: Asier Isayas <aisayas@microsoft.com>
2025-07-10 08:58:11 -07:00
jawelton74
30a3b5c7a4 Support data plane RBAC for Gremlin API (#2182)
* Refactor logic for determining if we should use data plane RBAC to a
common function.

* Support RBAC for gremlin API.

* Refactor to use common function.

* Fix unit tests.

* Move test function inside test scope.

* Minor clean ups.

* Reinstate utf8ToB64 function in case this breaks a corner case.
2025-07-09 16:23:09 -07:00
jawelton74
f370507a27 Refactor logic for determining if we should use data plane RBAC (#2180)
* Refactor logic for determining if we should use data plane RBAC to a
common function.

* Move test function into test scope.
2025-07-08 11:41:43 -07:00
sunghyunkang1111
e0edaf405c MSRC fixes for testExplorer and HeatMap (#2183)
* MSRC fixes for testExplorer and HeatMap

* MSRC fixes for testExplorer and HeatMap
2025-07-07 11:00:29 -05:00
Sourabh Jain
f8231600d6 DB Shell: Fix User Consent Message as per Privacy Guidlines and enable this for customers (#2173)
* Fix user Consent msg and fix issues

* fix format

* test fix

* fix snap file
2025-07-07 21:13:34 +05:30
asier-isayas
45c8d70c77 do not set disableNonStreamingOrderByQuery flag (#2179)
Co-authored-by: Asier Isayas <aisayas@microsoft.com>
2025-06-26 07:43:55 -07:00
bogercraig
70d7ee755b Add Additional Config from Config.json and Clean Up Unused Config (#2178)
* Cleaning up unused config from portal backend migration.

* Remove config used during backend migration.

* Add backend endpoint override from config.json.

* Add AAD and ARM endpoint overrides from config.json.

* Add GRAPH_ENDPOINT override from config.json.

* Remove unused catalog api version.

* Remove isTerminalEnabled from config.  Cannot find reference in DE, DE Release, or Frontend.

* Fix mongo client unit tests.

* Removing BackendApi from constants since no longer referenced in the codebase.

* Talked with Tara and added the CATALOG_API_VERSION back to the config and substituted out the hard coded string it was intended to replace.

* Include existing portal backend endpoints in default allow list.

* Add localhost:1234 endpoint for Mongo unit tests.

* Removing old backend local test endpoint from backend endpoint list.
2025-06-24 12:50:21 -07:00
Vsevolod Kukol
0a4aed4f47 feat: Enable Container Vector Policy for Fabric Native and adjust throughput/vecotr input visibility logic (#2170) 2025-06-23 17:18:19 +02:00
Dmitry Shilov
a7d007e0dd fix: Partition Keys block for fabric (#2174)
* fix: Partition Keys block for fabric

- Show Partition Keys block for fabric
- Adjust layout and visibility of separators in AddCollectionPanel
2025-06-23 17:18:09 +02:00
sunghyunkang1111
5f4a4e5c4c Added Quickstart open action (#2175) 2025-06-17 12:08:12 -05:00
asier-isayas
1b64827c24 Upgrade ARM Client API version to 2025-05-01-preview & Fetch enableMaterializedViews account property from ARM (#2171)
* Upgrade ARM Client API version to 2025-05-01-preview

* fix npm run compile

---------

Co-authored-by: Asier Isayas <aisayas@microsoft.com>
2025-06-13 06:20:08 -04:00
SATYA SB
a6ae784a45 [accessibility-3739744]:[Supporting the Platform - Azure Cosmos DB- Data Explorer - New Vertex]: When page viewport is set to 320x256px, Content under 'New Vertex' pane is not properly visible. (#2163)
Co-authored-by: Satyapriya Bai <v-satybai@microsoft.com>
2025-06-12 17:58:10 +05:30
SATYA SB
7458107efd [accessibility-3726486]:[Keyboard Navigation - Azure Cosmos DB - Data Explorer - New Graph]: Keyboard focus indicator is not landing on the first interactive control after activating the 'New Graph' control. (#2073)
Co-authored-by: Satyapriya Bai <v-satybai@microsoft.com>
2025-06-12 17:57:21 +05:30
JustinKol
64533b445f Autoscale min/max UX changes (#2162)
* master pull

* Better VSCode detection

* Prettier run

* Update .npmrc

* Update settings.json

* Fixed ESLint error

* Changed the VSCode detection to a test url that will not open if successful

* Initial UX changes to Add Collection Panel

* Removing changes from other branch

* Reverting explorer changes

* Snapshot updates and Lint fixes

* Formatting fixes

* Setting separator spacing the same

* Update test snapshot

* Reverting Manual to old UI
2025-06-04 10:40:57 -04:00
Dmitry Shilov
d7bdd0032e fix: Activate the last opened React tab after closing active tab (#2156) 2025-06-04 11:10:44 +02:00
SATYA SB
372ac6921f fix: Enhance splash screen layout with responsive design for stack elements (#2159)
Co-authored-by: Satyapriya Bai <v-satybai@microsoft.com>
2025-06-04 10:29:45 +05:30
SATYA SB
c6eda097fc [Supporting the Platform - Azure Cosmos DB- Data Explorer - Graphs]: When page viewport is set to 320x256px, Content under 'Notification' section is not properly visible. (#2161)
* [accessibility-3739643]: [Supporting the Platform - Azure Cosmos DB- Data Explorer - Graphs]: When page viewport is set to 320x256px, Content under 'Notification' section is not properly visible.

* fix: Adjust notification console styles for better responsiveness.

---------

Co-authored-by: Satyapriya Bai <v-satybai@microsoft.com>
2025-06-04 10:27:12 +05:30
Dmitry Shilov
05d02f08fa feat: Integrate Fabric Native support for throughput management (#2166) 2025-06-03 08:47:00 +02:00
jawelton74
ab4f02f74a Remove frame-src from CSP. (#2169) 2025-05-29 10:54:37 -07:00
asier-isayas
0fc6647627 Upgrade Cosmos SDK to v4.3 (#2137)
* Upgrade Cosmos SDK to v4.3

* push pkg lock

* fix tests

* fix package-lock.json

* fix package-lock

* fix package-lock.json

* fix package-lock.json

* log console warning when RU limit is reached

* fix tests

* fix format

* fix tests

* added description to RU limit message

* show warning icon on tab header

---------

Co-authored-by: Asier Isayas <aisayas@microsoft.com>
2025-05-29 11:35:06 -04:00
jawelton74
c5ed537109 Fix invalid reference in frame-src and add delimiter for (#2168)
frame-ancestors.
2025-05-29 07:27:15 -07:00
jawelton74
db322ccb59 Add login.microsoftonline.com to CSP. (#2167) 2025-05-28 12:25:14 -07:00
sunghyunkang1111
2d7631c358 Added throughput buckets in the scale update (#2164) 2025-05-27 11:54:20 -05:00
JustinKol
e401c88df6 Add query results, export to json/csv button (#2143)
* master pull

* Added export to json button

* Update .npmrc

* Update settings.json

* Update .npmrc

* Update .npmrc

* revert .npmrc file

* Added export to csv

* Prettier run

* Disable react/prop-types ESLint check

* Changed to download icon

* Added titles

* Switched to download icon already present

* Fixed download title

* Added check for all unique headers and added seperator header for excel only

* Moved to inline dropdown under download button

* Capitalized CSV and JSON

* Fixed where format wasn't updating before exporting

* removed testing console log

* Removed unnecessary async

* Added csv escaping

* Removing unnecessary escape character

* Separated into different functions for better organization and readability

* Fixed any value
2025-05-21 07:54:34 -04:00
asier-isayas
f14b574527 Enable Full Text Search on all NoSQL accounts (#2157)
* Enable Full Text Search on all NoSQL accounts

* format

* fix tests

* run tests

---------

Co-authored-by: Asier Isayas <aisayas@microsoft.com>
2025-05-20 10:49:09 -04:00
Dmitry Shilov
45513e5e1b fix: Update grid after uploading documents (#2131)
- Refactor UploadItemsPane to support onUpload callback
- Enhance bulk insert result type
- Insert uploaded documents to the grid
2025-05-19 11:35:04 +02:00
Dmitry Shilov
15154dfd6a fix: Implement abort functionality for bulk document deletion (#2139) 2025-05-19 11:34:44 +02:00
Lei Jiang
5db83f9a85 Revert version changes on http-proxy-middleware 2025-04-24 20:33:58 -07:00
Lei Jiang
f4276adca9 Update package-lock.json on http-proxy-middleware versions 2025-04-24 15:39:35 -07:00
Lei Jiang
36ded95be0 Update package-lock.json to update package version path-to-regexp 2025-04-14 15:09:52 -07:00
Lei Jiang
41d132b041 Update package-lock.json to update package version path-to-regexp 2025-04-14 14:46:40 -07:00
Lei Jiang
f6c8db9f37 Apply minimum changes on package-lock.json for package update 2025-04-14 13:51:04 -07:00
Lei Jiang
46e6695cba Revert package.json 2025-04-14 13:41:42 -07:00
Lei Jiang
392d0edcd1 Revert package-lock.json 2025-04-14 13:41:08 -07:00
Lei Jiang
b2ea464b6c Update package-lock.json to fix package vulnerabilities 2025-04-14 13:08:31 -07:00
Lei Jiang
c84beeb8c3 Update package.json to patch for vulnerabilities 2025-04-14 13:07:51 -07:00
Chris Anderson
3bf0f09d37 remove old local-dep 2024-12-21 00:31:14 +00:00
Chris Anderson
eb85b2959d added https support 2024-12-21 00:30:58 +00:00
Chris Anderson
ec472a3d4c exclude local proxy from ts 2024-12-06 18:38:37 +00:00
Chris Anderson
e3055b121f complete proxy with instructions 2024-12-03 21:43:29 +00:00
Chris Anderson
e489a66ae2 chores 2024-12-03 21:43:12 +00:00
Chris Anderson
601a335839 misc fixes 2024-12-03 01:17:31 +00:00
Chris Anderson
0029b04af1 add local proxy 2024-12-03 00:47:26 +00:00
Theo van Kraay
f4aa74ad6f comment out or change SDK code to remove errors 2024-12-03 00:47:26 +00:00
Theo van Kraay
2afb2d82e4 add copy of cosmos node.js SDK as local dependency 2024-12-03 00:47:26 +00:00
Chris Anderson
679e4e56df fix templates 2024-12-03 00:12:22 +00:00
Chris Anderson
df2c1b2345 update cosmos dep in lock file 2024-12-02 23:03:44 +00:00
Chris Anderson
7007368a1a temp: dynamically redirect quickstart template based on tls flag 2024-12-02 22:54:40 +00:00
Chris Anderson
079965d199 fix: explorer use emulator tls setting 2024-12-02 22:54:40 +00:00
Chris Anderson
fc774e1089 feat: enable http emulator support 2024-12-02 22:54:40 +00:00
Theo van Kraay
d9d90ac6d9 temp revert back changes that break even legacy emulator 2024-12-02 22:54:16 +00:00
Theo van Kraay
c101f7de74 comment out or change SDK code to remove errors 2024-12-02 22:54:16 +00:00
Theo van Kraay
ca396cdfbe add copy of cosmos node.js SDK as local dependency 2024-12-02 22:54:16 +00:00
Theo van Kraay
ff1e733679 pgcosmos 2024-12-02 22:52:32 +00:00
143 changed files with 6345 additions and 2105 deletions

View File

@@ -23,8 +23,6 @@ src/Common/MongoUtility.ts
src/Common/NotificationsClientBase.ts src/Common/NotificationsClientBase.ts
src/Common/QueriesClient.ts src/Common/QueriesClient.ts
src/Common/Splitter.ts src/Common/Splitter.ts
src/Controls/Heatmap/Heatmap.test.ts
src/Controls/Heatmap/Heatmap.ts
src/Definitions/datatables.d.ts src/Definitions/datatables.d.ts
src/Definitions/gif.d.ts src/Definitions/gif.d.ts
src/Definitions/globals.d.ts src/Definitions/globals.d.ts
@@ -146,3 +144,5 @@ __mocks__/monaco-editor.ts
src/Explorer/Tree/ResourceTree.tsx src/Explorer/Tree/ResourceTree.tsx
src/Utils/EndpointUtils.ts src/Utils/EndpointUtils.ts
src/Utils/PriorityBasedExecutionUtils.ts src/Utils/PriorityBasedExecutionUtils.ts
utils/local-proxy/**

View File

@@ -177,9 +177,27 @@ jobs:
- name: "Az CLI login" - name: "Az CLI login"
uses: Azure/login@v2 uses: Azure/login@v2
with: with:
client-id: ${{ secrets.AZURE_CLIENT_ID }} client-id: ${{ secrets.E2E_TESTS_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }} tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
# We can't use MSAL within playwright so we acquire tokens prior to running the tests
- name: "Acquire RBAC tokens for test accounts"
uses: azure/cli@v2
with:
azcliversion: latest
inlineScript: |
NOSQL_TESTACCOUNT_TOKEN=$(az account get-access-token --scope "https://github-e2etests-sql.documents.azure.com/.default" -o tsv --query accessToken)
echo "::add-mask::$NOSQL_TESTACCOUNT_TOKEN"
echo NOSQL_TESTACCOUNT_TOKEN=$NOSQL_TESTACCOUNT_TOKEN >> $GITHUB_ENV
NOSQL_READONLY_TESTACCOUNT_TOKEN=$(az account get-access-token --scope "https://github-e2etests-sql-readonly.documents.azure.com/.default" -o tsv --query accessToken)
echo "::add-mask::$NOSQL_READONLY_TESTACCOUNT_TOKEN"
echo NOSQL_READONLY_TESTACCOUNT_TOKEN=$NOSQL_READONLY_TESTACCOUNT_TOKEN >> $GITHUB_ENV
TABLE_TESTACCOUNT_TOKEN=$(az account get-access-token --scope "https://github-e2etests-tables.documents.azure.com/.default" -o tsv --query accessToken)
echo "::add-mask::$TABLE_TESTACCOUNT_TOKEN"
echo TABLE_TESTACCOUNT_TOKEN=$TABLE_TESTACCOUNT_TOKEN >> $GITHUB_ENV
GREMLIN_TESTACCOUNT_TOKEN=$(az account get-access-token --scope "https://github-e2etests-gremlin.documents.azure.com/.default" -o tsv --query accessToken)
echo "::add-mask::$GREMLIN_TESTACCOUNT_TOKEN"
echo GREMLIN_TESTACCOUNT_TOKEN=$GREMLIN_TESTACCOUNT_TOKEN >> $GITHUB_ENV
- name: Run test shard ${{ matrix['shardIndex'] }} of ${{ matrix['shardTotal']}} - name: Run test shard ${{ matrix['shardIndex'] }} of ${{ matrix['shardTotal']}}
run: npx playwright test --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --workers=3 run: npx playwright test --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --workers=3
- name: Upload blob report to GitHub Actions Artifacts - name: Upload blob report to GitHub Actions Artifacts

View File

@@ -27,7 +27,7 @@ jobs:
- name: "Az CLI login" - name: "Az CLI login"
uses: azure/login@v1 uses: azure/login@v1
with: with:
client-id: ${{ secrets.AZURE_CLIENT_ID }} client-id: ${{ secrets.E2E_TESTS_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }} tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}

View File

@@ -0,0 +1,3 @@
{
"EMULATOR_ENDPOINT": "http://localhost:8081"
}

View File

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

View File

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

View File

@@ -2869,6 +2869,7 @@ a:link {
z-index: 1000; z-index: 1000;
overflow-y: auto; overflow-y: auto;
overflow-x: clip; overflow-x: clip;
min-height: fit-content;
} }
.uniqueIndexesContainer { .uniqueIndexesContainer {

309
package-lock.json generated
View File

@@ -10,7 +10,7 @@
"hasInstallScript": true, "hasInstallScript": true,
"dependencies": { "dependencies": {
"@azure/arm-cosmosdb": "9.1.0", "@azure/arm-cosmosdb": "9.1.0",
"@azure/cosmos": "4.2.0-beta.1", "@azure/cosmos": "4.5.0",
"@azure/cosmos-language-service": "0.0.5", "@azure/cosmos-language-service": "0.0.5",
"@azure/identity": "4.5.0", "@azure/identity": "4.5.0",
"@azure/msal-browser": "2.14.2", "@azure/msal-browser": "2.14.2",
@@ -205,6 +205,58 @@
"webpack-dev-server": "4.15.2" "webpack-dev-server": "4.15.2"
} }
}, },
"canvas": {
"version": "1.0.0",
"extraneous": true,
"license": "ISC"
},
"local_dependencies/@azure/cosmos": {
"version": "4.0.1-beta.3",
"extraneous": true,
"license": "MIT",
"dependencies": {
"@azure/abort-controller": "^1.0.0",
"@azure/core-auth": "^1.3.0",
"@azure/core-rest-pipeline": "^1.2.0",
"@azure/core-tracing": "^1.0.0",
"debug": "^4.1.1",
"fast-json-stable-stringify": "^2.1.0",
"jsbi": "^3.1.3",
"node-abort-controller": "^3.0.0",
"priorityqueuejs": "^2.0.0",
"semaphore": "^1.0.5",
"tslib": "^2.2.0",
"universal-user-agent": "^6.0.0",
"uuid": "^8.3.0"
},
"engines": {
"node": ">=18.0.0"
}
},
"local_dependencies/cosmos": {
"name": "@azure/cosmos",
"version": "4.0.1-beta.3",
"extraneous": true,
"license": "MIT",
"dependencies": {
"@azure/abort-controller": "^1.0.0",
"@azure/core-auth": "^1.3.0",
"@azure/core-rest-pipeline": "^1.2.0",
"@azure/core-tracing": "^1.0.0",
"debug": "^4.1.1",
"fast-json-stable-stringify": "^2.1.0",
"jsbi": "^3.1.3",
"node-abort-controller": "^3.0.0",
"priorityqueuejs": "^2.0.0",
"semaphore": "^1.0.5",
"tslib": "^2.2.0",
"universal-user-agent": "^6.0.0",
"uuid": "^8.3.0"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/@aashutoshrathi/word-wrap": { "node_modules/@aashutoshrathi/word-wrap": {
"version": "1.2.6", "version": "1.2.6",
"license": "MIT", "license": "MIT",
@@ -290,57 +342,69 @@
"version": "2.6.2", "version": "2.6.2",
"license": "0BSD" "license": "0BSD"
}, },
"node_modules/@azure/core-rest-pipeline": { "node_modules/@azure/core-http-compat": {
"version": "1.18.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.18.0.tgz", "resolved": "https://registry.npmjs.org/@azure/core-http-compat/-/core-http-compat-2.3.0.tgz",
"integrity": "sha512-QSoGUp4Eq/gohEFNJaUOwTN7BCc2nHTjjbm75JT0aD7W65PWM1H/tItz0GsABn22uaKyGxiMhWQLt2r+FGU89Q==", "integrity": "sha512-qLQujmUypBBG0gxHd0j6/Jdmul6ttl24c8WGiLXIk7IHXdBlfoBqW27hyz3Xn6xbfdyVSarl1Ttbk0AwnZBYCw==",
"dependencies": { "dependencies": {
"@azure/abort-controller": "^2.0.0", "@azure/abort-controller": "^2.0.0",
"@azure/core-auth": "^1.8.0", "@azure/core-client": "^1.3.0",
"@azure/core-tracing": "^1.0.1", "@azure/core-rest-pipeline": "^1.20.0"
"@azure/core-util": "^1.11.0", },
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/@azure/core-lro": {
"version": "2.7.2",
"resolved": "https://registry.npmjs.org/@azure/core-lro/-/core-lro-2.7.2.tgz",
"integrity": "sha512-0YIpccoX8m/k00O7mDDMdJpbr6mf1yWo2dfmxt5A8XVZVVMz2SSKaEbMCeJRvgQ0IaSlqhjT47p4hVIRRy90xw==",
"dependencies": {
"@azure/abort-controller": "^2.0.0",
"@azure/core-util": "^1.2.0",
"@azure/logger": "^1.0.0", "@azure/logger": "^1.0.0",
"http-proxy-agent": "^7.0.0",
"https-proxy-agent": "^7.0.0",
"tslib": "^2.6.2" "tslib": "^2.6.2"
}, },
"engines": { "engines": {
"node": ">=18.0.0" "node": ">=18.0.0"
} }
}, },
"node_modules/@azure/core-rest-pipeline/node_modules/agent-base": { "node_modules/@azure/core-lro/node_modules/tslib": {
"version": "7.1.1", "version": "2.8.1",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
},
"node_modules/@azure/core-paging": {
"version": "1.6.2",
"resolved": "https://registry.npmjs.org/@azure/core-paging/-/core-paging-1.6.2.tgz",
"integrity": "sha512-YKWi9YuCU04B55h25cnOYZHxXYtEvQEbKST5vqRga7hWY9ydd3FZHdeQF8pyh+acWZvppw13M/LMGx0LABUVMA==",
"dependencies": { "dependencies": {
"debug": "^4.3.4" "tslib": "^2.6.2"
}, },
"engines": { "engines": {
"node": ">= 14" "node": ">=18.0.0"
} }
}, },
"node_modules/@azure/core-rest-pipeline/node_modules/http-proxy-agent": { "node_modules/@azure/core-paging/node_modules/tslib": {
"version": "7.0.2", "version": "2.8.1",
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
},
"node_modules/@azure/core-rest-pipeline": {
"version": "1.20.0",
"resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.20.0.tgz",
"integrity": "sha512-ASoP8uqZBS3H/8N8at/XwFr6vYrRP3syTK0EUjDXQy0Y1/AUS+QeIRThKmTNJO2RggvBBxaXDPM7YoIwDGeA0g==",
"dependencies": { "dependencies": {
"agent-base": "^7.1.0", "@azure/abort-controller": "^2.0.0",
"debug": "^4.3.4" "@azure/core-auth": "^1.8.0",
"@azure/core-tracing": "^1.0.1",
"@azure/core-util": "^1.11.0",
"@azure/logger": "^1.0.0",
"@typespec/ts-http-runtime": "^0.2.2",
"tslib": "^2.6.2"
}, },
"engines": { "engines": {
"node": ">= 14" "node": ">=18.0.0"
}
},
"node_modules/@azure/core-rest-pipeline/node_modules/https-proxy-agent": {
"version": "7.0.5",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz",
"integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==",
"dependencies": {
"agent-base": "^7.0.2",
"debug": "4"
},
"engines": {
"node": ">= 14"
} }
}, },
"node_modules/@azure/core-rest-pipeline/node_modules/tslib": { "node_modules/@azure/core-rest-pipeline/node_modules/tslib": {
@@ -379,23 +443,31 @@
"license": "0BSD" "license": "0BSD"
}, },
"node_modules/@azure/cosmos": { "node_modules/@azure/cosmos": {
<<<<<<< HEAD
"version": "4.2.0-beta.1", "version": "4.2.0-beta.1",
"resolved": "https://registry.npmjs.org/@azure/cosmos/-/cosmos-4.2.0-beta.1.tgz", "resolved": "https://registry.npmjs.org/@azure/cosmos/-/cosmos-4.2.0-beta.1.tgz",
"integrity": "sha512-mREONehm1DxjEKXGaNU6Wmpf9Ckb9IrhKFXhDFVs45pxmoEb3y2s/Ub0owuFmqlphpcS1zgtYQn5exn+lwnJuQ==", "integrity": "sha512-mREONehm1DxjEKXGaNU6Wmpf9Ckb9IrhKFXhDFVs45pxmoEb3y2s/Ub0owuFmqlphpcS1zgtYQn5exn+lwnJuQ==",
=======
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/@azure/cosmos/-/cosmos-4.5.0.tgz",
"integrity": "sha512-JsTh4twb6FcwP7rJwxQiNZQ/LGtuF6gmciaxY9Rnp6/A325Lhsw/SH4R2ArpT0yCvozbZpweIwdPfUkXVBtp5w==",
>>>>>>> master
"license": "MIT",
"dependencies": { "dependencies": {
"@azure/abort-controller": "^2.0.0", "@azure/abort-controller": "^2.1.2",
"@azure/core-auth": "^1.7.1", "@azure/core-auth": "^1.9.0",
"@azure/core-rest-pipeline": "^1.15.1", "@azure/core-rest-pipeline": "^1.19.1",
"@azure/core-tracing": "^1.1.1", "@azure/core-tracing": "^1.2.0",
"@azure/core-util": "^1.8.1", "@azure/core-util": "^1.11.0",
"@azure/keyvault-keys": "^4.9.0",
"@azure/logger": "^1.1.4",
"fast-json-stable-stringify": "^2.1.0", "fast-json-stable-stringify": "^2.1.0",
"jsbi": "^4.3.0",
"priorityqueuejs": "^2.0.0", "priorityqueuejs": "^2.0.0",
"semaphore": "^1.1.0", "semaphore": "^1.1.0",
"tslib": "^2.6.2" "tslib": "^2.8.1"
}, },
"engines": { "engines": {
"node": ">=18.0.0" "node": ">=20.0.0"
} }
}, },
"node_modules/@azure/cosmos-language-service": { "node_modules/@azure/cosmos-language-service": {
@@ -425,8 +497,14 @@
} }
}, },
"node_modules/@azure/cosmos/node_modules/tslib": { "node_modules/@azure/cosmos/node_modules/tslib": {
"version": "2.6.2", "version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
<<<<<<< HEAD
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD" "license": "0BSD"
=======
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
>>>>>>> master
}, },
"node_modules/@azure/identity": { "node_modules/@azure/identity": {
"version": "4.5.0", "version": "4.5.0",
@@ -492,14 +570,66 @@
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
}, },
"node_modules/@azure/logger": { "node_modules/@azure/keyvault-common": {
"version": "1.0.4", "version": "2.0.0",
"license": "MIT", "resolved": "https://registry.npmjs.org/@azure/keyvault-common/-/keyvault-common-2.0.0.tgz",
"integrity": "sha512-wRLVaroQtOqfg60cxkzUkGKrKMsCP6uYXAOomOIysSMyt1/YM0eUn9LqieAWM8DLcU4+07Fio2YGpPeqUbpP9w==",
"dependencies": { "dependencies": {
"@azure/abort-controller": "^2.0.0",
"@azure/core-auth": "^1.3.0",
"@azure/core-client": "^1.5.0",
"@azure/core-rest-pipeline": "^1.8.0",
"@azure/core-tracing": "^1.0.0",
"@azure/core-util": "^1.10.0",
"@azure/logger": "^1.1.4",
"tslib": "^2.2.0" "tslib": "^2.2.0"
}, },
"engines": { "engines": {
"node": ">=14.0.0" "node": ">=18.0.0"
}
},
"node_modules/@azure/keyvault-common/node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
},
"node_modules/@azure/keyvault-keys": {
"version": "4.9.0",
"resolved": "https://registry.npmjs.org/@azure/keyvault-keys/-/keyvault-keys-4.9.0.tgz",
"integrity": "sha512-ZBP07+K4Pj3kS4TF4XdkqFcspWwBHry3vJSOFM5k5ZABvf7JfiMonvaFk2nBF6xjlEbMpz5PE1g45iTMme0raQ==",
"dependencies": {
"@azure/abort-controller": "^2.0.0",
"@azure/core-auth": "^1.3.0",
"@azure/core-client": "^1.5.0",
"@azure/core-http-compat": "^2.0.1",
"@azure/core-lro": "^2.2.0",
"@azure/core-paging": "^1.1.1",
"@azure/core-rest-pipeline": "^1.8.1",
"@azure/core-tracing": "^1.0.0",
"@azure/core-util": "^1.0.0",
"@azure/keyvault-common": "^2.0.0",
"@azure/logger": "^1.0.0",
"tslib": "^2.2.0"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/@azure/keyvault-keys/node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
},
"node_modules/@azure/logger": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.2.0.tgz",
"integrity": "sha512-0hKEzLhpw+ZTAfNJyRrn6s+V0nDWzXk9OjBr2TiGIu0OfMr5s2V4FpKLTAK3Ca5r5OKLbf4hkOGDPyiRjie/jA==",
"dependencies": {
"@typespec/ts-http-runtime": "^0.2.2",
"tslib": "^2.6.2"
},
"engines": {
"node": ">=18.0.0"
} }
}, },
"node_modules/@azure/logger/node_modules/tslib": { "node_modules/@azure/logger/node_modules/tslib": {
@@ -7826,7 +7956,7 @@
} }
}, },
"node_modules/@nteract/data-explorer/node_modules/cross-spawn": { "node_modules/@nteract/data-explorer/node_modules/cross-spawn": {
"version": "6.0.5", "version": "7.0.5",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"nice-try": "^1.0.4", "nice-try": "^1.0.4",
@@ -7950,7 +8080,7 @@
"version": "1.0.0", "version": "1.0.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"cross-spawn": "^6.0.0", "cross-spawn": "^7.0.5",
"get-stream": "^4.0.0", "get-stream": "^4.0.0",
"is-stream": "^1.1.0", "is-stream": "^1.1.0",
"npm-run-path": "^2.0.0", "npm-run-path": "^2.0.0",
@@ -13074,6 +13204,56 @@
"url": "https://opencollective.com/typescript-eslint" "url": "https://opencollective.com/typescript-eslint"
} }
}, },
"node_modules/@typespec/ts-http-runtime": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/@typespec/ts-http-runtime/-/ts-http-runtime-0.2.2.tgz",
"integrity": "sha512-Gz/Sm64+Sq/vklJu1tt9t+4R2lvnud8NbTD/ZfpZtMiUX7YeVpCA8j6NSW8ptwcoLL+NmYANwqP8DV0q/bwl2w==",
"dependencies": {
"http-proxy-agent": "^7.0.0",
"https-proxy-agent": "^7.0.0",
"tslib": "^2.6.2"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/@typespec/ts-http-runtime/node_modules/agent-base": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz",
"integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==",
"engines": {
"node": ">= 14"
}
},
"node_modules/@typespec/ts-http-runtime/node_modules/http-proxy-agent": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
"integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
"dependencies": {
"agent-base": "^7.1.0",
"debug": "^4.3.4"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/@typespec/ts-http-runtime/node_modules/https-proxy-agent": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
"integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
"dependencies": {
"agent-base": "^7.1.2",
"debug": "4"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/@typespec/ts-http-runtime/node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
},
"node_modules/@ungap/url-search-params": { "node_modules/@ungap/url-search-params": {
"version": "0.2.2", "version": "0.2.2",
"license": "ISC" "license": "ISC"
@@ -16328,7 +16508,7 @@
} }
}, },
"node_modules/cross-spawn": { "node_modules/cross-spawn": {
"version": "7.0.3", "version": "7.0.5",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"path-key": "^3.1.0", "path-key": "^3.1.0",
@@ -18242,7 +18422,7 @@
"@nodelib/fs.walk": "^1.2.8", "@nodelib/fs.walk": "^1.2.8",
"ajv": "^6.12.4", "ajv": "^6.12.4",
"chalk": "^4.0.0", "chalk": "^4.0.0",
"cross-spawn": "^7.0.2", "cross-spawn": "^7.0.5",
"debug": "^4.3.2", "debug": "^4.3.2",
"doctrine": "^3.0.0", "doctrine": "^3.0.0",
"escape-string-regexp": "^4.0.0", "escape-string-regexp": "^4.0.0",
@@ -18829,7 +19009,7 @@
"integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==",
"devOptional": true, "devOptional": true,
"dependencies": { "dependencies": {
"cross-spawn": "^7.0.3", "cross-spawn": "^7.0.5",
"get-stream": "^6.0.0", "get-stream": "^6.0.0",
"human-signals": "^2.1.0", "human-signals": "^2.1.0",
"is-stream": "^2.0.0", "is-stream": "^2.0.0",
@@ -19094,7 +19274,7 @@
"methods": "~1.1.2", "methods": "~1.1.2",
"on-finished": "2.4.1", "on-finished": "2.4.1",
"parseurl": "~1.3.3", "parseurl": "~1.3.3",
"path-to-regexp": "0.1.7", "path-to-regexp": "0.1.12",
"proxy-addr": "~2.0.7", "proxy-addr": "~2.0.7",
"qs": "6.11.0", "qs": "6.11.0",
"range-parser": "~1.2.1", "range-parser": "~1.2.1",
@@ -19130,7 +19310,7 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/express/node_modules/path-to-regexp": { "node_modules/express/node_modules/path-to-regexp": {
"version": "0.1.7", "version": "0.1.12",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
@@ -27063,11 +27243,6 @@
"js-yaml": "bin/js-yaml.js" "js-yaml": "bin/js-yaml.js"
} }
}, },
"node_modules/jsbi": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/jsbi/-/jsbi-4.3.0.tgz",
"integrity": "sha512-SnZNcinB4RIcnEyZqFPdGPVgrg2AcnykiBy0sHVJQKHYeaLUvi3Exj+iaPpLnFVkDPZIV4U0yvgC9/R4uEAZ9g=="
},
"node_modules/jsbn": { "node_modules/jsbn": {
"version": "0.1.1", "version": "0.1.1",
"license": "MIT" "license": "MIT"
@@ -30423,7 +30598,7 @@
"@yarnpkg/lockfile": "^1.1.0", "@yarnpkg/lockfile": "^1.1.0",
"chalk": "^4.1.2", "chalk": "^4.1.2",
"ci-info": "^3.7.0", "ci-info": "^3.7.0",
"cross-spawn": "^7.0.3", "cross-spawn": "^7.0.5",
"find-yarn-workspace-root": "^2.0.0", "find-yarn-workspace-root": "^2.0.0",
"fs-extra": "^9.0.0", "fs-extra": "^9.0.0",
"json-stable-stringify": "^1.0.2", "json-stable-stringify": "^1.0.2",
@@ -31401,7 +31576,7 @@
"address": "^1.1.2", "address": "^1.1.2",
"browserslist": "^4.18.1", "browserslist": "^4.18.1",
"chalk": "^4.1.2", "chalk": "^4.1.2",
"cross-spawn": "^7.0.3", "cross-spawn": "^7.0.5",
"detect-port-alt": "^1.1.6", "detect-port-alt": "^1.1.6",
"escape-string-regexp": "^4.0.0", "escape-string-regexp": "^4.0.0",
"filesize": "^8.0.6", "filesize": "^8.0.6",
@@ -32857,7 +33032,7 @@
} }
}, },
"node_modules/sane/node_modules/cross-spawn": { "node_modules/sane/node_modules/cross-spawn": {
"version": "6.0.5", "version": "7.0.5",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"nice-try": "^1.0.4", "nice-try": "^1.0.4",
@@ -32874,7 +33049,7 @@
"version": "1.0.0", "version": "1.0.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"cross-spawn": "^6.0.0", "cross-spawn": "^7.0.5",
"get-stream": "^4.0.0", "get-stream": "^4.0.0",
"is-stream": "^1.1.0", "is-stream": "^1.1.0",
"npm-run-path": "^2.0.0", "npm-run-path": "^2.0.0",
@@ -35843,7 +36018,7 @@
"@webpack-cli/serve": "^2.0.5", "@webpack-cli/serve": "^2.0.5",
"colorette": "^2.0.14", "colorette": "^2.0.14",
"commander": "^10.0.1", "commander": "^10.0.1",
"cross-spawn": "^7.0.3", "cross-spawn": "^7.0.5",
"envinfo": "^7.7.3", "envinfo": "^7.7.3",
"fastest-levenshtein": "^1.0.12", "fastest-levenshtein": "^1.0.12",
"import-local": "^3.0.2", "import-local": "^3.0.2",
@@ -36368,7 +36543,7 @@
} }
}, },
"node_modules/windows-release/node_modules/cross-spawn": { "node_modules/windows-release/node_modules/cross-spawn": {
"version": "6.0.5", "version": "7.0.5",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"nice-try": "^1.0.4", "nice-try": "^1.0.4",
@@ -36385,7 +36560,7 @@
"version": "1.0.0", "version": "1.0.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"cross-spawn": "^6.0.0", "cross-spawn": "^7.0.5",
"get-stream": "^4.0.0", "get-stream": "^4.0.0",
"is-stream": "^1.1.0", "is-stream": "^1.1.0",
"npm-run-path": "^2.0.0", "npm-run-path": "^2.0.0",

View File

@@ -5,7 +5,7 @@
"main": "index.js", "main": "index.js",
"dependencies": { "dependencies": {
"@azure/arm-cosmosdb": "9.1.0", "@azure/arm-cosmosdb": "9.1.0",
"@azure/cosmos": "4.2.0-beta.1", "@azure/cosmos": "4.5.0",
"@azure/cosmos-language-service": "0.0.5", "@azure/cosmos-language-service": "0.0.5",
"@azure/identity": "4.5.0", "@azure/identity": "4.5.0",
"@azure/msal-browser": "2.14.2", "@azure/msal-browser": "2.14.2",
@@ -206,9 +206,11 @@
"build:dataExplorer:ci": "npm run build:ci", "build:dataExplorer:ci": "npm run build:ci",
"build": "npm run format:check && npm run lint && npm run compile && npm run compile:strict && npm run pack:prod && npm run copyToConsumers", "build": "npm run format:check && npm run lint && npm run compile && npm run compile:strict && npm run pack:prod && npm run copyToConsumers",
"build:ci": "npm run format:check && npm run lint && npm run compile && npm run compile:strict && npm run pack:fast", "build:ci": "npm run format:check && npm run lint && npm run compile && npm run compile:strict && npm run pack:fast",
"build:proxy": "npm run compile && npm run compile:strict && npm run pack:prod && npm run copyToProxy",
"pack:prod": "webpack --mode production", "pack:prod": "webpack --mode production",
"pack:fast": "webpack --mode development --progress", "pack:fast": "webpack --mode development --progress",
"copyToConsumers": "node copyToConsumers", "copyToConsumers": "node copyToConsumers",
"copyToProxy": "rm -rf ./utils/local-proxy/dist && cp -r ./dist ./utils/local-proxy",
"test": "rimraf coverage && jest", "test": "rimraf coverage && jest",
"test:debug": "jest --runInBand", "test:debug": "jest --runInBand",
"test:e2e": "jest -c ./jest.config.playwright.js --detectOpenHandles", "test:e2e": "jest -c ./jest.config.playwright.js --detectOpenHandles",

View File

@@ -10,7 +10,7 @@
"hasInstallScript": true, "hasInstallScript": true,
"dependencies": { "dependencies": {
"@azure/arm-cosmosdb": "9.1.0", "@azure/arm-cosmosdb": "9.1.0",
"@azure/cosmos": "4.2.0-beta.1", "@azure/cosmos": "4.3.0",
"@azure/cosmos-language-service": "0.0.5", "@azure/cosmos-language-service": "0.0.5",
"@azure/identity": "4.5.0", "@azure/identity": "4.5.0",
"@azure/msal-browser": "2.14.2", "@azure/msal-browser": "2.14.2",
@@ -377,8 +377,8 @@
"license": "0BSD" "license": "0BSD"
}, },
"node_modules/@azure/cosmos": { "node_modules/@azure/cosmos": {
"version": "4.2.0-beta.1", "version": "4.3.0",
"resolved": "https://registry.npmjs.org/@azure/cosmos/-/cosmos-4.2.0-beta.1.tgz", "resolved": "https://registry.npmjs.org/@azure/cosmos/-/cosmos-4.3.0.tgz",
"integrity": "sha512-mREONehm1DxjEKXGaNU6Wmpf9Ckb9IrhKFXhDFVs45pxmoEb3y2s/Ub0owuFmqlphpcS1zgtYQn5exn+lwnJuQ==", "integrity": "sha512-mREONehm1DxjEKXGaNU6Wmpf9Ckb9IrhKFXhDFVs45pxmoEb3y2s/Ub0owuFmqlphpcS1zgtYQn5exn+lwnJuQ==",
"dependencies": { "dependencies": {
"@azure/abort-controller": "^2.0.0", "@azure/abort-controller": "^2.0.0",
@@ -19077,9 +19077,9 @@
"methods": "~1.1.2", "methods": "~1.1.2",
"on-finished": "2.4.1", "on-finished": "2.4.1",
"parseurl": "~1.3.3", "parseurl": "~1.3.3",
"path-to-regexp": "0.1.7", "path-to-regexp": "0.1.12",
"proxy-addr": "~2.0.7", "proxy-addr": "~2.0.5",
"qs": "6.11.0", "qs": "6.7.0",
"range-parser": "~1.2.1", "range-parser": "~1.2.1",
"safe-buffer": "5.2.1", "safe-buffer": "5.2.1",
"send": "0.18.0", "send": "0.18.0",
@@ -35537,12 +35537,420 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/verror": { "@types/node": {
"version": "1.10.0", "version": "14.14.37",
"engines": [ "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.37.tgz",
"node >=0.6.0" "integrity": "sha512-XYmBiy+ohOR4Lh5jE379fV2IU+6Jn4g5qASinhitfyO71b/sCo6MKsMLF5tc7Zf2CE8hViVQyYSobJNke8OvUw=="
], },
"license": "MIT", "accepts": {
"version": "1.3.7",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
"integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==",
"requires": {
"mime-types": "~2.1.24",
"negotiator": "0.6.2"
}
},
"array-flatten": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
},
"body-parser": {
"version": "1.19.0",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
"integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==",
"requires": {
"bytes": "3.1.0",
"content-type": "~1.0.4",
"debug": "2.6.9",
"depd": "~1.1.2",
"http-errors": "1.7.2",
"iconv-lite": "0.4.24",
"on-finished": "~2.3.0",
"qs": "6.7.0",
"raw-body": "2.4.0",
"type-is": "~1.6.17"
}
},
"braces": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
"requires": {
"fill-range": "^7.0.1"
}
},
"bytes": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
"integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg=="
},
"camelcase": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz",
"integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg=="
},
"content-disposition": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz",
"integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==",
"requires": {
"safe-buffer": "5.1.2"
}
},
"content-type": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
"integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="
},
"cookie": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
"integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg=="
},
"cookie-signature": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
},
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"requires": {
"ms": "2.0.0"
}
},
"depd": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
"integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
},
"destroy": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
"integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
},
"ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
},
"encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
},
"escape-html": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
"integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
},
"etag": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
"integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
},
"eventemitter3": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="
},
"express": {
"version": "4.17.1",
"resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz",
"integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==",
"requires": {
"accepts": "~1.3.7",
"array-flatten": "1.1.1",
"body-parser": "1.19.0",
"content-disposition": "0.5.3",
"content-type": "~1.0.4",
"cookie": "0.4.0",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
"depd": "~1.1.2",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"finalhandler": "~1.1.2",
"fresh": "0.5.2",
"merge-descriptors": "1.0.1",
"methods": "~1.1.2",
"on-finished": "~2.3.0",
"parseurl": "~1.3.3",
"path-to-regexp": "0.1.12",
"proxy-addr": "~2.0.5",
"qs": "6.7.0",
"range-parser": "~1.2.1",
"safe-buffer": "5.1.2",
"send": "0.17.1",
"serve-static": "1.14.1",
"setprototypeof": "1.1.1",
"statuses": "~1.5.0",
"type-is": "~1.6.18",
"utils-merge": "1.0.1",
"vary": "~1.1.2"
}
},
"fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
"requires": {
"to-regex-range": "^5.0.1"
}
},
"finalhandler": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
"integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
"requires": {
"debug": "2.6.9",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"on-finished": "~2.3.0",
"parseurl": "~1.3.3",
"statuses": "~1.5.0",
"unpipe": "~1.0.0"
}
},
"follow-redirects": {
"version": "1.13.3",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.3.tgz",
"integrity": "sha512-DUgl6+HDzB0iEptNQEXLx/KhTmDb8tZUHSeLqpnjpknR70H0nC2t9N73BK6fN4hOvJ84pKlIQVQ4k5FFlBedKA=="
},
"forwarded": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
"integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ="
},
"fresh": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
"integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
},
"http-errors": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
"integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==",
"requires": {
"depd": "~1.1.2",
"inherits": "2.0.3",
"setprototypeof": "1.1.1",
"statuses": ">= 1.5.0 < 2",
"toidentifier": "1.0.0"
}
},
"http-proxy": {
"version": "1.18.1",
"resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz",
"integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==",
"requires": {
"eventemitter3": "^4.0.0",
"follow-redirects": "^1.0.0",
"requires-port": "^1.0.0"
}
},
"http-proxy-middleware": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-1.1.0.tgz",
"integrity": "sha512-OnjU5vyVgcZVe2AjLJyMrk8YLNOC2lspCHirB5ldM+B/dwEfZ5bgVTrFyzE9R7xRWAP/i/FXtvIqKjTNEZBhBg==",
"requires": {
"@types/http-proxy": "^1.17.5",
"camelcase": "^6.2.0",
"http-proxy": "^1.18.1",
"is-glob": "^4.0.1",
"is-plain-obj": "^3.0.0",
"micromatch": "^4.0.2"
}
},
"iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
"requires": {
"safer-buffer": ">= 2.1.2 < 3"
}
},
"inherits": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
},
"ipaddr.js": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="
},
"is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI="
},
"is-glob": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
"integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
"requires": {
"is-extglob": "^2.1.1"
}
},
"is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="
},
"is-plain-obj": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz",
"integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA=="
},
"media-typer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
},
"merge-descriptors": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
"integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
},
"methods": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
"integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
},
"micromatch": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz",
"integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==",
"requires": {
"braces": "^3.0.1",
"picomatch": "^2.0.5"
}
},
"mime": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="
},
"mime-db": {
"version": "1.46.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.46.0.tgz",
"integrity": "sha512-svXaP8UQRZ5K7or+ZmfNhg2xX3yKDMUzqadsSqi4NCH/KomcH75MAMYAGVlvXn4+b/xOPhS3I2uHKRUzvjY7BQ=="
},
"mime-types": {
"version": "2.1.29",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.29.tgz",
"integrity": "sha512-Y/jMt/S5sR9OaqteJtslsFZKWOIIqMACsJSiHghlCAyhf7jfVYjKBmLiX8OgpWeW+fjJ2b+Az69aPFPkUOY6xQ==",
"requires": {
"mime-db": "1.46.0"
}
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
},
"negotiator": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
"integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw=="
},
"node-fetch": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz",
"integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw=="
},
"on-finished": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
"integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
"requires": {
"ee-first": "1.1.1"
}
},
"parseurl": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="
},
"path-to-regexp": {
"version": "0.1.12",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
"integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ=="
},
"picomatch": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz",
"integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg=="
},
"proxy-addr": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz",
"integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==",
"requires": {
"forwarded": "~0.1.2",
"ipaddr.js": "1.9.1"
}
},
"qs": {
"version": "6.7.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
"integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ=="
},
"range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
},
"raw-body": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz",
"integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==",
"requires": {
"bytes": "3.1.0",
"http-errors": "1.7.2",
"iconv-lite": "0.4.24",
"unpipe": "1.0.0"
}
},
"requires-port": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
"integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8="
},
"safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
},
"safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"send": {
"version": "0.17.1",
"resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz",
"integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==",
"requires": {
"debug": "2.6.9",
"depd": "~1.1.2",
"destroy": "~1.0.4",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"fresh": "0.5.2",
"http-errors": "~1.7.2",
"mime": "1.6.0",
"ms": "2.1.1",
"on-finished": "~2.3.0",
"range-parser": "~1.2.1",
"statuses": "~1.5.0"
},
"dependencies": { "dependencies": {
"assert-plus": "^1.0.0", "assert-plus": "^1.0.0",
"core-util-is": "1.0.2", "core-util-is": "1.0.2",

View File

@@ -138,15 +138,6 @@ export enum MongoBackendEndpointType {
remote, remote,
} }
export class BackendApi {
public static readonly GenerateToken: string = "GenerateToken";
public static readonly PortalSettings: string = "PortalSettings";
public static readonly AccountRestrictions: string = "AccountRestrictions";
public static readonly RuntimeProxy: string = "RuntimeProxy";
public static readonly DisallowedLocations: string = "DisallowedLocations";
public static readonly SampleData: string = "SampleData";
}
export class PortalBackendEndpoints { export class PortalBackendEndpoints {
public static readonly Development: string = "https://localhost:7235"; public static readonly Development: string = "https://localhost:7235";
public static readonly Mpac: string = "https://cdb-ms-mpac-pbe.cosmos.azure.com"; public static readonly Mpac: string = "https://cdb-ms-mpac-pbe.cosmos.azure.com";

View File

@@ -4,12 +4,12 @@ import { CosmosDbArtifactType } from "Contracts/FabricMessagesContract";
import { AuthorizationToken } from "Contracts/FabricMessageTypes"; import { AuthorizationToken } from "Contracts/FabricMessageTypes";
import { checkDatabaseResourceTokensValidity, isFabricMirroredKey } from "Platform/Fabric/FabricUtil"; import { checkDatabaseResourceTokensValidity, isFabricMirroredKey } from "Platform/Fabric/FabricUtil";
import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility"; import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
import { useDataplaneRbacAuthorization } from "Utils/AuthorizationUtils";
import { AuthType } from "../AuthType"; import { AuthType } from "../AuthType";
import { PriorityLevel } from "../Common/Constants"; import { PriorityLevel } from "../Common/Constants";
import * as Logger from "../Common/Logger"; import * as Logger from "../Common/Logger";
import { Platform, configContext } from "../ConfigContext"; import { Platform, configContext } from "../ConfigContext";
import { FabricArtifactInfo, updateUserContext, userContext } from "../UserContext"; import { FabricArtifactInfo, updateUserContext, userContext } from "../UserContext";
import { isDataplaneRbacSupported } from "../Utils/APITypeUtils";
import { logConsoleError } from "../Utils/NotificationConsoleUtils"; import { logConsoleError } from "../Utils/NotificationConsoleUtils";
import * as PriorityBasedExecutionUtils from "../Utils/PriorityBasedExecutionUtils"; import * as PriorityBasedExecutionUtils from "../Utils/PriorityBasedExecutionUtils";
import { EmulatorMasterKey, HttpHeaders } from "./Constants"; import { EmulatorMasterKey, HttpHeaders } from "./Constants";
@@ -20,8 +20,7 @@ const _global = typeof self === "undefined" ? window : self;
export const tokenProvider = async (requestInfo: Cosmos.RequestInfo) => { export const tokenProvider = async (requestInfo: Cosmos.RequestInfo) => {
const { verb, resourceId, resourceType, headers } = requestInfo; const { verb, resourceId, resourceType, headers } = requestInfo;
const dataPlaneRBACOptionEnabled = userContext.dataPlaneRbacEnabled && isDataplaneRbacSupported(userContext.apiType); if (useDataplaneRbacAuthorization(userContext)) {
if (userContext.features.enableAadDataPlane || dataPlaneRBACOptionEnabled) {
Logger.logInfo( Logger.logInfo(
`AAD Data Plane Feature flag set to ${userContext.features.enableAadDataPlane} for account with disable local auth ${userContext.databaseAccount.properties.disableLocalAuth} `, `AAD Data Plane Feature flag set to ${userContext.features.enableAadDataPlane} for account with disable local auth ${userContext.databaseAccount.properties.disableLocalAuth} `,
"Explorer/tokenProvider", "Explorer/tokenProvider",

View File

@@ -1,4 +1,3 @@
import { QueryOperationOptions } from "@azure/cosmos";
import { Action } from "Shared/Telemetry/TelemetryConstants"; import { Action } from "Shared/Telemetry/TelemetryConstants";
import * as Constants from "../Common/Constants"; import * as Constants from "../Common/Constants";
import { QueryResults } from "../Contracts/ViewModels"; import { QueryResults } from "../Contracts/ViewModels";
@@ -14,18 +13,14 @@ interface QueryResponse {
} }
export interface MinimalQueryIterator { export interface MinimalQueryIterator {
fetchNext: (queryOperationOptions?: QueryOperationOptions) => Promise<QueryResponse>; fetchNext: () => Promise<QueryResponse>;
} }
// Pick<QueryIterator<any>, "fetchNext">; // Pick<QueryIterator<any>, "fetchNext">;
export function nextPage( export function nextPage(documentsIterator: MinimalQueryIterator, firstItemIndex: number): Promise<QueryResults> {
documentsIterator: MinimalQueryIterator,
firstItemIndex: number,
queryOperationOptions?: QueryOperationOptions,
): Promise<QueryResults> {
TelemetryProcessor.traceStart(Action.ExecuteQuery); TelemetryProcessor.traceStart(Action.ExecuteQuery);
return documentsIterator.fetchNext(queryOperationOptions).then((response) => { return documentsIterator.fetchNext().then((response) => {
TelemetryProcessor.traceSuccess(Action.ExecuteQuery, { dataExplorerArea: Constants.Areas.Tab }); TelemetryProcessor.traceSuccess(Action.ExecuteQuery, { dataExplorerArea: Constants.Areas.Tab });
const documents = response.resources; const documents = response.resources;
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any

View File

@@ -65,7 +65,6 @@ describe("MongoProxyClient", () => {
}); });
updateConfigContext({ updateConfigContext({
MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Prod, MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Prod,
globallyEnabledMongoAPIs: [],
}); });
window.fetch = jest.fn().mockImplementation(fetchMock); window.fetch = jest.fn().mockImplementation(fetchMock);
}); });
@@ -84,7 +83,6 @@ describe("MongoProxyClient", () => {
it("builds the correct proxy URL in development", () => { it("builds the correct proxy URL in development", () => {
updateConfigContext({ updateConfigContext({
MONGO_PROXY_ENDPOINT: "https://localhost:1234", MONGO_PROXY_ENDPOINT: "https://localhost:1234",
globallyEnabledMongoAPIs: [],
}); });
queryDocuments(databaseId, collection, true, "{}"); queryDocuments(databaseId, collection, true, "{}");
expect(window.fetch).toHaveBeenCalledWith( expect(window.fetch).toHaveBeenCalledWith(
@@ -101,7 +99,6 @@ describe("MongoProxyClient", () => {
}); });
updateConfigContext({ updateConfigContext({
MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Prod, MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Prod,
globallyEnabledMongoAPIs: [],
}); });
window.fetch = jest.fn().mockImplementation(fetchMock); window.fetch = jest.fn().mockImplementation(fetchMock);
}); });
@@ -120,7 +117,6 @@ describe("MongoProxyClient", () => {
it("builds the correct proxy URL in development", () => { it("builds the correct proxy URL in development", () => {
updateConfigContext({ updateConfigContext({
MONGO_PROXY_ENDPOINT: "https://localhost:1234", MONGO_PROXY_ENDPOINT: "https://localhost:1234",
globallyEnabledMongoAPIs: [],
}); });
readDocument(databaseId, collection, documentId); readDocument(databaseId, collection, documentId);
expect(window.fetch).toHaveBeenCalledWith( expect(window.fetch).toHaveBeenCalledWith(
@@ -137,7 +133,6 @@ describe("MongoProxyClient", () => {
}); });
updateConfigContext({ updateConfigContext({
MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Prod, MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Prod,
globallyEnabledMongoAPIs: [],
}); });
window.fetch = jest.fn().mockImplementation(fetchMock); window.fetch = jest.fn().mockImplementation(fetchMock);
}); });
@@ -156,7 +151,6 @@ describe("MongoProxyClient", () => {
it("builds the correct proxy URL in development", () => { it("builds the correct proxy URL in development", () => {
updateConfigContext({ updateConfigContext({
MONGO_PROXY_ENDPOINT: "https://localhost:1234", MONGO_PROXY_ENDPOINT: "https://localhost:1234",
globallyEnabledMongoAPIs: [],
}); });
readDocument(databaseId, collection, documentId); readDocument(databaseId, collection, documentId);
expect(window.fetch).toHaveBeenCalledWith( expect(window.fetch).toHaveBeenCalledWith(
@@ -173,7 +167,6 @@ describe("MongoProxyClient", () => {
}); });
updateConfigContext({ updateConfigContext({
MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Prod, MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Prod,
globallyEnabledMongoAPIs: [],
}); });
window.fetch = jest.fn().mockImplementation(fetchMock); window.fetch = jest.fn().mockImplementation(fetchMock);
}); });
@@ -197,7 +190,6 @@ describe("MongoProxyClient", () => {
}); });
updateConfigContext({ updateConfigContext({
MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Prod, MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Prod,
globallyEnabledMongoAPIs: [],
}); });
window.fetch = jest.fn().mockImplementation(fetchMock); window.fetch = jest.fn().mockImplementation(fetchMock);
}); });
@@ -216,7 +208,6 @@ describe("MongoProxyClient", () => {
it("builds the correct proxy URL in development", () => { it("builds the correct proxy URL in development", () => {
updateConfigContext({ updateConfigContext({
MONGO_PROXY_ENDPOINT: "https://localhost:1234", MONGO_PROXY_ENDPOINT: "https://localhost:1234",
globallyEnabledMongoAPIs: [],
}); });
deleteDocuments(databaseId, collection, [documentId]); deleteDocuments(databaseId, collection, [documentId]);
expect(window.fetch).toHaveBeenCalledWith( expect(window.fetch).toHaveBeenCalledWith(
@@ -233,7 +224,6 @@ describe("MongoProxyClient", () => {
}); });
updateConfigContext({ updateConfigContext({
MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Prod, MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Prod,
globallyEnabledMongoAPIs: [],
}); });
}); });

View File

@@ -1,5 +1,4 @@
import { monaco } from "Explorer/LazyMonaco"; import { monaco } from "Explorer/LazyMonaco";
import { getRUThreshold, ruThresholdEnabled } from "Shared/StorageUtility";
export enum QueryErrorSeverity { export enum QueryErrorSeverity {
Error = "Error", Error = "Error",
@@ -103,20 +102,9 @@ export interface ErrorEnrichment {
learnMoreUrl?: string; learnMoreUrl?: string;
} }
const REPLACEMENT_MESSAGES: Record<string, (original: string) => string> = { const REPLACEMENT_MESSAGES: Record<string, (original: string) => string> = {};
OPERATION_RU_LIMIT_EXCEEDED: (original) => {
if (ruThresholdEnabled()) {
const threshold = getRUThreshold();
return `Query exceeded the Request Unit (RU) limit of ${threshold} RUs. You can change this limit in Data Explorer settings.`;
}
return original;
},
};
const HELP_LINKS: Record<string, string> = { const HELP_LINKS: Record<string, string> = {};
OPERATION_RU_LIMIT_EXCEEDED:
"https://learn.microsoft.com/en-us/azure/cosmos-db/data-explorer#configure-request-unit-threshold",
};
export default class QueryError { export default class QueryError {
message: string; message: string;

View File

@@ -2,7 +2,7 @@
exports[`getCommonQueryOptions builds the correct default options objects 1`] = ` exports[`getCommonQueryOptions builds the correct default options objects 1`] = `
{ {
"disableNonStreamingOrderByQuery": true, "enableQueryControl": false,
"enableScanInQuery": true, "enableScanInQuery": true,
"forceQueryPlan": true, "forceQueryPlan": true,
"maxDegreeOfParallelism": 0, "maxDegreeOfParallelism": 0,
@@ -13,7 +13,7 @@ exports[`getCommonQueryOptions builds the correct default options objects 1`] =
exports[`getCommonQueryOptions reads from localStorage 1`] = ` exports[`getCommonQueryOptions reads from localStorage 1`] = `
{ {
"disableNonStreamingOrderByQuery": true, "enableQueryControl": false,
"enableScanInQuery": true, "enableScanInQuery": true,
"forceQueryPlan": true, "forceQueryPlan": true,
"maxDegreeOfParallelism": 17, "maxDegreeOfParallelism": 17,

View File

@@ -42,6 +42,7 @@ export interface IBulkDeleteResult {
export const deleteDocuments = async ( export const deleteDocuments = async (
collection: CollectionBase, collection: CollectionBase,
documentIds: DocumentId[], documentIds: DocumentId[],
abortSignal: AbortSignal,
): Promise<IBulkDeleteResult[]> => { ): Promise<IBulkDeleteResult[]> => {
const clearMessage = logConsoleProgress(`Deleting ${documentIds.length} ${getEntityName(true)}`); const clearMessage = logConsoleProgress(`Deleting ${documentIds.length} ${getEntityName(true)}`);
try { try {
@@ -65,7 +66,11 @@ export const deleteDocuments = async (
operationType: BulkOperationType.Delete, operationType: BulkOperationType.Delete,
})); }));
const promise = v2Container.items.bulk(operations).then((bulkResults) => { const promise = v2Container.items
.bulk(operations, undefined, {
abortSignal,
})
.then((bulkResults) => {
return bulkResults.map((bulkResult, index) => { return bulkResults.map((bulkResult, index) => {
const documentId = documentIdsChunk[index]; const documentId = documentIdsChunk[index];
return { ...bulkResult, documentId }; return { ...bulkResult, documentId };

View File

@@ -1,3 +1,4 @@
import { isFabricNative } from "Platform/Fabric/FabricUtil";
import { AuthType } from "../../AuthType"; import { AuthType } from "../../AuthType";
import { configContext } from "../../ConfigContext"; import { configContext } from "../../ConfigContext";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
@@ -41,7 +42,7 @@ interface MetricsResponse {
} }
export const getCollectionUsageSizeInKB = async (databaseName: string, containerName: string): Promise<number> => { export const getCollectionUsageSizeInKB = async (databaseName: string, containerName: string): Promise<number> => {
if (userContext.authType !== AuthType.AAD) { if (userContext.authType !== AuthType.AAD || isFabricNative()) {
return undefined; return undefined;
} }

View File

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

View File

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

View File

@@ -126,12 +126,5 @@ async function readCollectionsWithARM(databaseId: string): Promise<DataModels.Co
throw new Error(`Unsupported default experience type: ${apiType}`); throw new Error(`Unsupported default experience type: ${apiType}`);
} }
// TO DO: Remove when we get RP API Spec with materializedViews return rpResponse?.value?.map((collection) => collection.properties?.resource as DataModels.Collection);
/* eslint-disable @typescript-eslint/no-explicit-any */
return rpResponse?.value?.map((collection: any) => {
const collectionDataModel: DataModels.Collection = collection.properties?.resource as DataModels.Collection;
collectionDataModel.materializedViews = collection.properties?.resource?.materializedViews;
collectionDataModel.materializedViewDefinition = collection.properties?.resource?.materializedViewDefinition;
return collectionDataModel;
});
} }

View File

@@ -1,21 +1,15 @@
import { CassandraProxyEndpoints, JunoEndpoints, MongoProxyEndpoints, PortalBackendEndpoints } from "Common/Constants";
import { import {
BackendApi,
CassandraProxyEndpoints,
JunoEndpoints,
MongoProxyEndpoints,
PortalBackendEndpoints,
} from "Common/Constants";
import {
allowedAadEndpoints,
allowedArcadiaEndpoints, allowedArcadiaEndpoints,
allowedEmulatorEndpoints, allowedEmulatorEndpoints,
allowedGraphEndpoints,
allowedHostedExplorerEndpoints, allowedHostedExplorerEndpoints,
allowedJunoOrigins, allowedJunoOrigins,
allowedMsalRedirectEndpoints, allowedMsalRedirectEndpoints,
defaultAllowedAadEndpoints,
defaultAllowedArmEndpoints, defaultAllowedArmEndpoints,
defaultAllowedBackendEndpoints, defaultAllowedBackendEndpoints,
defaultAllowedCassandraProxyEndpoints, defaultAllowedCassandraProxyEndpoints,
defaultAllowedGraphEndpoints,
defaultAllowedMongoProxyEndpoints, defaultAllowedMongoProxyEndpoints,
validateEndpoint, validateEndpoint,
} from "Utils/EndpointUtils"; } from "Utils/EndpointUtils";
@@ -25,10 +19,13 @@ export enum Platform {
Hosted = "Hosted", Hosted = "Hosted",
Emulator = "Emulator", Emulator = "Emulator",
Fabric = "Fabric", Fabric = "Fabric",
VNextEmulator = "VNextEmulator",
} }
export interface ConfigContext { export interface ConfigContext {
platform: Platform; platform: Platform;
allowedAadEndpoints: ReadonlyArray<string>;
allowedGraphEndpoints: ReadonlyArray<string>;
allowedArmEndpoints: ReadonlyArray<string>; allowedArmEndpoints: ReadonlyArray<string>;
allowedBackendEndpoints: ReadonlyArray<string>; allowedBackendEndpoints: ReadonlyArray<string>;
allowedCassandraProxyEndpoints: ReadonlyArray<string>; allowedCassandraProxyEndpoints: ReadonlyArray<string>;
@@ -37,10 +34,8 @@ export interface ConfigContext {
gitSha?: string; gitSha?: string;
proxyPath?: string; proxyPath?: string;
AAD_ENDPOINT: string; AAD_ENDPOINT: string;
ARM_AUTH_AREA: string;
ARM_ENDPOINT: string; ARM_ENDPOINT: string;
EMULATOR_ENDPOINT?: string; EMULATOR_ENDPOINT?: string;
ARM_API_VERSION: string;
GRAPH_ENDPOINT: string; GRAPH_ENDPOINT: string;
GRAPH_API_VERSION: string; GRAPH_API_VERSION: string;
// This is the endpoint to get offering Ids to be used to fetch prices. Refer to this doc: https://learn.microsoft.com/en-us/rest/api/marketplacecatalog/dataplane/skus/list?view=rest-marketplacecatalog-dataplane-2023-05-01-preview&tabs=HTTP // This is the endpoint to get offering Ids to be used to fetch prices. Refer to this doc: https://learn.microsoft.com/en-us/rest/api/marketplacecatalog/dataplane/skus/list?view=rest-marketplacecatalog-dataplane-2023-05-01-preview&tabs=HTTP
@@ -50,27 +45,24 @@ export interface ConfigContext {
ARCADIA_ENDPOINT: string; ARCADIA_ENDPOINT: string;
ARCADIA_LIVY_ENDPOINT_DNS_ZONE: string; ARCADIA_LIVY_ENDPOINT_DNS_ZONE: string;
PORTAL_BACKEND_ENDPOINT: string; PORTAL_BACKEND_ENDPOINT: string;
NEW_BACKEND_APIS?: BackendApi[];
MONGO_PROXY_ENDPOINT: string; MONGO_PROXY_ENDPOINT: string;
CASSANDRA_PROXY_ENDPOINT: string; CASSANDRA_PROXY_ENDPOINT: string;
NEW_CASSANDRA_APIS?: string[];
PROXY_PATH?: string; PROXY_PATH?: string;
JUNO_ENDPOINT: string; JUNO_ENDPOINT: string;
GITHUB_CLIENT_ID: string; GITHUB_CLIENT_ID: string;
GITHUB_TEST_ENV_CLIENT_ID: string; GITHUB_TEST_ENV_CLIENT_ID: string;
GITHUB_CLIENT_SECRET?: string; // No need to inject secret for prod. Juno already knows it. GITHUB_CLIENT_SECRET?: string; // No need to inject secret for prod. Juno already knows it.
isTerminalEnabled: boolean;
isPhoenixEnabled: boolean; isPhoenixEnabled: boolean;
hostedExplorerURL: string; hostedExplorerURL: string;
armAPIVersion?: string; armAPIVersion?: string;
msalRedirectURI?: string; msalRedirectURI?: string;
globallyEnabledCassandraAPIs?: string[];
globallyEnabledMongoAPIs?: string[];
} }
// Default configuration // Default configuration
let configContext: Readonly<ConfigContext> = { let configContext: Readonly<ConfigContext> = {
platform: Platform.Portal, platform: Platform.Portal,
allowedAadEndpoints: defaultAllowedAadEndpoints,
allowedGraphEndpoints: defaultAllowedGraphEndpoints,
allowedArmEndpoints: defaultAllowedArmEndpoints, allowedArmEndpoints: defaultAllowedArmEndpoints,
allowedBackendEndpoints: defaultAllowedBackendEndpoints, allowedBackendEndpoints: defaultAllowedBackendEndpoints,
allowedCassandraProxyEndpoints: defaultAllowedCassandraProxyEndpoints, allowedCassandraProxyEndpoints: defaultAllowedCassandraProxyEndpoints,
@@ -85,17 +77,12 @@ let configContext: Readonly<ConfigContext> = {
`^https:\\/\\/cosmos-db-dataexplorer-germanycentral\\.azurewebsites\\.de$`, `^https:\\/\\/cosmos-db-dataexplorer-germanycentral\\.azurewebsites\\.de$`,
`^https:\\/\\/.*\\.fabric\\.microsoft\\.com$`, `^https:\\/\\/.*\\.fabric\\.microsoft\\.com$`,
`^https:\\/\\/.*\\.powerbi\\.com$`, `^https:\\/\\/.*\\.powerbi\\.com$`,
`^https:\\/\\/.*\\.analysis-df\\.net$`,
`^https:\\/\\/.*\\.analysis-df\\.windows\\.net$`,
`^https:\\/\\/.*\\.azure-test\\.net$`,
`^https:\\/\\/dataexplorer-preview\\.azurewebsites\\.net$`, `^https:\\/\\/dataexplorer-preview\\.azurewebsites\\.net$`,
], // Webpack injects this at build time ], // Webpack injects this at build time
gitSha: process.env.GIT_SHA, gitSha: process.env.GIT_SHA,
hostedExplorerURL: "https://cosmos.azure.com/", hostedExplorerURL: "https://cosmos.azure.com/",
AAD_ENDPOINT: "https://login.microsoftonline.com/", AAD_ENDPOINT: "https://login.microsoftonline.com/",
ARM_AUTH_AREA: "https://management.azure.com/",
ARM_ENDPOINT: "https://management.azure.com/", ARM_ENDPOINT: "https://management.azure.com/",
ARM_API_VERSION: "2016-06-01",
GRAPH_ENDPOINT: "https://graph.microsoft.com", GRAPH_ENDPOINT: "https://graph.microsoft.com",
GRAPH_API_VERSION: "1.6", GRAPH_API_VERSION: "1.6",
CATALOG_ENDPOINT: "https://catalogapi.azure.com/", CATALOG_ENDPOINT: "https://catalogapi.azure.com/",
@@ -109,11 +96,7 @@ let configContext: Readonly<ConfigContext> = {
PORTAL_BACKEND_ENDPOINT: PortalBackendEndpoints.Prod, PORTAL_BACKEND_ENDPOINT: PortalBackendEndpoints.Prod,
MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Prod, MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Prod,
CASSANDRA_PROXY_ENDPOINT: CassandraProxyEndpoints.Prod, CASSANDRA_PROXY_ENDPOINT: CassandraProxyEndpoints.Prod,
NEW_CASSANDRA_APIS: ["postQuery", "createOrDelete", "getKeys", "getSchema"],
isTerminalEnabled: false,
isPhoenixEnabled: false, isPhoenixEnabled: false,
globallyEnabledCassandraAPIs: [],
globallyEnabledMongoAPIs: [],
}; };
export function resetConfigContext(): void { export function resetConfigContext(): void {
@@ -128,19 +111,21 @@ export function updateConfigContext(newContext: Partial<ConfigContext>): void {
return; return;
} }
if (!validateEndpoint(newContext.ARM_ENDPOINT, configContext.allowedArmEndpoints || defaultAllowedArmEndpoints)) { if (!validateEndpoint(newContext.AAD_ENDPOINT, configContext.allowedAadEndpoints || defaultAllowedAadEndpoints)) {
delete newContext.ARM_ENDPOINT; delete newContext.AAD_ENDPOINT;
} }
if (!validateEndpoint(newContext.AAD_ENDPOINT, allowedAadEndpoints)) { if (!validateEndpoint(newContext.ARM_ENDPOINT, configContext.allowedArmEndpoints || defaultAllowedArmEndpoints)) {
delete newContext.AAD_ENDPOINT; delete newContext.ARM_ENDPOINT;
} }
if (!validateEndpoint(newContext.EMULATOR_ENDPOINT, allowedEmulatorEndpoints)) { if (!validateEndpoint(newContext.EMULATOR_ENDPOINT, allowedEmulatorEndpoints)) {
delete newContext.EMULATOR_ENDPOINT; delete newContext.EMULATOR_ENDPOINT;
} }
if (!validateEndpoint(newContext.GRAPH_ENDPOINT, allowedGraphEndpoints)) { if (
!validateEndpoint(newContext.GRAPH_ENDPOINT, configContext.allowedGraphEndpoints || defaultAllowedGraphEndpoints)
) {
delete newContext.GRAPH_ENDPOINT; delete newContext.GRAPH_ENDPOINT;
} }
@@ -148,6 +133,15 @@ export function updateConfigContext(newContext: Partial<ConfigContext>): void {
delete newContext.ARCADIA_ENDPOINT; delete newContext.ARCADIA_ENDPOINT;
} }
if (
!validateEndpoint(
newContext.PORTAL_BACKEND_ENDPOINT,
configContext.allowedBackendEndpoints || defaultAllowedBackendEndpoints,
)
) {
delete newContext.PORTAL_BACKEND_ENDPOINT;
}
if ( if (
!validateEndpoint( !validateEndpoint(
newContext.MONGO_PROXY_ENDPOINT, newContext.MONGO_PROXY_ENDPOINT,
@@ -232,6 +226,7 @@ export async function initializeConfiguration(): Promise<ConfigContext> {
case Platform.Fabric: case Platform.Fabric:
case Platform.Hosted: case Platform.Hosted:
case Platform.Emulator: case Platform.Emulator:
case Platform.VNextEmulator:
updateConfigContext({ platform }); updateConfigContext({ platform });
} }
} }

View File

@@ -23,6 +23,7 @@ export enum PaneKind {
GlobalSettings, GlobalSettings,
AdHocAccess, AdHocAccess,
SwitchDirectory, SwitchDirectory,
QuickStart,
} }
/** /**

View File

@@ -7,6 +7,7 @@ export interface ArmEntity {
type: string; type: string;
kind: string; kind: string;
tags?: Tags; tags?: Tags;
resourceGroup?: string;
} }
export interface DatabaseAccount extends ArmEntity { export interface DatabaseAccount extends ArmEntity {
@@ -388,7 +389,7 @@ export interface VectorEmbeddingPolicy {
} }
export interface VectorEmbedding { export interface VectorEmbedding {
dataType: "float16" | "float32" | "uint8" | "int8"; dataType: "float32" | "uint8" | "int8";
dimensions: number; dimensions: number;
distanceFunction: "euclidean" | "cosine" | "dotproduct"; distanceFunction: "euclidean" | "cosine" | "dotproduct";
path: string; path: string;

View File

@@ -1,4 +1,5 @@
import { import {
ItemDefinition,
JSONObject, JSONObject,
QueryMetrics, QueryMetrics,
Resource, Resource,
@@ -30,8 +31,11 @@ export interface UploadDetailsRecord {
numFailed: number; numFailed: number;
numThrottled: number; numThrottled: number;
errors: string[]; errors: string[];
resources?: ItemDefinition[];
} }
export type BulkInsertResult = Omit<UploadDetailsRecord, "fileName">;
export interface QueryResultsMetadata { export interface QueryResultsMetadata {
hasMoreResults: boolean; hasMoreResults: boolean;
firstItemIndex: number; firstItemIndex: number;
@@ -46,6 +50,7 @@ export interface QueryResults extends QueryResultsMetadata {
roundTrips?: number; roundTrips?: number;
headers?: any; headers?: any;
queryMetrics?: QueryMetrics; queryMetrics?: QueryMetrics;
ruThresholdExceeded?: boolean;
} }
export interface Button { export interface Button {
@@ -438,6 +443,7 @@ export interface DataExplorerInputsFrame {
[key: string]: string; [key: string]: string;
}; };
feedbackPolicies?: any; feedbackPolicies?: any;
aadToken?: string;
} }
export interface SelfServeFrameInputs { export interface SelfServeFrameInputs {

View File

@@ -1,11 +0,0 @@
<!doctype html>
<html class="no-js" lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="data:," />
</head>
<body>
<div id="heatmap"></div>
</body>
</html>

View File

@@ -1,55 +0,0 @@
@import "../../../less/Common/Constants";
html {
font-family: @DataExplorerFont;
padding: 0px;
margin: 0px;
border: 0px;
overflow: hidden;
position: fixed;
width: 100%;
height: 100%;
}
body {
font-family: @DataExplorerFont;
padding: 0px;
margin: 0px;
border: 0px;
overflow: hidden;
}
#heatmap {
.dark-theme {
color: @BaseLight;
}
.chartTitle {
position: absolute;
top: 5px;
left: 3px;
font-size: 13px;
}
.noDataMessage {
display: flex;
justify-content: center;
align-items: center;
position: absolute;
z-index: 10000;
height: 100%;
width: 100%;
top: 0;
left: 0;
opacity: 0.97;
div {
border-color: rgba(204, 204, 204, 0.8);
box-shadow: 0 4px 6px 0 rgba(0, 0, 0, 0.12);
padding: 15px 10px;
width: calc(55% - 40px);
font-size: 13px;
text-align: center;
border-width: 1px;
border-style: solid;
}
}
}

View File

@@ -1,143 +0,0 @@
import dayjs from "dayjs";
import { handleMessage, Heatmap, isDarkTheme } from "./Heatmap";
import { PortalTheme } from "./HeatmapDatatypes";
describe("The Heatmap Control", () => {
const dataPoints = {
"1": {
"2019-06-19T00:59:10Z": {
"Normalized Throughput": 0.35,
},
"2019-06-19T00:48:10Z": {
"Normalized Throughput": 0.25,
},
},
};
const chartCaptions = {
chartTitle: "chart title",
yAxisTitle: "YAxisTitle",
tooltipText: "Tooltip text",
timeWindow: 123456789,
};
let heatmap: Heatmap;
const theme: PortalTheme = 1;
const divElement = `<div id="${Heatmap.elementId}"></div>`;
describe("drawHeatmap rendering", () => {
beforeEach(() => {
heatmap = new Heatmap(dataPoints, chartCaptions, theme);
document.body.innerHTML = divElement;
});
afterEach(() => {
document.body.innerHTML = ``;
});
it("should call _getChartSettings when drawHeatmap is invoked", () => {
const _getChartSettings = jest.spyOn(heatmap, "_getChartSettings");
heatmap.drawHeatmap();
expect(_getChartSettings).toHaveBeenCalled();
});
it("should call _getLayoutSettings when drawHeatmap is invoked", () => {
const _getLayoutSettings = jest.spyOn(heatmap, "_getLayoutSettings");
heatmap.drawHeatmap();
expect(_getLayoutSettings).toHaveBeenCalled();
});
it("should call _getChartDisplaySettings when drawHeatmap is invoked", () => {
const _getChartDisplaySettings = jest.spyOn(heatmap, "_getChartDisplaySettings");
heatmap.drawHeatmap();
expect(_getChartDisplaySettings).toHaveBeenCalled();
});
it("drawHeatmap should render a Heatmap inside the div element", () => {
heatmap.drawHeatmap();
expect(document.body.innerHTML).not.toEqual(divElement);
});
});
describe("generateMatrixFromMap", () => {
it("should massage input data to match output expected", () => {
expect(heatmap.generateMatrixFromMap(dataPoints).yAxisPoints).toEqual(["1"]);
expect(heatmap.generateMatrixFromMap(dataPoints).dataPoints).toEqual([[0.25, 0.35]]);
expect(heatmap.generateMatrixFromMap(dataPoints).xAxisPoints.length).toEqual(2);
});
it("should output the date format to ISO8601 string format", () => {
expect(heatmap.generateMatrixFromMap(dataPoints).xAxisPoints[0].slice(10, 11)).toEqual("T");
expect(heatmap.generateMatrixFromMap(dataPoints).xAxisPoints[0].slice(-1)).toEqual("Z");
});
it("should convert the time to the user's local time", () => {
if (dayjs().utcOffset()) {
expect(heatmap.generateMatrixFromMap(dataPoints).xAxisPoints).not.toEqual([
"2019-06-19T00:48:10Z",
"2019-06-19T00:59:10Z",
]);
} else {
expect(heatmap.generateMatrixFromMap(dataPoints).xAxisPoints).toEqual([
"2019-06-19T00:48:10Z",
"2019-06-19T00:59:10Z",
]);
}
});
});
describe("isDarkTheme", () => {
it("isDarkTheme should return the correct result", () => {
expect(isDarkTheme(PortalTheme.dark)).toEqual(true);
expect(isDarkTheme(PortalTheme.azure)).not.toEqual(true);
});
});
});
describe("iframe rendering when there is no data", () => {
afterEach(() => {
document.body.innerHTML = ``;
});
it("should show a no data message with a dark theme", () => {
const data = {
data: {
signature: "pcIframe",
data: {
chartData: {},
chartSettings: {},
theme: 4,
},
},
origin: "http://localhost",
};
const divElement = `<div id="${Heatmap.elementId}"></div>`;
document.body.innerHTML = divElement;
handleMessage(data as MessageEvent);
expect(document.body.innerHTML).toContain("dark-theme");
expect(document.body.innerHTML).toContain("noDataMessage");
});
it("should show a no data message with a white theme", () => {
const data = {
data: {
signature: "pcIframe",
data: {
chartData: {},
chartSettings: {},
theme: 2,
},
},
origin: "http://localhost",
};
const divElement = `<div id="${Heatmap.elementId}"></div>`;
document.body.innerHTML = divElement;
handleMessage(data as MessageEvent);
expect(document.body.innerHTML).not.toContain("dark-theme");
expect(document.body.innerHTML).toContain("noDataMessage");
});
});

View File

@@ -1,272 +0,0 @@
import dayjs from "dayjs";
import * as Plotly from "plotly.js-cartesian-dist-min";
import { sendCachedDataMessage, sendReadyMessage } from "../../Common/MessageHandler";
import { StyleConstants } from "../../Common/StyleConstants";
import { MessageTypes } from "../../Contracts/ExplorerContracts";
import { isInvalidParentFrameOrigin } from "../../Utils/MessageValidation";
import "./Heatmap.less";
import {
ChartSettings,
DataPayload,
DisplaySettings,
FontSettings,
HeatmapCaptions,
HeatmapData,
LayoutSettings,
PartitionTimeStampToData,
PortalTheme,
} from "./HeatmapDatatypes";
export class Heatmap {
public static readonly elementId: string = "heatmap";
private _chartData: HeatmapData;
private _heatmapCaptions: HeatmapCaptions;
private _theme: PortalTheme;
private _defaultFontColor: string;
constructor(data: DataPayload, heatmapCaptions: HeatmapCaptions, theme: PortalTheme) {
this._theme = theme;
this._defaultFontColor = StyleConstants.BaseDark;
this._setThemeColorForChart();
this._chartData = this.generateMatrixFromMap(data);
this._heatmapCaptions = heatmapCaptions;
}
private _setThemeColorForChart() {
if (isDarkTheme(this._theme)) {
this._defaultFontColor = StyleConstants.BaseLight;
}
}
private _getFontStyles(size: number = StyleConstants.MediumFontSize, color = "#838383"): FontSettings {
return {
family: StyleConstants.DataExplorerFont,
size,
color,
};
}
public generateMatrixFromMap(data: DataPayload): HeatmapData {
// all keys in data payload, sorted...
const rows: string[] = Object.keys(data).sort((a: string, b: string) => {
if (parseInt(a) < parseInt(b)) {
return -1;
} else {
if (parseInt(a) > parseInt(b)) {
return 1;
} else {
return 0;
}
}
});
const output: HeatmapData = {
yAxisPoints: [],
dataPoints: [],
xAxisPoints: Object.keys(data[rows[0]]).sort((a: string, b: string) => {
if (a < b) {
return -1;
} else {
if (a > b) {
return 1;
} else {
return 0;
}
}
}),
};
// go thru all rows and create 2d matrix for heatmap...
for (let i = 0; i < rows.length; i++) {
output.yAxisPoints.push(rows[i]);
const dataPoints: number[] = [];
for (let a = 0; a < output.xAxisPoints.length; a++) {
const row: PartitionTimeStampToData = data[rows[i]];
dataPoints.push(row[output.xAxisPoints[a]]["Normalized Throughput"]);
}
output.dataPoints.push(dataPoints);
}
for (let a = 0; a < output.xAxisPoints.length; a++) {
const dateTime = output.xAxisPoints[a];
// convert to local users timezone...
const day = dayjs(new Date(dateTime)).format("YYYY-MM-DD");
const hour = dayjs(new Date(dateTime)).format("HH:mm:ss");
// coerce to ISOString format since that is what plotly wants...
output.xAxisPoints[a] = `${day}T${hour}Z`;
}
return output;
}
// public for testing purposes
public _getChartSettings(): ChartSettings[] {
return [
{
z: this._chartData.dataPoints,
type: "heatmap",
zmin: 0,
zmid: 50,
zmax: 100,
colorscale: [
[0.0, "#1FD338"],
[0.1, "#1CAD2F"],
[0.2, "#50A527"],
[0.3, "#719F21"],
[0.4, "#95991B"],
[0.5, "#CE8F11"],
[0.6, "#E27F0F"],
[0.7, "#E46612"],
[0.8, "#E64914"],
[0.9, "#B80016"],
[1.0, "#B80016"],
],
name: "",
hovertemplate: this._heatmapCaptions.tooltipText,
colorbar: {
thickness: 15,
outlinewidth: 0,
tickcolor: StyleConstants.BaseDark,
tickfont: this._getFontStyles(10, this._defaultFontColor),
},
y: this._chartData.yAxisPoints,
x: this._chartData.xAxisPoints,
},
];
}
// public for testing purposes
public _getLayoutSettings(): LayoutSettings {
return {
margin: {
l: 40,
r: 10,
b: 35,
t: 30,
pad: 0,
},
paper_bgcolor: "transparent",
plot_bgcolor: "transparent",
width: 462,
height: 240,
yaxis: {
title: this._heatmapCaptions.yAxisTitle,
titlefont: this._getFontStyles(11),
autorange: true,
showgrid: false,
zeroline: false,
showline: false,
autotick: true,
fixedrange: true,
ticks: "",
showticklabels: false,
},
xaxis: {
fixedrange: true,
title: "*White area in heatmap indicates there is no available data",
titlefont: this._getFontStyles(11),
autorange: true,
showgrid: false,
zeroline: false,
showline: false,
autotick: true,
tickformat: this._heatmapCaptions.timeWindow > 7 ? "%I:%M %p" : "%b %e",
showticklabels: true,
tickfont: this._getFontStyles(10),
},
title: {
text: this._heatmapCaptions.chartTitle,
x: 0.01,
font: this._getFontStyles(13, this._defaultFontColor),
},
};
}
// public for testing purposes
public _getChartDisplaySettings(): DisplaySettings {
return {
/* heatmap can be fully responsive however the min-height needed in that case is greater than the iframe portal height, hence explicit width + height have been set in _getLayoutSettings
responsive: true,*/
displayModeBar: false,
};
}
public drawHeatmap(): void {
// todo - create random elementId generator so multiple heatmaps can be created - ticket # 431469
Plotly.plot(
Heatmap.elementId,
this._getChartSettings(),
this._getLayoutSettings(),
this._getChartDisplaySettings(),
);
const plotDiv: any = document.getElementById(Heatmap.elementId);
plotDiv.on("plotly_click", (data: any) => {
let timeSelected: string = data.points[0].x;
timeSelected = timeSelected.replace(" ", "T");
timeSelected = `${timeSelected}Z`;
let xAxisIndex = 0;
for (let i = 0; i < this._chartData.xAxisPoints.length; i++) {
if (this._chartData.xAxisPoints[i] === timeSelected) {
xAxisIndex = i;
break;
}
}
const output = [];
for (let i = 0; i < this._chartData.dataPoints.length; i++) {
output.push(this._chartData.dataPoints[i][xAxisIndex]);
}
sendCachedDataMessage(MessageTypes.LogInfo, output);
});
}
}
export function isDarkTheme(theme: PortalTheme) {
return theme === PortalTheme.dark;
}
export function handleMessage(event: MessageEvent) {
if (isInvalidParentFrameOrigin(event)) {
return;
}
if (typeof event.data !== "object" || event.data["signature"] !== "pcIframe") {
return;
}
if (
typeof event.data.data !== "object" ||
!("chartData" in event.data.data) ||
!("chartSettings" in event.data.data)
) {
return;
}
Plotly.purge(Heatmap.elementId);
document.getElementById(Heatmap.elementId)!.innerHTML = "";
const data = event.data.data;
const chartData: DataPayload = data.chartData;
const chartSettings: HeatmapCaptions = data.chartSettings;
const chartTheme: PortalTheme = data.theme;
if (Object.keys(chartData).length) {
new Heatmap(chartData, chartSettings, chartTheme).drawHeatmap();
} else {
const chartTitleElement = document.createElement("div");
chartTitleElement.innerHTML = data.chartSettings.chartTitle;
chartTitleElement.classList.add("chartTitle");
const noDataMessageElement = document.createElement("div");
noDataMessageElement.classList.add("noDataMessage");
const noDataMessageContent = document.createElement("div");
noDataMessageContent.innerHTML = data.errorMessage;
noDataMessageElement.appendChild(noDataMessageContent);
if (isDarkTheme(chartTheme)) {
chartTitleElement.classList.add("dark-theme");
noDataMessageElement.classList.add("dark-theme");
noDataMessageContent.classList.add("dark-theme");
}
document.getElementById(Heatmap.elementId)!.appendChild(chartTitleElement);
document.getElementById(Heatmap.elementId)!.appendChild(noDataMessageElement);
}
}
window.addEventListener("message", handleMessage, false);
sendReadyMessage();

View File

@@ -1,106 +0,0 @@
type dataPoint = string | number;
export interface DataPayload {
[id: string]: PartitionTimeStampToData;
}
export enum PortalTheme {
blue = 1,
azure,
light,
dark,
}
export interface HeatmapData {
yAxisPoints: string[];
xAxisPoints: string[];
dataPoints: dataPoint[][];
}
export interface HeatmapCaptions {
chartTitle: string;
yAxisTitle: string;
tooltipText: string;
timeWindow: number;
}
export interface FontSettings {
family: string;
size: number;
color: string;
}
export interface LayoutSettings {
paper_bgcolor?: string;
plot_bgcolor?: string;
margin?: {
l: number;
r: number;
b: number;
t: number;
pad: number;
};
width?: number;
height?: number;
yaxis?: {
fixedrange: boolean;
title: HeatmapCaptions["yAxisTitle"];
titlefont: FontSettings;
autorange: boolean;
showgrid: boolean;
zeroline: boolean;
showline: boolean;
autotick: boolean;
ticks: "";
showticklabels: boolean;
};
xaxis?: {
fixedrange: boolean;
title: string;
titlefont: FontSettings;
autorange: boolean;
showgrid: boolean;
zeroline: boolean;
showline: boolean;
autotick: boolean;
showticklabels: boolean;
tickformat: string;
tickfont: FontSettings;
};
title?: {
text: HeatmapCaptions["chartTitle"];
x: number;
font?: FontSettings;
};
font?: FontSettings;
}
export interface ChartSettings {
z: HeatmapData["dataPoints"];
type: "heatmap";
zmin: number;
zmid: number;
zmax: number;
colorscale: [number, string][];
name: string;
hovertemplate: HeatmapCaptions["tooltipText"];
colorbar: {
thickness: number;
outlinewidth: number;
tickcolor: string;
tickfont: FontSettings;
};
y: HeatmapData["yAxisPoints"];
x: HeatmapData["xAxisPoints"];
}
export interface DisplaySettings {
displayModeBar: boolean;
responsive?: boolean;
}
export interface PartitionTimeStampToData {
[timeSeriesDates: string]: {
[NormalizedThroughput: string]: number;
};
}

View File

@@ -13,8 +13,9 @@ import {
} from "Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/ThroughputBucketsComponent"; } from "Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/ThroughputBucketsComponent";
import { useDatabases } from "Explorer/useDatabases"; import { useDatabases } from "Explorer/useDatabases";
import { isFabricNative } from "Platform/Fabric/FabricUtil"; import { isFabricNative } from "Platform/Fabric/FabricUtil";
import { isFullTextSearchEnabled, isVectorSearchEnabled } from "Utils/CapabilityUtils"; import { isVectorSearchEnabled } from "Utils/CapabilityUtils";
import { isRunningOnPublicCloud } from "Utils/CloudUtils"; import { isRunningOnPublicCloud } from "Utils/CloudUtils";
import { isFeatureSupported, PlatformFeature } from "Utils/PlatformFeatureUtils";
import * as React from "react"; import * as React from "react";
import DiscardIcon from "../../../../images/discard.svg"; import DiscardIcon from "../../../../images/discard.svg";
import SaveIcon from "../../../../images/save-cosmos.svg"; import SaveIcon from "../../../../images/save-cosmos.svg";
@@ -60,15 +61,15 @@ import {
AddMongoIndexProps, AddMongoIndexProps,
ChangeFeedPolicyState, ChangeFeedPolicyState,
GeospatialConfigType, GeospatialConfigType,
MongoIndexTypes,
SettingsV2TabTypes,
TtlType,
getMongoNotification, getMongoNotification,
getTabTitle, getTabTitle,
hasDatabaseSharedThroughput, hasDatabaseSharedThroughput,
isDirty, isDirty,
MongoIndexTypes,
parseConflictResolutionMode, parseConflictResolutionMode,
parseConflictResolutionProcedure, parseConflictResolutionProcedure,
SettingsV2TabTypes,
TtlType,
} from "./SettingsUtils"; } from "./SettingsUtils";
interface SettingsV2TabInfo { interface SettingsV2TabInfo {
@@ -188,7 +189,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
this.isGlobalSecondaryIndex = this.isGlobalSecondaryIndex =
!!this.collection?.materializedViewDefinition() || !!this.collection?.materializedViews(); !!this.collection?.materializedViewDefinition() || !!this.collection?.materializedViews();
this.isVectorSearchEnabled = isVectorSearchEnabled() && !hasDatabaseSharedThroughput(this.collection); this.isVectorSearchEnabled = isVectorSearchEnabled() && !hasDatabaseSharedThroughput(this.collection);
this.isFullTextSearchEnabled = isFullTextSearchEnabled() && !hasDatabaseSharedThroughput(this.collection); this.isFullTextSearchEnabled = userContext.apiType === "SQL";
this.changeFeedPolicyVisible = userContext.features.enableChangeFeedPolicy; this.changeFeedPolicyVisible = userContext.features.enableChangeFeedPolicy;
this.throughputBucketsEnabled = userContext.throughputBucketsEnabled; this.throughputBucketsEnabled = userContext.throughputBucketsEnabled;
@@ -276,14 +277,14 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
this.saveSettingsButton = { this.saveSettingsButton = {
isEnabled: this.isSaveSettingsButtonEnabled, isEnabled: this.isSaveSettingsButtonEnabled,
isVisible: () => { isVisible: () => {
return true; return isFeatureSupported(PlatformFeature.UpdateCollection);
}, },
}; };
this.discardSettingsChangesButton = { this.discardSettingsChangesButton = {
isEnabled: this.isDiscardSettingsButtonEnabled, isEnabled: this.isDiscardSettingsButtonEnabled,
isVisible: () => { isVisible: () => {
return true; return isFeatureSupported(PlatformFeature.UpdateCollection);
}, },
}; };
@@ -1091,6 +1092,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
currentOffer: this.collection.offer(), currentOffer: this.collection.offer(),
autopilotThroughput: this.state.isAutoPilotSelected ? this.state.autoPilotThroughput : undefined, autopilotThroughput: this.state.isAutoPilotSelected ? this.state.autoPilotThroughput : undefined,
manualThroughput: this.state.isAutoPilotSelected ? undefined : this.state.throughput, manualThroughput: this.state.isAutoPilotSelected ? undefined : this.state.throughput,
throughputBuckets: this.throughputBucketsEnabled ? this.state.throughputBuckets : undefined,
}; };
if (this.hasProvisioningTypeChanged()) { if (this.hasProvisioningTypeChanged()) {
if (this.state.isAutoPilotSelected) { if (this.state.isAutoPilotSelected) {

View File

@@ -559,16 +559,69 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
private getThroughputTextField = (): JSX.Element => ( private getThroughputTextField = (): JSX.Element => (
<> <>
{this.props.isAutoPilotSelected ? ( {this.props.isAutoPilotSelected ? (
<Stack horizontal verticalAlign="end" tokens={{ childrenGap: 8 }}>
{/* Column 1: Minimum RU/s */}
<Stack tokens={{ childrenGap: 4 }}>
<Stack horizontal verticalAlign="center" tokens={{ childrenGap: 4 }}>
<Text variant="small" style={{ lineHeight: "20px", fontWeight: 600 }}>
Minimum RU/s
</Text>
<FontIcon iconName="Info" style={{ fontSize: 12, color: "#666" }} />
</Stack>
<Text
style={{
fontFamily: "Segoe UI",
width: 70,
height: 28,
border: "none",
fontSize: 14,
backgroundColor: "transparent",
fontWeight: 400,
display: "flex",
alignItems: "center",
justifyContent: "center",
boxSizing: "border-box",
}}
>
{AutoPilotUtils.getMinRUsBasedOnUserInput(this.props.maxAutoPilotThroughput)}
</Text>
</Stack>
{/* Column 2: "x 10 =" Text */}
<Text
style={{
fontFamily: "Segoe UI",
fontSize: 12,
fontWeight: 400,
paddingBottom: 6,
}}
>
x 10 =
</Text>
{/* Column 3: Maximum RU/s */}
<Stack tokens={{ childrenGap: 4 }}>
<Stack horizontal verticalAlign="center" tokens={{ childrenGap: 4 }}>
<Text variant="small" style={{ lineHeight: "20px", fontWeight: 600 }}>
Maximum RU/s
</Text>
<FontIcon iconName="Info" style={{ fontSize: 12, color: "#666" }} />
</Stack>
<TextField <TextField
label="Maximum RU/s required by this resource"
required required
type="number" type="number"
id="autopilotInput" id="autopilotInput"
key="auto pilot throughput input" key="auto pilot throughput input"
styles={getTextFieldStyles(this.props.maxAutoPilotThroughput, this.props.maxAutoPilotThroughputBaseline)} styles={{
...getTextFieldStyles(this.props.maxAutoPilotThroughput, this.props.maxAutoPilotThroughputBaseline),
fieldGroup: { width: 100, height: 28 },
field: { fontSize: 14, fontWeight: 400 },
}}
disabled={this.overrideWithProvisionedThroughputSettings()} disabled={this.overrideWithProvisionedThroughputSettings()}
step={AutoPilotUtils.autoPilotIncrementStep} step={AutoPilotUtils.autoPilotIncrementStep}
value={this.overrideWithProvisionedThroughputSettings() ? "" : this.props.maxAutoPilotThroughput?.toString()} value={
this.overrideWithProvisionedThroughputSettings() ? "" : this.props.maxAutoPilotThroughput?.toString()
}
onChange={this.onAutoPilotThroughputChange} onChange={this.onAutoPilotThroughputChange}
min={autoPilotThroughput1K} min={autoPilotThroughput1K}
onGetErrorMessage={(value: string) => { onGetErrorMessage={(value: string) => {
@@ -579,6 +632,8 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
}} }}
validateOnLoad={false} validateOnLoad={false}
/> />
</Stack>
</Stack>
) : ( ) : (
<TextField <TextField
required required

View File

@@ -157,11 +157,125 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
} }
} }
> >
<Stack
horizontal={true}
tokens={
{
"childrenGap": 8,
}
}
verticalAlign="end"
>
<Stack
tokens={
{
"childrenGap": 4,
}
}
>
<Stack
horizontal={true}
tokens={
{
"childrenGap": 4,
}
}
verticalAlign="center"
>
<Text
style={
{
"fontWeight": 600,
"lineHeight": "20px",
}
}
variant="small"
>
Minimum RU/s
</Text>
<FontIcon
iconName="Info"
style={
{
"color": "#666",
"fontSize": 12,
}
}
/>
</Stack>
<Text
style={
{
"alignItems": "center",
"backgroundColor": "transparent",
"border": "none",
"boxSizing": "border-box",
"display": "flex",
"fontFamily": "Segoe UI",
"fontSize": 14,
"fontWeight": 400,
"height": 28,
"justifyContent": "center",
"width": 70,
}
}
>
400
</Text>
</Stack>
<Text
style={
{
"fontFamily": "Segoe UI",
"fontSize": 12,
"fontWeight": 400,
"paddingBottom": 6,
}
}
>
x 10 =
</Text>
<Stack
tokens={
{
"childrenGap": 4,
}
}
>
<Stack
horizontal={true}
tokens={
{
"childrenGap": 4,
}
}
verticalAlign="center"
>
<Text
style={
{
"fontWeight": 600,
"lineHeight": "20px",
}
}
variant="small"
>
Maximum RU/s
</Text>
<FontIcon
iconName="Info"
style={
{
"color": "#666",
"fontSize": 12,
}
}
/>
</Stack>
<StyledTextFieldBase <StyledTextFieldBase
disabled={true} disabled={true}
id="autopilotInput" id="autopilotInput"
key="auto pilot throughput input" key="auto pilot throughput input"
label="Maximum RU/s required by this resource"
min={1000} min={1000}
onChange={[Function]} onChange={[Function]}
onGetErrorMessage={[Function]} onGetErrorMessage={[Function]}
@@ -169,16 +283,13 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
step={1000} step={1000}
styles={ styles={
{ {
"field": {
"fontSize": 14,
"fontWeight": 400,
},
"fieldGroup": { "fieldGroup": {
"borderColor": "", "height": 28,
"height": 25, "width": 100,
"selectors": {
":disabled": {
"backgroundColor": undefined,
"borderColor": undefined,
},
},
"width": 300,
}, },
} }
} }
@@ -186,6 +297,8 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
validateOnLoad={false} validateOnLoad={false}
value="" value=""
/> />
</Stack>
</Stack>
<Stack> <Stack>
<Stack> <Stack>
<Stack <Stack

View File

@@ -198,6 +198,32 @@ exports[`SettingsComponent renders 1`] = `
timeToLiveSecondsBaseline={5} timeToLiveSecondsBaseline={5}
/> />
</PivotItem> </PivotItem>
<PivotItem
headerText="Container Policies"
itemKey="ContainerVectorPolicyTab"
key="ContainerVectorPolicyTab"
style={
{
"marginTop": 20,
}
}
>
<ContainerPolicyComponent
fullTextPolicy={{}}
fullTextPolicyBaseline={{}}
isFullTextSearchEnabled={true}
isGlobalSecondaryIndex={true}
isVectorSearchEnabled={false}
onFullTextPolicyChange={[Function]}
onFullTextPolicyDirtyChange={[Function]}
onVectorEmbeddingPolicyChange={[Function]}
onVectorEmbeddingPolicyDirtyChange={[Function]}
resetShouldDiscardContainerPolicyChange={[Function]}
shouldDiscardContainerPolicies={false}
vectorEmbeddingPolicy={{}}
vectorEmbeddingPolicyBaseline={{}}
/>
</PivotItem>
<PivotItem <PivotItem
headerText="Indexing Policy" headerText="Indexing Policy"
itemKey="IndexingPolicyTab" itemKey="IndexingPolicyTab"

View File

@@ -1,4 +1,4 @@
import { Text } from "@fluentui/react"; import { Stack, Text } from "@fluentui/react";
import React, { FunctionComponent } from "react"; import React, { FunctionComponent } from "react";
import { InfoTooltip } from "../../../../Common/Tooltip/InfoTooltip"; import { InfoTooltip } from "../../../../Common/Tooltip/InfoTooltip";
import * as SharedConstants from "../../../../Shared/Constants"; import * as SharedConstants from "../../../../Shared/Constants";
@@ -54,6 +54,7 @@ export const CostEstimateText: FunctionComponent<CostEstimateTextProps> = ({
if (isAutoscale) { if (isAutoscale) {
return ( return (
<Stack style={{ marginBottom: 6 }}>
<Text variant="small"> <Text variant="small">
{estimatedMonthlyCost} ({currency}){iconWithEstimatedCostDisclaimer}:{" "} {estimatedMonthlyCost} ({currency}){iconWithEstimatedCostDisclaimer}:{" "}
<b> <b>
@@ -63,10 +64,12 @@ export const CostEstimateText: FunctionComponent<CostEstimateTextProps> = ({
({numberOfRegions + (numberOfRegions === 1 ? " region" : " regions")}, {requestUnits / 10} - {requestUnits}{" "} ({numberOfRegions + (numberOfRegions === 1 ? " region" : " regions")}, {requestUnits / 10} - {requestUnits}{" "}
RU/s, {currencySign + pricePerRu}/RU) RU/s, {currencySign + pricePerRu}/RU)
</Text> </Text>
</Stack>
); );
} }
return ( return (
<Stack style={{ marginBottom: 8 }}>
<Text variant="small"> <Text variant="small">
Estimated cost ({currency}){iconWithEstimatedCostDisclaimer}:{" "} Estimated cost ({currency}){iconWithEstimatedCostDisclaimer}:{" "}
<b> <b>
@@ -77,5 +80,6 @@ export const CostEstimateText: FunctionComponent<CostEstimateTextProps> = ({
({numberOfRegions + (numberOfRegions === 1 ? " region" : " regions")}, {requestUnits}RU/s,{" "} ({numberOfRegions + (numberOfRegions === 1 ? " region" : " regions")}, {requestUnits}RU/s,{" "}
{currencySign + pricePerRu}/RU) {currencySign + pricePerRu}/RU)
</Text> </Text>
</Stack>
); );
}; };

View File

@@ -1,15 +1,16 @@
import { Checkbox, DirectionalHint, Link, Stack, Text, TextField, TooltipHost } from "@fluentui/react"; import { Checkbox, DirectionalHint, Link, Separator, Stack, Text, TextField, TooltipHost } from "@fluentui/react";
import { getWorkloadType } from "Common/DatabaseAccountUtility"; import { getWorkloadType } from "Common/DatabaseAccountUtility";
import { CostEstimateText } from "Explorer/Controls/ThroughputInput/CostEstimateText/CostEstimateText";
import { useDatabases } from "Explorer/useDatabases"; import { useDatabases } from "Explorer/useDatabases";
import React, { FunctionComponent, useEffect, useState } from "react"; import React, { FunctionComponent, useEffect, useState } from "react";
import * as Constants from "../../../Common/Constants"; import * as Constants from "../../../Common/Constants";
import { InfoTooltip } from "../../../Common/Tooltip/InfoTooltip"; import { InfoTooltip } from "../../../Common/Tooltip/InfoTooltip";
import { isFabricNative } from "../../../Platform/Fabric/FabricUtil";
import * as SharedConstants from "../../../Shared/Constants"; import * as SharedConstants from "../../../Shared/Constants";
import { userContext } from "../../../UserContext"; import { userContext } from "../../../UserContext";
import { getCollectionName } from "../../../Utils/APITypeUtils"; import { getCollectionName } from "../../../Utils/APITypeUtils";
import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils"; import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
import * as PricingUtils from "../../../Utils/PricingUtils"; import * as PricingUtils from "../../../Utils/PricingUtils";
import { CostEstimateText } from "./CostEstimateText/CostEstimateText";
import "./ThroughputInput.less"; import "./ThroughputInput.less";
export interface ThroughputInputProps { export interface ThroughputInputProps {
@@ -40,7 +41,9 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
let defaultThroughput: number; let defaultThroughput: number;
const workloadType: Constants.WorkloadType = getWorkloadType(); const workloadType: Constants.WorkloadType = getWorkloadType();
if ( if (isFabricNative()) {
defaultThroughput = AutoPilotUtils.autoPilotThroughput5K;
} else if (
isFreeTier || isFreeTier ||
isQuickstart || isQuickstart ||
[Constants.WorkloadType.Learning, Constants.WorkloadType.DevelopmentTesting].includes(workloadType) [Constants.WorkloadType.Learning, Constants.WorkloadType.DevelopmentTesting].includes(workloadType)
@@ -230,36 +233,65 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
</div> </div>
</Stack> </Stack>
)} )}
{isAutoscaleSelected && ( {isAutoscaleSelected && (
<Stack className="throughputInputSpacing"> <Stack className="throughputInputSpacing">
<Text variant="small" aria-label="capacity calculator of azure cosmos db"> <Text style={{ marginTop: -2, fontSize: 12 }}>
Estimate your required RU/s with{" "} Your container throughput will automatically scale up to the maximum value you select, from a minimum of 10%
<Link of that value.
className="underlinedLink outlineNone" </Text>
target="_blank" <Stack horizontal verticalAlign="end" tokens={{ childrenGap: 8 }}>
href="https://cosmos.azure.com/capacitycalculator/" <Stack tokens={{ childrenGap: 4 }}>
aria-label="capacity calculator of azure cosmos db" <Stack horizontal verticalAlign="center" tokens={{ childrenGap: 4 }}>
<Text variant="small" style={{ lineHeight: "20px", fontWeight: 600 }}>
Minimum RU/s
</Text>
<InfoTooltip>The minimum RU/s your container will scale to</InfoTooltip>
</Stack>
<Text
style={{
fontFamily: "Segoe UI",
width: 70,
height: 27,
border: "none",
fontSize: 14,
backgroundColor: "transparent",
fontWeight: 400,
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
> >
capacity calculator {Math.round(throughput / 10).toString()}
</Link> </Text>
. </Stack>
<Text
style={{
fontFamily: "Segoe UI",
fontSize: 12,
fontWeight: 400,
paddingBottom: 6,
}}
>
x 10 =
</Text> </Text>
<Stack horizontal> <Stack tokens={{ childrenGap: 4 }}>
<Text variant="small" style={{ lineHeight: "20px", fontWeight: 600 }} aria-label="maxRUDescription"> <Stack horizontal verticalAlign="center" tokens={{ childrenGap: 4 }}>
{isDatabase ? "Database" : getCollectionName()} Max RU/s <Text variant="small" style={{ lineHeight: "20px", fontWeight: 600 }}>
Maximum RU/s
</Text> </Text>
<InfoTooltip>{getAutoScaleTooltip()}</InfoTooltip> <InfoTooltip>{getAutoScaleTooltip()}</InfoTooltip>
</Stack> </Stack>
<TextField <TextField
id="autoscaleRUValueField" id="autoscaleRUValueField"
type="number" type="number"
styles={{ styles={{
fieldGroup: { width: 300, height: 27 }, fieldGroup: { width: 100, height: 27, flexShrink: 0 },
field: { fontSize: 12 }, field: { fontSize: 14, fontWeight: 400 },
}} }}
onChange={(event, newInput?: string) => onThroughputValueChange(newInput)} onChange={(_event, newInput?: string) => onThroughputValueChange(newInput)}
step={AutoPilotUtils.autoPilotIncrementStep} step={AutoPilotUtils.autoPilotIncrementStep}
min={AutoPilotUtils.autoPilotThroughput1K} min={AutoPilotUtils.autoPilotThroughput1K}
max={isSharded ? Number.MAX_SAFE_INTEGER.toString() : "10000"} max={isSharded ? Number.MAX_SAFE_INTEGER.toString() : "10000"}
@@ -268,16 +300,26 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
required={true} required={true}
errorMessage={throughputError} errorMessage={throughputError}
/> />
</Stack>
</Stack>
<Text variant="small"> <CostEstimateText requestUnits={throughput} isAutoscale={isAutoscaleSelected} />
Your {isDatabase ? "database" : getCollectionName().toLocaleLowerCase()} throughput will automatically scale <Stack className="throughputInputSpacing">
from{" "} <Text variant="small" aria-label="ruDescription">
<b> Estimate your required RU/s with&nbsp;
{AutoPilotUtils.getMinRUsBasedOnUserInput(throughput)} RU/s (10% of max RU/s) - {throughput} RU/s <Link
</b>{" "} className="underlinedLink"
based on usage. target="_blank"
href="https://cosmos.azure.com/capacitycalculator/"
aria-label="Capacity calculator"
>
capacity calculator
</Link>
.
</Text> </Text>
</Stack> </Stack>
<Separator className="panelSeparator" style={{ paddingTop: -8, paddingBottom: -8 }} />
</Stack>
)} )}
{!isAutoscaleSelected && ( {!isAutoscaleSelected && (
@@ -300,7 +342,6 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
</Text> </Text>
<InfoTooltip>{getAutoScaleTooltip()}</InfoTooltip> <InfoTooltip>{getAutoScaleTooltip()}</InfoTooltip>
</Stack> </Stack>
<TooltipHost <TooltipHost
directionalHint={DirectionalHint.topLeftEdge} directionalHint={DirectionalHint.topLeftEdge}
content={ content={
@@ -325,11 +366,10 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
errorMessage={throughputError} errorMessage={throughputError}
/> />
</TooltipHost> </TooltipHost>
<CostEstimateText requestUnits={throughput} isAutoscale={isAutoscaleSelected} />
</Stack> </Stack>
)} )}
<CostEstimateText requestUnits={throughput} isAutoscale={isAutoscaleSelected} />
{throughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K && ( {throughput > SharedConstants.CollectionCreation.DefaultCollectionRUs100K && (
<Stack horizontal verticalAlign="start"> <Stack horizontal verticalAlign="start">
<Checkbox <Checkbox

View File

@@ -1,3 +1,4 @@
import { useDataplaneRbacAuthorization } from "Utils/AuthorizationUtils";
import { createCollection } from "../../Common/dataAccess/createCollection"; import { createCollection } from "../../Common/dataAccess/createCollection";
import { createDocument } from "../../Common/dataAccess/createDocument"; import { createDocument } from "../../Common/dataAccess/createDocument";
import { createDocument as createMongoDocument } from "../../Common/MongoProxyClient"; import { createDocument as createMongoDocument } from "../../Common/MongoProxyClient";
@@ -90,12 +91,13 @@ export class ContainerSampleGenerator {
} }
const { databaseAccount: account } = userContext; const { databaseAccount: account } = userContext;
const databaseId = collection.databaseId; const databaseId = collection.databaseId;
const gremlinClient = new GremlinClient(); const gremlinClient = new GremlinClient();
gremlinClient.initialize({ gremlinClient.initialize({
endpoint: `wss://${GraphTab.getGremlinEndpoint(account)}`, endpoint: `wss://${GraphTab.getGremlinEndpoint(account)}`,
databaseId: databaseId, databaseId: databaseId,
collectionId: collection.id(), collectionId: collection.id(),
masterKey: userContext.masterKey || "", password: useDataplaneRbacAuthorization(userContext) ? userContext.aadToken : userContext.masterKey || "",
maxResultSize: 100, maxResultSize: 100,
}); });

View File

@@ -3,16 +3,22 @@ import { Link } from "@fluentui/react/lib/Link";
import { isPublicInternetAccessAllowed } from "Common/DatabaseAccountUtility"; import { isPublicInternetAccessAllowed } from "Common/DatabaseAccountUtility";
import { Environment, getEnvironment } from "Common/EnvironmentUtility"; import { Environment, getEnvironment } from "Common/EnvironmentUtility";
import { sendMessage } from "Common/MessageHandler"; import { sendMessage } from "Common/MessageHandler";
import { Platform, configContext } from "ConfigContext"; import { configContext, Platform } from "ConfigContext";
import { MessageTypes } from "Contracts/ExplorerContracts"; import { MessageTypes } from "Contracts/ExplorerContracts";
import { useDataPlaneRbac } from "Explorer/Panes/SettingsPane/SettingsPane"; import { useDataPlaneRbac } from "Explorer/Panes/SettingsPane/SettingsPane";
import { getCopilotEnabled, isCopilotFeatureRegistered } from "Explorer/QueryCopilot/Shared/QueryCopilotClient"; import { getCopilotEnabled, isCopilotFeatureRegistered } from "Explorer/QueryCopilot/Shared/QueryCopilotClient";
import { IGalleryItem } from "Juno/JunoClient"; import { IGalleryItem } from "Juno/JunoClient";
import { isFabricMirrored, isFabricMirroredKey, scheduleRefreshFabricToken } from "Platform/Fabric/FabricUtil"; import {
isFabricMirrored,
isFabricMirroredKey,
isFabricNative,
scheduleRefreshFabricToken,
} from "Platform/Fabric/FabricUtil";
import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility"; import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
import { acquireMsalTokenForAccount } from "Utils/AuthorizationUtils"; import { acquireMsalTokenForAccount } from "Utils/AuthorizationUtils";
import { allowedNotebookServerUrls, validateEndpoint } from "Utils/EndpointUtils"; import { allowedNotebookServerUrls, validateEndpoint } from "Utils/EndpointUtils";
import { featureRegistered } from "Utils/FeatureRegistrationUtils"; import { featureRegistered } from "Utils/FeatureRegistrationUtils";
import { isFeatureSupported, PlatformFeature } from "Utils/PlatformFeatureUtils";
import { update } from "Utils/arm/generatedClients/cosmos/databaseAccounts"; import { update } from "Utils/arm/generatedClients/cosmos/databaseAccounts";
import { useQueryCopilot } from "hooks/useQueryCopilot"; import { useQueryCopilot } from "hooks/useQueryCopilot";
import * as ko from "knockout"; import * as ko from "knockout";
@@ -31,6 +37,7 @@ import { readDatabases } from "../Common/dataAccess/readDatabases";
import * as DataModels from "../Contracts/DataModels"; import * as DataModels from "../Contracts/DataModels";
import { ContainerConnectionInfo, IPhoenixServiceInfo, IProvisionData, IResponse } from "../Contracts/DataModels"; import { ContainerConnectionInfo, IPhoenixServiceInfo, IProvisionData, IResponse } from "../Contracts/DataModels";
import * as ViewModels from "../Contracts/ViewModels"; import * as ViewModels from "../Contracts/ViewModels";
import { UploadDetailsRecord } from "../Contracts/ViewModels";
import { GitHubOAuthService } from "../GitHub/GitHubOAuthService"; import { GitHubOAuthService } from "../GitHub/GitHubOAuthService";
import { PhoenixClient } from "../Phoenix/PhoenixClient"; import { PhoenixClient } from "../Phoenix/PhoenixClient";
import * as ExplorerSettings from "../Shared/ExplorerSettings"; import * as ExplorerSettings from "../Shared/ExplorerSettings";
@@ -283,14 +290,40 @@ export default class Explorer {
} }
} }
public openInVsCode(): void { /**
* Generates a VS Code DocumentDB connection URL using the current user's MongoDB connection parameters.
* Double-encodes the updated connection string for safe usage in VS Code URLs.
*
* The DocumentDB VS Code extension requires double encoding for connection strings.
* See: https://microsoft.github.io/vscode-documentdb/manual/how-to-construct-url.html#double-encoding
*
* @returns {string} The encoded VS Code DocumentDB connection URL.
*/
private getDocumentDbUrl() {
const { adminLogin: adminLoginuserName = "", connectionString = "" } = userContext.vcoreMongoConnectionParams;
const updatedConnectionString = connectionString.replace(/<(user|username)>:<password>/i, adminLoginuserName);
const encodedUpdatedConnectionString = encodeURIComponent(encodeURIComponent(updatedConnectionString));
const documentDbUrl = `vscode://ms-azuretools.vscode-documentdb?connectionString=${encodedUpdatedConnectionString}`;
return documentDbUrl;
}
private getCosmosDbUrl() {
const activeTab = useTabs.getState().activeTab; const activeTab = useTabs.getState().activeTab;
const resourceId = encodeURIComponent(userContext.databaseAccount.id); const resourceId = encodeURIComponent(userContext.databaseAccount.id);
const database = encodeURIComponent(activeTab?.collection?.databaseId); const database = encodeURIComponent(activeTab?.collection?.databaseId);
const container = encodeURIComponent(activeTab?.collection?.id()); const container = encodeURIComponent(activeTab?.collection?.id());
const baseUrl = `vscode://ms-azuretools.vscode-cosmosdb?resourceId=${resourceId}`; const baseUrl = `vscode://ms-azuretools.vscode-cosmosdb?resourceId=${resourceId}`;
const vscodeUrl = activeTab ? `${baseUrl}&database=${database}&container=${container}` : baseUrl; const vscodeUrl = activeTab ? `${baseUrl}&database=${database}&container=${container}` : baseUrl;
return vscodeUrl;
}
private getVSCodeUrl(): string {
const isvCore = (userContext.apiType || userContext.databaseAccount.kind) === "VCoreMongo";
return isvCore ? this.getDocumentDbUrl() : this.getCosmosDbUrl();
}
public openInVsCode(): void {
const vscodeUrl = this.getVSCodeUrl();
const openVSCodeDialogProps: DialogProps = { const openVSCodeDialogProps: DialogProps = {
linkProps: { linkProps: {
linkText: "Download Visual Studio Code", linkText: "Download Visual Studio Code",
@@ -1115,8 +1148,8 @@ export default class Explorer {
} }
} }
public openUploadItemsPane(): void { public openUploadItemsPane(onUpload?: (data: UploadDetailsRecord[]) => void): void {
useSidePanel.getState().openSidePanel("Upload " + getUploadName(), <UploadItemsPane />); useSidePanel.getState().openSidePanel("Upload " + getUploadName(), <UploadItemsPane onUpload={onUpload} />);
} }
public openExecuteSprocParamsPanel(storedProcedure: StoredProcedure): void { public openExecuteSprocParamsPanel(storedProcedure: StoredProcedure): void {
useSidePanel useSidePanel
@@ -1124,7 +1157,7 @@ export default class Explorer {
.openSidePanel("Input parameters", <ExecuteSprocParamsPane storedProcedure={storedProcedure} />); .openSidePanel("Input parameters", <ExecuteSprocParamsPane storedProcedure={storedProcedure} />);
} }
public getDownloadModalConent(fileName: string): JSX.Element { public getDownloadModalContent(fileName: string): JSX.Element {
if (useNotebook.getState().isPhoenixNotebooks) { if (useNotebook.getState().isPhoenixNotebooks) {
return ( return (
<> <>
@@ -1148,10 +1181,14 @@ export default class Explorer {
? this.refreshDatabaseForResourceToken() ? this.refreshDatabaseForResourceToken()
: await this.refreshAllDatabases(); // await: we rely on the databases to be loaded before restoring the tabs further in the flow : await this.refreshAllDatabases(); // await: we rely on the databases to be loaded before restoring the tabs further in the flow
} }
if (!isFabricNative()) {
await useNotebook.getState().refreshNotebooksEnabledStateForAccount(); await useNotebook.getState().refreshNotebooksEnabledStateForAccount();
}
// TODO: remove reference to isNotebookEnabled and isNotebooksEnabledForAccount // TODO: remove reference to isNotebookEnabled and isNotebooksEnabledForAccount
const isNotebookEnabled = const isNotebookEnabled =
isFeatureSupported(PlatformFeature.Notebooks) &&
configContext.platform !== Platform.Fabric && configContext.platform !== Platform.Fabric &&
(userContext.features.notebooksDownBanner || (userContext.features.notebooksDownBanner ||
useNotebook.getState().isPhoenixNotebooks || useNotebook.getState().isPhoenixNotebooks ||
@@ -1159,7 +1196,11 @@ export default class Explorer {
useNotebook.getState().setIsNotebookEnabled(isNotebookEnabled); useNotebook.getState().setIsNotebookEnabled(isNotebookEnabled);
useNotebook useNotebook
.getState() .getState()
.setIsShellEnabled(useNotebook.getState().isPhoenixFeatures && isPublicInternetAccessAllowed()); .setIsShellEnabled(
isFeatureSupported(PlatformFeature.CloudShell) &&
useNotebook.getState().isPhoenixFeatures &&
isPublicInternetAccessAllowed(),
);
TelemetryProcessor.trace(Action.NotebookEnabled, ActionModifiers.Mark, { TelemetryProcessor.trace(Action.NotebookEnabled, ActionModifiers.Mark, {
isNotebookEnabled, isNotebookEnabled,
@@ -1170,7 +1211,7 @@ export default class Explorer {
await this.initNotebooks(userContext.databaseAccount); await this.initNotebooks(userContext.databaseAccount);
} }
if (userContext.authType === AuthType.AAD && userContext.apiType === "SQL") { if (userContext.authType === AuthType.AAD && userContext.apiType === "SQL" && !isFabricNative()) {
const throughputBucketsEnabled = await featureRegistered(userContext.subscriptionId, "ThroughputBucketing"); const throughputBucketsEnabled = await featureRegistered(userContext.subscriptionId, "ThroughputBucketing");
updateUserContext({ throughputBucketsEnabled }); updateUserContext({ throughputBucketsEnabled });
} }
@@ -1180,6 +1221,7 @@ export default class Explorer {
public async configureCopilot(): Promise<void> { public async configureCopilot(): Promise<void> {
if ( if (
!isFeatureSupported(PlatformFeature.Copilot) ||
userContext.apiType !== "SQL" || userContext.apiType !== "SQL" ||
!userContext.subscriptionId || !userContext.subscriptionId ||
![Environment.Development, Environment.Mpac, Environment.Prod].includes(getEnvironment()) ![Environment.Development, Environment.Mpac, Environment.Prod].includes(getEnvironment())

View File

@@ -163,8 +163,7 @@ describe("GraphExplorer", () => {
graphBackendEndpoint: "graphBackendEndpoint", graphBackendEndpoint: "graphBackendEndpoint",
databaseId: "databaseId", databaseId: "databaseId",
collectionId: "collectionId", collectionId: "collectionId",
masterKey: "masterKey", password: "password",
onLoadStartKey: 0, onLoadStartKey: 0,
onLoadStartKeyChange: (newKey: number): void => {}, onLoadStartKeyChange: (newKey: number): void => {},
resourceId: "resourceId", resourceId: "resourceId",

View File

@@ -16,7 +16,12 @@ import * as StorageUtility from "../../../Shared/StorageUtility";
import { LocalStorageUtility, StorageKey } from "../../../Shared/StorageUtility"; import { LocalStorageUtility, StorageKey } from "../../../Shared/StorageUtility";
import { Action } from "../../../Shared/Telemetry/TelemetryConstants"; import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor"; import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../../Utils/NotificationConsoleUtils"; import {
logConsoleError,
logConsoleInfo,
logConsoleProgress,
logConsoleWarning,
} from "../../../Utils/NotificationConsoleUtils";
import { EditorReact } from "../../Controls/Editor/EditorReact"; import { EditorReact } from "../../Controls/Editor/EditorReact";
import * as InputTypeaheadComponent from "../../Controls/InputTypeahead/InputTypeaheadComponent"; import * as InputTypeaheadComponent from "../../Controls/InputTypeahead/InputTypeaheadComponent";
import * as TabComponent from "../../Controls/Tabs/TabComponent"; import * as TabComponent from "../../Controls/Tabs/TabComponent";
@@ -54,7 +59,7 @@ export interface GraphExplorerProps {
graphBackendEndpoint: string; graphBackendEndpoint: string;
databaseId: string; databaseId: string;
collectionId: string; collectionId: string;
masterKey: string; password: string;
onLoadStartKey: number; onLoadStartKey: number;
onLoadStartKeyChange: (newKey: number) => void; onLoadStartKeyChange: (newKey: number) => void;
@@ -1083,6 +1088,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
public static reportToConsole(type: ConsoleDataType.InProgress, msg: string, ...errorData: any[]): () => void; public static reportToConsole(type: ConsoleDataType.InProgress, msg: string, ...errorData: any[]): () => void;
public static reportToConsole(type: ConsoleDataType.Info, msg: string, ...errorData: any[]): void; public static reportToConsole(type: ConsoleDataType.Info, msg: string, ...errorData: any[]): void;
public static reportToConsole(type: ConsoleDataType.Error, msg: string, ...errorData: any[]): void; public static reportToConsole(type: ConsoleDataType.Error, msg: string, ...errorData: any[]): void;
public static reportToConsole(type: ConsoleDataType.Warning, msg: string, ...errorData: any[]): void;
public static reportToConsole(type: ConsoleDataType, msg: string, ...errorData: any[]): void | (() => void) { public static reportToConsole(type: ConsoleDataType, msg: string, ...errorData: any[]): void | (() => void) {
let errorDataStr = ""; let errorDataStr = "";
if (errorData && errorData.length > 0) { if (errorData && errorData.length > 0) {
@@ -1099,6 +1105,8 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
return logConsoleInfo(consoleMessage); return logConsoleInfo(consoleMessage);
case ConsoleDataType.InProgress: case ConsoleDataType.InProgress:
return logConsoleProgress(consoleMessage); return logConsoleProgress(consoleMessage);
case ConsoleDataType.Warning:
return logConsoleWarning(consoleMessage);
} }
} }
@@ -1292,7 +1300,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
endpoint: `wss://${this.props.graphBackendEndpoint}`, endpoint: `wss://${this.props.graphBackendEndpoint}`,
databaseId: this.props.databaseId, databaseId: this.props.databaseId,
collectionId: this.props.collectionId, collectionId: this.props.collectionId,
masterKey: this.props.masterKey, password: this.props.password,
maxResultSize: GraphExplorer.MAX_RESULT_SIZE, maxResultSize: GraphExplorer.MAX_RESULT_SIZE,
}); });
} }

View File

@@ -8,28 +8,28 @@ describe("Gremlin Client", () => {
endpoint: null, endpoint: null,
collectionId: null, collectionId: null,
databaseId: null, databaseId: null,
masterKey: null,
maxResultSize: 10000, maxResultSize: 10000,
password: null,
}; };
it("should use databaseId, collectionId and masterKey to authenticate", () => { it("should use databaseId, collectionId and password to authenticate", () => {
const collectionId = "collectionId"; const collectionId = "collectionId";
const databaseId = "databaseId"; const databaseId = "databaseId";
const masterKey = "masterKey"; const testPassword = "password";
const gremlinClient = new GremlinClient(); const gremlinClient = new GremlinClient();
gremlinClient.initialize({ gremlinClient.initialize({
endpoint: null, endpoint: null,
collectionId, collectionId,
databaseId, databaseId,
masterKey,
maxResultSize: 0, maxResultSize: 0,
password: testPassword,
}); });
// User must includes these values // User must includes these values
expect(gremlinClient.client.params.user.indexOf(collectionId)).not.toBe(-1); expect(gremlinClient.client.params.user.indexOf(collectionId)).not.toBe(-1);
expect(gremlinClient.client.params.user.indexOf(databaseId)).not.toBe(-1); expect(gremlinClient.client.params.user.indexOf(databaseId)).not.toBe(-1);
expect(gremlinClient.client.params.password).toEqual(masterKey); expect(gremlinClient.client.params.password).toEqual(testPassword);
}); });
it("should aggregate RU charges across multiple responses", (done) => { it("should aggregate RU charges across multiple responses", (done) => {

View File

@@ -11,8 +11,8 @@ export interface GremlinClientParameters {
endpoint: string; endpoint: string;
databaseId: string; databaseId: string;
collectionId: string; collectionId: string;
masterKey: string;
maxResultSize: number; maxResultSize: number;
password: string;
} }
export interface GremlinRequestResult { export interface GremlinRequestResult {
@@ -43,7 +43,7 @@ export class GremlinClient {
this.client = new GremlinSimpleClient({ this.client = new GremlinSimpleClient({
endpoint: params.endpoint, endpoint: params.endpoint,
user: `/dbs/${params.databaseId}/colls/${params.collectionId}`, user: `/dbs/${params.databaseId}/colls/${params.collectionId}`,
password: params.masterKey, password: params.password,
successCallback: (result: Result) => { successCallback: (result: Result) => {
this.storePendingResult(result); this.storePendingResult(result);
this.flushResult(result.requestId); this.flushResult(result.requestId);

View File

@@ -5,11 +5,11 @@
import * as sinon from "sinon"; import * as sinon from "sinon";
import { import {
GremlinRequestMessage,
GremlinResponseMessage,
GremlinSimpleClient, GremlinSimpleClient,
GremlinSimpleClientParameters, GremlinSimpleClientParameters,
Result, Result,
GremlinRequestMessage,
GremlinResponseMessage,
} from "./GremlinSimpleClient"; } from "./GremlinSimpleClient";
describe("Gremlin Simple Client", () => { describe("Gremlin Simple Client", () => {

View File

@@ -95,3 +95,10 @@
white-space: nowrap; white-space: nowrap;
} }
} }
@media (max-width: 768px) {
.newVertexComponent {
padding: 0;
width: 100%;
}
}

View File

@@ -1,5 +1,6 @@
import { KeyboardAction } from "KeyboardShortcuts"; import { KeyboardAction } from "KeyboardShortcuts";
import { isDataplaneRbacSupported } from "Utils/APITypeUtils"; import { isDataplaneRbacSupported } from "Utils/APITypeUtils";
import { areAdvancedScriptsSupported, isFeatureSupported, PlatformFeature } from "Utils/PlatformFeatureUtils";
import * as React from "react"; import * as React from "react";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import AddSqlQueryIcon from "../../../../images/AddSqlQuery_16x16.svg"; import AddSqlQueryIcon from "../../../../images/AddSqlQuery_16x16.svg";
@@ -17,7 +18,7 @@ import SynapseIcon from "../../../../images/synapse-link.svg";
import VSCodeIcon from "../../../../images/vscode.svg"; import VSCodeIcon from "../../../../images/vscode.svg";
import { AuthType } from "../../../AuthType"; import { AuthType } from "../../../AuthType";
import * as Constants from "../../../Common/Constants"; import * as Constants from "../../../Common/Constants";
import { Platform, configContext } from "../../../ConfigContext"; import { configContext, Platform } from "../../../ConfigContext";
import * as ViewModels from "../../../Contracts/ViewModels"; import * as ViewModels from "../../../Contracts/ViewModels";
import { userContext } from "../../../UserContext"; import { userContext } from "../../../UserContext";
import { isRunningOnNationalCloud } from "../../../Utils/CloudUtils"; import { isRunningOnNationalCloud } from "../../../Utils/CloudUtils";
@@ -63,9 +64,11 @@ export function createStaticCommandBarButtons(
} }
if (userContext.apiType !== "Gremlin") { if (userContext.apiType !== "Gremlin") {
const addVsCode = createOpenVsCodeDialogButton(container); const addVsCode = createOpenVsCodeDialogButton(container);
if (addVsCode) {
buttons.push(addVsCode); buttons.push(addVsCode);
} }
} }
}
if (isDataplaneRbacSupported(userContext.apiType)) { if (isDataplaneRbacSupported(userContext.apiType)) {
const [loginButtonProps, setLoginButtonProps] = useState<CommandButtonComponentProps | undefined>(undefined); const [loginButtonProps, setLoginButtonProps] = useState<CommandButtonComponentProps | undefined>(undefined);
@@ -242,11 +245,17 @@ export function createDivider(): CommandButtonComponentProps {
function areScriptsSupported(): boolean { function areScriptsSupported(): boolean {
return ( return (
configContext.platform !== Platform.Fabric && (userContext.apiType === "SQL" || userContext.apiType === "Gremlin") areAdvancedScriptsSupported() &&
configContext.platform !== Platform.Fabric &&
(userContext.apiType === "SQL" || userContext.apiType === "Gremlin")
); );
} }
function createOpenSynapseLinkDialogButton(container: Explorer): CommandButtonComponentProps { function createOpenSynapseLinkDialogButton(container: Explorer): CommandButtonComponentProps {
if (!isFeatureSupported(PlatformFeature.SynapseLink)) {
return undefined;
}
if (configContext.platform === Platform.Emulator) { if (configContext.platform === Platform.Emulator) {
return undefined; return undefined;
} }
@@ -274,6 +283,10 @@ function createOpenSynapseLinkDialogButton(container: Explorer): CommandButtonCo
} }
function createOpenVsCodeDialogButton(container: Explorer): CommandButtonComponentProps { function createOpenVsCodeDialogButton(container: Explorer): CommandButtonComponentProps {
if (!isFeatureSupported(PlatformFeature.VSCodeIntegration)) {
return undefined;
}
const label = "Visual Studio Code"; const label = "Visual Studio Code";
return { return {
iconSrc: VSCodeIcon, iconSrc: VSCodeIcon,

View File

@@ -13,4 +13,5 @@ export enum ConsoleDataType {
Info = 0, Info = 0,
Error = 1, Error = 1,
InProgress = 2, InProgress = 2,
Warning = 3,
} }

View File

@@ -173,8 +173,20 @@
.message { .message {
flex-grow: 1; flex-grow: 1;
white-space:pre-wrap; white-space:pre-wrap;
overflow-wrap: break-word;
word-break: break-word;
} }
} }
} }
} }
@media (max-width: 768px) {
.notificationConsoleContents {
overflow-y: auto;
.notificationConsoleData {
overflow: visible;
}
}
}
} }

View File

@@ -14,6 +14,7 @@ import ErrorRedIcon from "../../../../images/error_red.svg";
import infoBubbleIcon from "../../../../images/info-bubble-9x9.svg"; import infoBubbleIcon from "../../../../images/info-bubble-9x9.svg";
import InfoIcon from "../../../../images/info_color.svg"; import InfoIcon from "../../../../images/info_color.svg";
import LoadingIcon from "../../../../images/loading.svg"; import LoadingIcon from "../../../../images/loading.svg";
import WarningIcon from "../../../../images/warning.svg";
import { ClientDefaults, KeyCodes } from "../../../Common/Constants"; import { ClientDefaults, KeyCodes } from "../../../Common/Constants";
import { userContext } from "../../../UserContext"; import { userContext } from "../../../UserContext";
import { useNotificationConsole } from "../../../hooks/useNotificationConsole"; import { useNotificationConsole } from "../../../hooks/useNotificationConsole";
@@ -91,6 +92,9 @@ export class NotificationConsoleComponent extends React.Component<
const numInfoItems = this.state.allConsoleData.filter( const numInfoItems = this.state.allConsoleData.filter(
(data: ConsoleData) => data.type === ConsoleDataType.Info, (data: ConsoleData) => data.type === ConsoleDataType.Info,
).length; ).length;
const numWarningItems = this.state.allConsoleData.filter(
(data: ConsoleData) => data.type === ConsoleDataType.Warning,
).length;
return ( return (
<div className="notificationConsoleContainer"> <div className="notificationConsoleContainer">
@@ -118,6 +122,10 @@ export class NotificationConsoleComponent extends React.Component<
<img src={infoBubbleIcon} alt="Info items" /> <img src={infoBubbleIcon} alt="Info items" />
<span className="numInfoItems">{numInfoItems}</span> <span className="numInfoItems">{numInfoItems}</span>
</span> </span>
<span className="notificationConsoleHeaderIconWithData">
<img src={WarningIcon} alt="Warning items" />
<span className="numWarningItems">{numWarningItems}</span>
</span>
</span> </span>
{userContext.features.pr && <PrPreview pr={userContext.features.pr} />} {userContext.features.pr && <PrPreview pr={userContext.features.pr} />}
<span className="consoleSplitter" /> <span className="consoleSplitter" />
@@ -198,6 +206,7 @@ export class NotificationConsoleComponent extends React.Component<
{item.type === ConsoleDataType.Info && <img className="infoIcon" src={InfoIcon} alt="info" />} {item.type === ConsoleDataType.Info && <img className="infoIcon" src={InfoIcon} alt="info" />}
{item.type === ConsoleDataType.Error && <img className="errorIcon" src={ErrorRedIcon} alt="error" />} {item.type === ConsoleDataType.Error && <img className="errorIcon" src={ErrorRedIcon} alt="error" />}
{item.type === ConsoleDataType.InProgress && <img className="loaderIcon" src={LoaderIcon} alt="in progress" />} {item.type === ConsoleDataType.InProgress && <img className="loaderIcon" src={LoaderIcon} alt="in progress" />}
{item.type === ConsoleDataType.Warning && <img className="warningIcon" src={WarningIcon} alt="warning" />}
<span className="date">{item.date}</span> <span className="date">{item.date}</span>
<span className="message" role="alert" aria-live="assertive"> <span className="message" role="alert" aria-live="assertive">
{item.message} {item.message}

View File

@@ -59,6 +59,19 @@ exports[`NotificationConsoleComponent renders the console 1`] = `
0 0
</span> </span>
</span> </span>
<span
className="notificationConsoleHeaderIconWithData"
>
<img
alt="Warning items"
src={{}}
/>
<span
className="numWarningItems"
>
0
</span>
</span>
</span> </span>
<span <span
className="consoleSplitter" className="consoleSplitter"
@@ -229,6 +242,19 @@ exports[`NotificationConsoleComponent renders the console 2`] = `
1 1
</span> </span>
</span> </span>
<span
className="notificationConsoleHeaderIconWithData"
>
<img
alt="Warning items"
src={{}}
/>
<span
className="numWarningItems"
>
0
</span>
</span>
</span> </span>
<span <span
className="consoleSplitter" className="consoleSplitter"

View File

@@ -188,6 +188,11 @@ function openPane(action: ActionContracts.OpenPane, explorer: Explorer) {
action.paneKind === ActionContracts.PaneKind[ActionContracts.PaneKind.AddCollection] action.paneKind === ActionContracts.PaneKind[ActionContracts.PaneKind.AddCollection]
) { ) {
explorer.onNewCollectionClicked(); explorer.onNewCollectionClicked();
} else if (
action.paneKind === ActionContracts.PaneKind.QuickStart ||
action.paneKind === ActionContracts.PaneKind[ActionContracts.PaneKind.QuickStart]
) {
explorer.onNewCollectionClicked({ isQuickstart: true });
} else if ( } else if (
action.paneKind === ActionContracts.PaneKind.CassandraAddCollection || action.paneKind === ActionContracts.PaneKind.CassandraAddCollection ||
action.paneKind === ActionContracts.PaneKind[ActionContracts.PaneKind.CassandraAddCollection] action.paneKind === ActionContracts.PaneKind[ActionContracts.PaneKind.CassandraAddCollection]

View File

@@ -25,7 +25,7 @@ import { FullTextPoliciesComponent } from "Explorer/Controls/FullTextSeach/FullT
import { VectorEmbeddingPoliciesComponent } from "Explorer/Controls/VectorSearch/VectorEmbeddingPoliciesComponent"; import { VectorEmbeddingPoliciesComponent } from "Explorer/Controls/VectorSearch/VectorEmbeddingPoliciesComponent";
import { import {
AllPropertiesIndexed, AllPropertiesIndexed,
AnalyticalStorageContent, AnalyticalStoreHeader,
ContainerVectorPolicyTooltipContent, ContainerVectorPolicyTooltipContent,
FullTextPolicyDefault, FullTextPolicyDefault,
getPartitionKey, getPartitionKey,
@@ -49,14 +49,11 @@ import { Action } from "Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "Shared/Telemetry/TelemetryProcessor"; import * as TelemetryProcessor from "Shared/Telemetry/TelemetryProcessor";
import { userContext } from "UserContext"; import { userContext } from "UserContext";
import { getCollectionName } from "Utils/APITypeUtils"; import { getCollectionName } from "Utils/APITypeUtils";
import { import { isCapabilityEnabled, isServerlessAccount, isVectorSearchEnabled } from "Utils/CapabilityUtils";
isCapabilityEnabled, import { isFeatureSupported, PlatformFeature } from "Utils/PlatformFeatureUtils";
isFullTextSearchEnabled,
isServerlessAccount,
isVectorSearchEnabled,
} from "Utils/CapabilityUtils";
import { getUpsellMessage } from "Utils/PricingUtils"; import { getUpsellMessage } from "Utils/PricingUtils";
import { ValidCosmosDbIdDescription, ValidCosmosDbIdInputPattern } from "Utils/ValidationUtils"; import { ValidCosmosDbIdDescription, ValidCosmosDbIdInputPattern } from "Utils/ValidationUtils";
import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
import { CollapsibleSectionComponent } from "../../Controls/CollapsiblePanel/CollapsibleSectionComponent"; import { CollapsibleSectionComponent } from "../../Controls/CollapsiblePanel/CollapsibleSectionComponent";
import { ThroughputInput } from "../../Controls/ThroughputInput/ThroughputInput"; import { ThroughputInput } from "../../Controls/ThroughputInput/ThroughputInput";
import { ContainerSampleGenerator } from "../../DataSamples/ContainerSampleGenerator"; import { ContainerSampleGenerator } from "../../DataSamples/ContainerSampleGenerator";
@@ -110,6 +107,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
private collectionThroughput: number; private collectionThroughput: number;
private isCollectionAutoscale: boolean; private isCollectionAutoscale: boolean;
private isCostAcknowledged: boolean; private isCostAcknowledged: boolean;
private showFullTextSearch: boolean;
constructor(props: AddCollectionPanelProps) { constructor(props: AddCollectionPanelProps) {
super(props); super(props);
@@ -126,7 +124,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
isSharded: userContext.apiType !== "Tables", isSharded: userContext.apiType !== "Tables",
partitionKey: getPartitionKey(props.isQuickstart), partitionKey: getPartitionKey(props.isQuickstart),
subPartitionKeys: [], subPartitionKeys: [],
enableDedicatedThroughput: false, enableDedicatedThroughput: isFabricNative(), // Dedicated throughput is only enabled in Fabric Native by default
createMongoWildCardIndex: createMongoWildCardIndex:
isCapabilityEnabled("EnableMongo") && !isCapabilityEnabled("EnableMongo16MBDocumentSupport"), isCapabilityEnabled("EnableMongo") && !isCapabilityEnabled("EnableMongo16MBDocumentSupport"),
useHashV1: false, useHashV1: false,
@@ -144,6 +142,8 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
fullTextIndexes: [], fullTextIndexes: [],
fullTextPolicyValidated: true, fullTextPolicyValidated: true,
}; };
this.showFullTextSearch = userContext.apiType === "SQL";
} }
componentDidMount(): void { componentDidMount(): void {
@@ -266,7 +266,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
<div className="panelMainContent"> <div className="panelMainContent">
{!(isFabricNative() && this.props.databaseId !== undefined) && ( {!(isFabricNative() && this.props.databaseId !== undefined) && (
<Stack hidden={userContext.apiType === "Tables"}> <Stack hidden={userContext.apiType === "Tables"} style={{ marginBottom: -2 }}>
<Stack horizontal> <Stack horizontal>
<span className="mandatoryStar">*&nbsp;</span> <span className="mandatoryStar">*&nbsp;</span>
<Text className="panelTextBold" variant="small"> <Text className="panelTextBold" variant="small">
@@ -337,7 +337,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
size={40} size={40}
className="panelTextField" className="panelTextField"
aria-label="New database id, Type a new database id" aria-label="New database id, Type a new database id"
autoFocus
tabIndex={0} tabIndex={0}
value={this.state.newDatabaseId} value={this.state.newDatabaseId}
onChange={(event: React.ChangeEvent<HTMLInputElement>) => onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
@@ -408,12 +407,12 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
responsiveMode={999} responsiveMode={999}
/> />
)} )}
<Separator className="panelSeparator" /> <Separator className="panelSeparator" style={{ marginTop: -4, marginBottom: -4 }} />
</Stack> </Stack>
)} )}
<Stack> <Stack>
<Stack horizontal> <Stack horizontal style={{ marginTop: -5, marginBottom: 1 }}>
<span className="mandatoryStar">*&nbsp;</span> <span className="mandatoryStar">*&nbsp;</span>
<Text className="panelTextBold" variant="small"> <Text className="panelTextBold" variant="small">
{`${getCollectionName()} id`} {`${getCollectionName()} id`}
@@ -450,11 +449,12 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
this.setState({ collectionId: event.target.value }) this.setState({ collectionId: event.target.value })
} }
/> />
<Separator className="panelSeparator" style={{ marginTop: -5, marginBottom: -5 }} />
</Stack> </Stack>
{this.shouldShowIndexingOptionsForFreeTierAccount() && ( {this.shouldShowIndexingOptionsForFreeTierAccount() && (
<Stack> <Stack>
<Stack horizontal> <Stack horizontal style={{ marginTop: -4, marginBottom: -5 }}>
<span className="mandatoryStar">*&nbsp;</span> <span className="mandatoryStar">*&nbsp;</span>
<Text className="panelTextBold" variant="small"> <Text className="panelTextBold" variant="small">
Indexing Indexing
@@ -500,7 +500,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
(!this.state.isSharedThroughputChecked || (!this.state.isSharedThroughputChecked ||
this.props.explorer.isFixedCollectionWithSharedThroughputSupported()) && ( this.props.explorer.isFixedCollectionWithSharedThroughputSupported()) && (
<Stack> <Stack>
<Stack horizontal> <Stack horizontal style={{ marginTop: -5, marginBottom: -4 }}>
<span className="mandatoryStar">*&nbsp;</span> <span className="mandatoryStar">*&nbsp;</span>
<Text className="panelTextBold" variant="small"> <Text className="panelTextBold" variant="small">
Sharding Sharding
@@ -556,7 +556,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
{this.state.isSharded && ( {this.state.isSharded && (
<Stack> <Stack>
<Stack horizontal> <Stack horizontal style={{ marginTop: -5, marginBottom: -4 }}>
<span className="mandatoryStar">*&nbsp;</span> <span className="mandatoryStar">*&nbsp;</span>
<Text className="panelTextBold" variant="small"> <Text className="panelTextBold" variant="small">
{getPartitionKeyName()} {getPartitionKeyName()}
@@ -600,7 +600,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
{userContext.apiType === "SQL" && {userContext.apiType === "SQL" &&
this.state.subPartitionKeys.map((subPartitionKey: string, index: number) => { this.state.subPartitionKeys.map((subPartitionKey: string, index: number) => {
return ( return (
<Stack style={{ marginBottom: 8 }} key={`uniqueKey${index}`} horizontal> <Stack style={{ marginBottom: 2, marginTop: -5 }} key={`uniqueKey${index}`} horizontal>
<div <div
style={{ style={{
width: "20px", width: "20px",
@@ -646,7 +646,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
</Stack> </Stack>
); );
})} })}
{!isFabricNative() && userContext.apiType === "SQL" && ( {userContext.apiType === "SQL" && (
<Stack className="panelGroupSpacing"> <Stack className="panelGroupSpacing">
<DefaultButton <DefaultButton
styles={{ root: { padding: 0, width: 200, height: 30 }, label: { fontSize: 12 } }} styles={{ root: { padding: 0, width: 200, height: 30 }, label: { fontSize: 12 } }}
@@ -668,6 +668,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
)} )}
</Stack> </Stack>
)} )}
<Separator className="panelSeparator" style={{ marginTop: 2, marginBottom: -4 }} />
</Stack> </Stack>
)} )}
@@ -709,7 +710,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
</Stack> </Stack>
)} )}
{this.shouldShowCollectionThroughputInput() && ( {this.shouldShowCollectionThroughputInput() && !isFabricNative() && (
<ThroughputInput <ThroughputInput
showFreeTierExceedThroughputTooltip={isFreeTierAccount() && !isFirstResourceCreated} showFreeTierExceedThroughputTooltip={isFreeTierAccount() && !isFirstResourceCreated}
isDatabase={false} isDatabase={false}
@@ -728,7 +729,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
)} )}
{!isFabricNative() && userContext.apiType === "SQL" && ( {!isFabricNative() && userContext.apiType === "SQL" && (
<Stack> <Stack style={{ marginTop: -2, marginBottom: -4 }}>
{UniqueKeysHeader()} {UniqueKeysHeader()}
{this.state.uniqueKeys.map((uniqueKey: string, i: number): JSX.Element => { {this.state.uniqueKeys.map((uniqueKey: string, i: number): JSX.Element => {
return ( return (
@@ -742,7 +743,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
: "Comma separated paths e.g. /firstName,/address/zipCode" : "Comma separated paths e.g. /firstName,/address/zipCode"
} }
className="panelTextField" className="panelTextField"
autoFocus
value={uniqueKey} value={uniqueKey}
onChange={(event: React.ChangeEvent<HTMLInputElement>) => { onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
const uniqueKeys = this.state.uniqueKeys.map((uniqueKey: string, j: number) => { const uniqueKeys = this.state.uniqueKeys.map((uniqueKey: string, j: number) => {
@@ -777,10 +777,14 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
</Stack> </Stack>
)} )}
{!isFabricNative() && userContext.apiType === "SQL" && (
<Separator className="panelSeparator" style={{ marginTop: -15, marginBottom: -4 }} />
)}
{shouldShowAnalyticalStoreOptions() && ( {shouldShowAnalyticalStoreOptions() && (
<Stack className="panelGroupSpacing"> <Stack className="panelGroupSpacing" style={{ marginTop: -4 }}>
<Text className="panelTextBold" variant="small"> <Text className="panelTextBold" variant="small">
{AnalyticalStorageContent()} {AnalyticalStoreHeader()}
</Text> </Text>
<Stack horizontal verticalAlign="center"> <Stack horizontal verticalAlign="center">
@@ -821,7 +825,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
<Stack className="panelGroupSpacing"> <Stack className="panelGroupSpacing">
<Text variant="small"> <Text variant="small">
Azure Synapse Link is required for creating an analytical store{" "} Azure Synapse Link is required for creating an analytical store{" "}
{getCollectionName().toLocaleLowerCase()}. Enable Synapse Link for this Cosmos DB account.{" "} {getCollectionName().toLocaleLowerCase()}. Enable Synapse Link for this Cosmos DB account. <br />
<Link <Link
href="https://aka.ms/cosmosdb-synapselink" href="https://aka.ms/cosmosdb-synapselink"
target="_blank" target="_blank"
@@ -1131,7 +1135,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
// } // }
private shouldShowCollectionThroughputInput(): boolean { private shouldShowCollectionThroughputInput(): boolean {
if (isFabricNative() || isServerlessAccount()) { if (isServerlessAccount()) {
return false; return false;
} }
@@ -1157,11 +1161,15 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
} }
private shouldShowVectorSearchParameters() { private shouldShowVectorSearchParameters() {
return isVectorSearchEnabled() && (isServerlessAccount() || this.shouldShowCollectionThroughputInput()); return (
isFeatureSupported(PlatformFeature.VectorSearch) &&
isVectorSearchEnabled() &&
(isServerlessAccount() || this.shouldShowCollectionThroughputInput())
);
} }
private shouldShowFullTextSearchParameters() { private shouldShowFullTextSearchParameters() {
return isFullTextSearchEnabled() && (isServerlessAccount() || this.shouldShowCollectionThroughputInput()); return isFeatureSupported(PlatformFeature.FullTextSearch) && !isFabricNative() && this.showFullTextSearch;
} }
private parseUniqueKeys(): DataModels.UniqueKeyPolicy { private parseUniqueKeys(): DataModels.UniqueKeyPolicy {
@@ -1316,7 +1324,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
}; };
} }
if (this.shouldShowFullTextSearchParameters()) { if (this.showFullTextSearch) {
indexingPolicy.fullTextIndexes = this.state.fullTextIndexes; indexingPolicy.fullTextIndexes = this.state.fullTextIndexes;
} }
@@ -1350,7 +1358,12 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
let offerThroughput: number; let offerThroughput: number;
let autoPilotMaxThroughput: number; let autoPilotMaxThroughput: number;
if (databaseLevelThroughput) { // Throughput
if (isFabricNative()) {
// Fabric Native accounts are always autoscale and have a fixed throughput of 5K
autoPilotMaxThroughput = AutoPilotUtils.autoPilotThroughput5K;
offerThroughput = undefined;
} else if (databaseLevelThroughput) {
if (this.state.createNewDatabase) { if (this.state.createNewDatabase) {
if (this.isNewDatabaseAutoscale) { if (this.isNewDatabaseAutoscale) {
autoPilotMaxThroughput = this.newDatabaseThroughput; autoPilotMaxThroughput = this.newDatabaseThroughput;

View File

@@ -6,6 +6,7 @@ import { getFullTextLanguageOptions } from "Explorer/Controls/FullTextSeach/Full
import { isFabricNative } from "Platform/Fabric/FabricUtil"; import { isFabricNative } from "Platform/Fabric/FabricUtil";
import React from "react"; import React from "react";
import { userContext } from "UserContext"; import { userContext } from "UserContext";
import { isFeatureSupported, PlatformFeature } from "Utils/PlatformFeatureUtils";
export function getPartitionKeyTooltipText(): string { export function getPartitionKeyTooltipText(): string {
if (userContext.apiType === "Mongo") { if (userContext.apiType === "Mongo") {
@@ -73,7 +74,7 @@ export function UniqueKeysHeader(): JSX.Element {
"Unique keys provide developers with the ability to add a layer of data integrity to their database. By creating a unique key policy when a container is created, you ensure the uniqueness of one or more values per partition key."; "Unique keys provide developers with the ability to add a layer of data integrity to their database. By creating a unique key policy when a container is created, you ensure the uniqueness of one or more values per partition key.";
return ( return (
<Stack horizontal> <Stack horizontal style={{ marginBottom: -2 }}>
<Text className="panelTextBold" variant="small"> <Text className="panelTextBold" variant="small">
Unique keys Unique keys
</Text> </Text>
@@ -85,7 +86,11 @@ export function UniqueKeysHeader(): JSX.Element {
} }
export function shouldShowAnalyticalStoreOptions(): boolean { export function shouldShowAnalyticalStoreOptions(): boolean {
if (isFabricNative() || configContext.platform === Platform.Emulator) { if (
!isFeatureSupported(PlatformFeature.AnalyticalStore) ||
isFabricNative() ||
configContext.platform === Platform.Emulator
) {
return false; return false;
} }
@@ -98,6 +103,21 @@ export function shouldShowAnalyticalStoreOptions(): boolean {
} }
} }
export function AnalyticalStoreHeader(): JSX.Element {
const tooltipContent =
"Enable analytical store capability to perform near real-time analytics on your operational data, without impacting the performance of transactional workloads.";
return (
<Stack horizontal style={{ marginBottom: -2 }}>
<Text className="panelTextBold" variant="small">
Analytical Store
</Text>
<TooltipHost directionalHint={DirectionalHint.bottomLeftEdge} content={tooltipContent}>
<Icon iconName="Info" className="panelInfoIcon" tabIndex={0} ariaLabel={tooltipContent} />
</TooltipHost>
</Stack>
);
}
export function AnalyticalStorageContent(): JSX.Element { export function AnalyticalStorageContent(): JSX.Element {
return ( return (
<Text variant="small"> <Text variant="small">

View File

@@ -11,6 +11,11 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
> >
<Stack <Stack
hidden={false} hidden={false}
style={
{
"marginBottom": -2,
}
}
> >
<Stack <Stack
horizontal={true} horizontal={true}
@@ -88,7 +93,6 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
aria-label="New database id, Type a new database id" aria-label="New database id, Type a new database id"
aria-required={true} aria-required={true}
autoComplete="off" autoComplete="off"
autoFocus={true}
className="panelTextField" className="panelTextField"
id="newDatabaseId" id="newDatabaseId"
name="newDatabaseId" name="newDatabaseId"
@@ -140,11 +144,23 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
</Stack> </Stack>
<Separator <Separator
className="panelSeparator" className="panelSeparator"
style={
{
"marginBottom": -4,
"marginTop": -4,
}
}
/> />
</Stack> </Stack>
<Stack> <Stack>
<Stack <Stack
horizontal={true} horizontal={true}
style={
{
"marginBottom": 1,
"marginTop": -5,
}
}
> >
<span <span
className="mandatoryStar" className="mandatoryStar"
@@ -186,10 +202,25 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
type="text" type="text"
value="" value=""
/> />
<Separator
className="panelSeparator"
style={
{
"marginBottom": -5,
"marginTop": -5,
}
}
/>
</Stack> </Stack>
<Stack> <Stack>
<Stack <Stack
horizontal={true} horizontal={true}
style={
{
"marginBottom": -4,
"marginTop": -5,
}
}
> >
<span <span
className="mandatoryStar" className="mandatoryStar"
@@ -254,6 +285,15 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
Add hierarchical partition key Add hierarchical partition key
</CustomizedDefaultButton> </CustomizedDefaultButton>
</Stack> </Stack>
<Separator
className="panelSeparator"
style={
{
"marginBottom": -4,
"marginTop": 2,
}
}
/>
</Stack> </Stack>
<ThroughputInput <ThroughputInput
isDatabase={false} isDatabase={false}
@@ -263,9 +303,21 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
setIsThroughputCapExceeded={[Function]} setIsThroughputCapExceeded={[Function]}
setThroughputValue={[Function]} setThroughputValue={[Function]}
/> />
<Stack> <Stack
style={
{
"marginBottom": -4,
"marginTop": -2,
}
}
>
<Stack <Stack
horizontal={true} horizontal={true}
style={
{
"marginBottom": -2,
}
}
> >
<Text <Text
className="panelTextBold" className="panelTextBold"
@@ -306,26 +358,53 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
Add unique key Add unique key
</CustomizedActionButton> </CustomizedActionButton>
</Stack> </Stack>
<Separator
className="panelSeparator"
style={
{
"marginBottom": -4,
"marginTop": -15,
}
}
/>
<Stack <Stack
className="panelGroupSpacing" className="panelGroupSpacing"
style={
{
"marginTop": -4,
}
}
> >
<Text <Text
className="panelTextBold" className="panelTextBold"
variant="small" variant="small"
>
<Stack
horizontal={true}
style={
{
"marginBottom": -2,
}
}
> >
<Text <Text
className="panelTextBold"
variant="small" variant="small"
> >
Enable analytical store capability to perform near real-time analytics on your operational data, without impacting the performance of transactional workloads. Analytical Store
<StyledLinkBase
aria-label="Learn more about analytical store."
href="https://aka.ms/analytical-store-overview"
target="_blank"
>
Learn more
</StyledLinkBase>
</Text> </Text>
<StyledTooltipHostBase
content="Enable analytical store capability to perform near real-time analytics on your operational data, without impacting the performance of transactional workloads."
directionalHint={4}
>
<Icon
ariaLabel="Enable analytical store capability to perform near real-time analytics on your operational data, without impacting the performance of transactional workloads."
className="panelInfoIcon"
iconName="Info"
tabIndex={0}
/>
</StyledTooltipHostBase>
</Stack>
</Text> </Text>
<Stack <Stack
horizontal={true} horizontal={true}
@@ -382,7 +461,7 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
container container
. Enable Synapse Link for this Cosmos DB account. . Enable Synapse Link for this Cosmos DB account.
<br />
<StyledLinkBase <StyledLinkBase
aria-label="Learn more about Azure Synapse Link." aria-label="Learn more about Azure Synapse Link."
className="capacitycalculator-link" className="capacitycalculator-link"
@@ -411,6 +490,44 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
/> />
</Stack> </Stack>
</Stack> </Stack>
<Stack>
<CollapsibleSectionComponent
isExpandedByDefault={false}
onExpand={[Function]}
title="Container Full Text Search Policy"
>
<Stack
id="collapsibleFullTextPolicySectionContent"
styles={
{
"root": {
"position": "relative",
},
}
}
>
<Stack
styles={
{
"root": {
"paddingLeft": 40,
},
}
}
>
<FullTextPoliciesComponent
fullTextPolicy={
{
"defaultLanguage": "en-US",
"fullTextPaths": [],
}
}
onFullTextPathChange={[Function]}
/>
</Stack>
</Stack>
</CollapsibleSectionComponent>
</Stack>
<CollapsibleSectionComponent <CollapsibleSectionComponent
isExpandedByDefault={false} isExpandedByDefault={false}
onExpand={[Function]} onExpand={[Function]}

View File

@@ -40,12 +40,12 @@ import { PanelInfoErrorComponent } from "Explorer/Panes/PanelInfoErrorComponent"
import { PanelLoadingScreen } from "Explorer/Panes/PanelLoadingScreen"; import { PanelLoadingScreen } from "Explorer/Panes/PanelLoadingScreen";
import { useDatabases } from "Explorer/useDatabases"; import { useDatabases } from "Explorer/useDatabases";
import { useSidePanel } from "hooks/useSidePanel"; import { useSidePanel } from "hooks/useSidePanel";
import React, { useEffect, useState } from "react"; import React, { MutableRefObject, useEffect, useRef, useState } from "react";
import { CollectionCreation } from "Shared/Constants"; import { CollectionCreation } from "Shared/Constants";
import { Action } from "Shared/Telemetry/TelemetryConstants"; import { Action } from "Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "Shared/Telemetry/TelemetryProcessor"; import * as TelemetryProcessor from "Shared/Telemetry/TelemetryProcessor";
import { userContext } from "UserContext"; import { userContext } from "UserContext";
import { isFullTextSearchEnabled, isServerlessAccount, isVectorSearchEnabled } from "Utils/CapabilityUtils"; import { isServerlessAccount, isVectorSearchEnabled } from "Utils/CapabilityUtils";
import { ValidCosmosDbIdDescription, ValidCosmosDbIdInputPattern } from "Utils/ValidationUtils"; import { ValidCosmosDbIdDescription, ValidCosmosDbIdInputPattern } from "Utils/ValidationUtils";
export interface AddGlobalSecondaryIndexPanelProps { export interface AddGlobalSecondaryIndexPanelProps {
@@ -75,6 +75,8 @@ export const AddGlobalSecondaryIndexPanel = (props: AddGlobalSecondaryIndexPanel
const [showErrorDetails, setShowErrorDetails] = useState<boolean>(); const [showErrorDetails, setShowErrorDetails] = useState<boolean>();
const [isExecuting, setIsExecuting] = useState<boolean>(); const [isExecuting, setIsExecuting] = useState<boolean>();
const showFullTextSearch: MutableRefObject<boolean> = useRef<boolean>(userContext.apiType === "SQL");
useEffect(() => { useEffect(() => {
const sourceContainerOptions: IDropdownOption[] = []; const sourceContainerOptions: IDropdownOption[] = [];
useDatabases.getState().databases.forEach((database: Database) => { useDatabases.getState().databases.forEach((database: Database) => {
@@ -140,10 +142,6 @@ export const AddGlobalSecondaryIndexPanel = (props: AddGlobalSecondaryIndexPanel
return isVectorSearchEnabled() && (isServerlessAccount() || showCollectionThroughputInput()); return isVectorSearchEnabled() && (isServerlessAccount() || showCollectionThroughputInput());
}; };
const showFullTextSearchParameters = (): boolean => {
return isFullTextSearchEnabled() && (isServerlessAccount() || showCollectionThroughputInput());
};
const getAnalyticalStorageTtl = (): number => { const getAnalyticalStorageTtl = (): number => {
if (!isSynapseLinkEnabled()) { if (!isSynapseLinkEnabled()) {
return undefined; return undefined;
@@ -228,7 +226,7 @@ export const AddGlobalSecondaryIndexPanel = (props: AddGlobalSecondaryIndexPanel
}; };
} }
if (showFullTextSearchParameters()) { if (showFullTextSearch) {
indexingPolicy.fullTextIndexes = fullTextIndexes; indexingPolicy.fullTextIndexes = fullTextIndexes;
} }
@@ -387,7 +385,7 @@ export const AddGlobalSecondaryIndexPanel = (props: AddGlobalSecondaryIndexPanel
}} }}
/> />
)} )}
{showFullTextSearchParameters() && ( {showFullTextSearch && (
<FullTextSearchComponent <FullTextSearchComponent
{...{ fullTextPolicy, setFullTextPolicy, setFullTextIndexes, setFullTextPolicyValidated }} {...{ fullTextPolicy, setFullTextPolicy, setFullTextIndexes, setFullTextPolicyValidated }}
/> />

View File

@@ -172,6 +172,17 @@ exports[`AddGlobalSecondaryIndexPanel render default panel 1`] = `
} }
setEnableAnalyticalStore={[Function]} setEnableAnalyticalStore={[Function]}
/> />
<FullTextSearchComponent
fullTextPolicy={
{
"defaultLanguage": "en-US",
"fullTextPaths": [],
}
}
setFullTextIndexes={[Function]}
setFullTextPolicy={[Function]}
setFullTextPolicyValidated={[Function]}
/>
<AdvancedComponent <AdvancedComponent
setSubPartitionKeys={[Function]} setSubPartitionKeys={[Function]}
setUseHashV1={[Function]} setUseHashV1={[Function]}

View File

@@ -56,6 +56,14 @@
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
} }
} }
@media (max-width: 768px) {
.panelMainContent {
padding: 0 24px;
margin: 0;
overflow-x: auto;
}
}
} }
.panelHeader { .panelHeader {
@@ -113,17 +121,21 @@
.deleteCollectionFeedback { .deleteCollectionFeedback {
margin-top: 12px; margin-top: 12px;
} }
.addRemoveIcon { .addRemoveIcon {
margin-left: 4px !important; margin-left: 4px !important;
} }
.addRemoveIconLabel { .addRemoveIconLabel {
margin-top: 28px; margin-top: 28px;
margin-left: 4px !important; margin-left: 4px !important;
} }
.addRemoveIcon [alt="editEntity"]:focus, .addRemoveIcon [alt="editEntity"]:focus,
.addRemoveIconLabel [alt="editEntity"]:focus { .addRemoveIconLabel [alt="editEntity"]:focus {
border: 1px dashed #605e5c; border: 1px dashed #605e5c;
} }
.addNewParamStyle { .addNewParamStyle {
margin-top: 5px; margin-top: 5px;
margin-left: 5px !important; margin-left: 5px !important;
@@ -133,50 +145,63 @@
.panelGroupSpacing> :not(:last-child) { .panelGroupSpacing> :not(:last-child) {
margin-bottom: @DefaultSpace; margin-bottom: @DefaultSpace;
} }
.fileUpload { .fileUpload {
display: none !important; display: none !important;
} }
.customFileUpload { .customFileUpload {
padding: 25px 0px 0px 10px; padding: 25px 0px 0px 10px;
cursor: pointer; cursor: pointer;
display: flex; display: flex;
} }
.fileIcon { .fileIcon {
align-self: center; align-self: center;
} }
.panelAddIconLabel { .panelAddIconLabel {
font-size: 20px; font-size: 20px;
width: 20px; width: 20px;
margin: 30px 0 0 10px; margin: 30px 0 0 10px;
cursor: default; cursor: default;
} }
.panelAddIcon { .panelAddIcon {
font-size: 20px; font-size: 20px;
width: 20px; width: 20px;
margin: 30px 0 0 10px; margin: 30px 0 0 10px;
cursor: default; cursor: default;
} }
.removeIcon { .removeIcon {
color: @InfoIconColor; color: @InfoIconColor;
} }
.backImageIcon { .backImageIcon {
margin-top: 8px; margin-top: 8px;
} }
[alt="back"]:focus { [alt="back"]:focus {
border: 1px solid #605e5c; border: 1px solid #605e5c;
} }
.addEntityDatePicker { .addEntityDatePicker {
max-width: 145px; max-width: 145px;
} }
.addEntityTextField { .addEntityTextField {
width: 237px; width: 237px;
} }
.addButtonEntiy { .addButtonEntiy {
width: 25%; width: 25%;
} }
.column-select-view { .column-select-view {
margin: 20px 0px 0px 0px; margin: 20px 0px 0px 0px;
} }
.panelSeparator::before { .panelSeparator::before {
background-color: #edebe9; background-color: #edebe9;
} }

View File

@@ -180,6 +180,11 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
? LocalStorageUtility.getEntryNumber(StorageKey.MaxWaitTimeInSeconds) ? LocalStorageUtility.getEntryNumber(StorageKey.MaxWaitTimeInSeconds)
: Constants.Queries.DefaultMaxWaitTimeInSeconds, : Constants.Queries.DefaultMaxWaitTimeInSeconds,
); );
const [queryControlEnabled, setQueryControlEnabled] = useState<boolean>(
LocalStorageUtility.hasItem(StorageKey.QueryControlEnabled)
? LocalStorageUtility.getEntryString(StorageKey.QueryControlEnabled) === "true"
: false,
);
const [maxDegreeOfParallelism, setMaxDegreeOfParallelism] = useState<number>( const [maxDegreeOfParallelism, setMaxDegreeOfParallelism] = useState<number>(
LocalStorageUtility.hasItem(StorageKey.MaxDegreeOfParellism) LocalStorageUtility.hasItem(StorageKey.MaxDegreeOfParellism)
? LocalStorageUtility.getEntryNumber(StorageKey.MaxDegreeOfParellism) ? LocalStorageUtility.getEntryNumber(StorageKey.MaxDegreeOfParellism)
@@ -204,6 +209,7 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
!isEmulator; !isEmulator;
const shouldShowGraphAutoVizOption = userContext.apiType === "Gremlin" && !isEmulator; const shouldShowGraphAutoVizOption = userContext.apiType === "Gremlin" && !isEmulator;
const shouldShowCrossPartitionOption = userContext.apiType !== "Gremlin" && !isEmulator; const shouldShowCrossPartitionOption = userContext.apiType !== "Gremlin" && !isEmulator;
const shouldShowEnhancedQueryControl = userContext.apiType === "SQL";
const shouldShowParallelismOption = userContext.apiType !== "Gremlin" && !isEmulator; const shouldShowParallelismOption = userContext.apiType !== "Gremlin" && !isEmulator;
const showEnableEntraIdRbac = const showEnableEntraIdRbac =
isDataplaneRbacSupported(userContext.apiType) && isDataplaneRbacSupported(userContext.apiType) &&
@@ -381,6 +387,7 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
LocalStorageUtility.setEntryNumber(StorageKey.MaxWaitTimeInSeconds, MaxWaitTimeInSeconds); LocalStorageUtility.setEntryNumber(StorageKey.MaxWaitTimeInSeconds, MaxWaitTimeInSeconds);
LocalStorageUtility.setEntryString(StorageKey.ContainerPaginationEnabled, containerPaginationEnabled.toString()); LocalStorageUtility.setEntryString(StorageKey.ContainerPaginationEnabled, containerPaginationEnabled.toString());
LocalStorageUtility.setEntryString(StorageKey.IsCrossPartitionQueryEnabled, crossPartitionQueryEnabled.toString()); LocalStorageUtility.setEntryString(StorageKey.IsCrossPartitionQueryEnabled, crossPartitionQueryEnabled.toString());
LocalStorageUtility.setEntryString(StorageKey.QueryControlEnabled, queryControlEnabled.toString());
LocalStorageUtility.setEntryNumber(StorageKey.MaxDegreeOfParellism, maxDegreeOfParallelism); LocalStorageUtility.setEntryNumber(StorageKey.MaxDegreeOfParellism, maxDegreeOfParallelism);
LocalStorageUtility.setEntryString(StorageKey.PriorityLevel, priorityLevel.toString()); LocalStorageUtility.setEntryString(StorageKey.PriorityLevel, priorityLevel.toString());
LocalStorageUtility.setEntryString(StorageKey.CopilotSampleDBEnabled, copilotSampleDBEnabled.toString()); LocalStorageUtility.setEntryString(StorageKey.CopilotSampleDBEnabled, copilotSampleDBEnabled.toString());
@@ -410,6 +417,7 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
`Updated items per page setting to ${LocalStorageUtility.getEntryNumber(StorageKey.ActualItemPerPage)}`, `Updated items per page setting to ${LocalStorageUtility.getEntryNumber(StorageKey.ActualItemPerPage)}`,
); );
logConsoleInfo(`${crossPartitionQueryEnabled ? "Enabled" : "Disabled"} cross-partition query feed option`); logConsoleInfo(`${crossPartitionQueryEnabled ? "Enabled" : "Disabled"} cross-partition query feed option`);
logConsoleInfo(`${queryControlEnabled ? "Enabled" : "Disabled"} query control option`);
logConsoleInfo( logConsoleInfo(
`Updated the max degree of parallelism query feed option to ${LocalStorageUtility.getEntryNumber( `Updated the max degree of parallelism query feed option to ${LocalStorageUtility.getEntryNumber(
StorageKey.MaxDegreeOfParellism, StorageKey.MaxDegreeOfParellism,
@@ -425,9 +433,6 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
); );
} }
logConsoleInfo(
`Updated query setting to ${LocalStorageUtility.getEntryString(StorageKey.SetPartitionKeyUndefined)}`,
);
refreshExplorer && (await explorer.refreshExplorer()); refreshExplorer && (await explorer.refreshExplorer());
closeSidePanel(); closeSidePanel();
}; };
@@ -760,7 +765,6 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
)} )}
</AccordionPanel> </AccordionPanel>
</AccordionItem> </AccordionItem>
<AccordionItem value="5"> <AccordionItem value="5">
<AccordionHeader> <AccordionHeader>
<div className={styles.header}>RU Limit</div> <div className={styles.header}>RU Limit</div>
@@ -943,6 +947,38 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
</AccordionPanel> </AccordionPanel>
</AccordionItem> </AccordionItem>
)} )}
{shouldShowEnhancedQueryControl && (
<AccordionItem value="10">
<AccordionHeader>
<div className={styles.header}>Enhanced query control</div>
</AccordionHeader>
<AccordionPanel>
<div className={styles.settingsSectionContainer}>
<div className={styles.settingsSectionDescription}>
Query up to the max degree of parallelism.
<a
href="https://learn.microsoft.com/en-us/azure/cosmos-db/nosql/performance-tips-query-sdk?tabs=v3&pivots=programming-language-nodejs#enhanced-query-control"
target="_blank"
rel="noopener noreferrer"
>
{" "}
Learn more{" "}
</a>
</div>
<Checkbox
styles={{
label: { padding: 0 },
}}
className="padding"
ariaLabel="EnableQueryControl"
checked={queryControlEnabled}
onChange={() => setQueryControlEnabled(!queryControlEnabled)}
label="Enable query control"
/>
</div>
</AccordionPanel>
</AccordionItem>
)}
{shouldShowParallelismOption && ( {shouldShowParallelismOption && (
<AccordionItem value="10"> <AccordionItem value="10">
<AccordionHeader> <AccordionHeader>

View File

@@ -495,6 +495,51 @@ exports[`Settings Pane should render Default properly 1`] = `
</div> </div>
</AccordionPanel> </AccordionPanel>
</AccordionItem> </AccordionItem>
<AccordionItem
value="10"
>
<AccordionHeader>
<div
className="___15c001r_0000000 fq02s40"
>
Enhanced query control
</div>
</AccordionHeader>
<AccordionPanel>
<div
className="___1dfa554_0000000 fo7qwa0"
>
<div
className="___10gar1i_0000000 f1fow5ox f1ugzwwg"
>
Query up to the max degree of parallelism.
<a
href="https://learn.microsoft.com/en-us/azure/cosmos-db/nosql/performance-tips-query-sdk?tabs=v3&pivots=programming-language-nodejs#enhanced-query-control"
rel="noopener noreferrer"
target="_blank"
>
Learn more
</a>
</div>
<StyledCheckboxBase
ariaLabel="EnableQueryControl"
checked={false}
className="padding"
label="Enable query control"
onChange={[Function]}
styles={
{
"label": {
"padding": 0,
},
}
}
/>
</div>
</AccordionPanel>
</AccordionItem>
<AccordionItem <AccordionItem
value="10" value="10"
> >

View File

@@ -48,7 +48,11 @@ const classNames = mergeStyleSets({
warning: [{ color: theme.semanticColors.warningIcon }, iconClass], warning: [{ color: theme.semanticColors.warningIcon }, iconClass],
}); });
export const UploadItemsPane: FunctionComponent = () => { export type UploadItemsPaneProps = {
onUpload?: (data: UploadDetailsRecord[]) => void;
};
export const UploadItemsPane: FunctionComponent<UploadItemsPaneProps> = ({ onUpload }) => {
const [files, setFiles] = useState<FileList>(); const [files, setFiles] = useState<FileList>();
const [uploadFileData, setUploadFileData] = useState<UploadDetailsRecord[]>([]); const [uploadFileData, setUploadFileData] = useState<UploadDetailsRecord[]>([]);
const [formError, setFormError] = useState<string>(""); const [formError, setFormError] = useState<string>("");
@@ -71,6 +75,8 @@ export const UploadItemsPane: FunctionComponent = () => {
(uploadDetails) => { (uploadDetails) => {
setUploadFileData(uploadDetails.data); setUploadFileData(uploadDetails.data);
setFiles(undefined); setFiles(undefined);
// Emit the upload details to the parent component
onUpload && onUpload(uploadDetails.data);
}, },
(error: Error) => { (error: Error) => {
const errorMessage = getErrorMessage(error); const errorMessage = getErrorMessage(error);

View File

@@ -30,6 +30,21 @@
margin: 0px auto; margin: 0px auto;
text-align: center; text-align: center;
} }
.splashStackContainer {
.splashStackRow {
display: flex;
gap: 0 16px;
@media (max-width: 768px) {
flex-direction: column;
gap: 16px 0;
}
}
@media (max-width: 768px) {
width: 85% !important;
}
}
.mainButtonsContainer { .mainButtonsContainer {
.flex-display(); .flex-display();

View File

@@ -126,8 +126,12 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
useDatabases.getState().sampleDataResourceTokenCollection useDatabases.getState().sampleDataResourceTokenCollection
) { ) {
return ( return (
<Stack style={{ width: "66%", cursor: "pointer", margin: "40px auto" }} tokens={{ childrenGap: 16 }}> <Stack
<Stack horizontal tokens={{ childrenGap: 16 }}> className="splashStackContainer"
style={{ width: "66%", cursor: "pointer", margin: "40px auto" }}
tokens={{ childrenGap: 16 }}
>
<Stack className="splashStackRow" horizontal>
<SplashScreenButton <SplashScreenButton
imgSrc={QuickStartIcon} imgSrc={QuickStartIcon}
title={"Launch quick start"} title={"Launch quick start"}
@@ -147,7 +151,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
}} }}
/> />
</Stack> </Stack>
<Stack horizontal tokens={{ childrenGap: 16 }}> <Stack className="splashStackRow" horizontal>
{useQueryCopilot.getState().copilotEnabled && ( {useQueryCopilot.getState().copilotEnabled && (
<SplashScreenButton <SplashScreenButton
imgSrc={CopilotIcon} imgSrc={CopilotIcon}

View File

@@ -44,32 +44,26 @@ export const startCloudShellTerminal = async (terminal: Terminal, shellType: Ter
resolvedRegion = determineCloudShellRegion(); resolvedRegion = determineCloudShellRegion();
resolvedRegion = determineCloudShellRegion();
terminal.writeln(formatWarningMessage("⚠️ IMPORTANT: Azure Cloud Shell Region Notice ⚠️")); terminal.writeln(formatWarningMessage("⚠️ IMPORTANT: Azure Cloud Shell Region Notice ⚠️"));
terminal.writeln( terminal.writeln(
formatInfoMessage( formatInfoMessage(
"The Cloud Shell environment will operate in a region that may differ from your database's region.", "The Cloud Shell environment will operate in a region that may differ from your database's region.",
), ),
); );
terminal.writeln(formatInfoMessage("This has two potential implications:")); terminal.writeln(formatInfoMessage("By using this feature, you acknowledge and agree to the following"));
terminal.writeln(formatInfoMessage("1. Performance Impact:")); terminal.writeln(formatInfoMessage("1. Performance Impact:"));
terminal.writeln( terminal.writeln(
formatInfoMessage(" Commands may experience higher latency due to geographic distance between regions."), formatInfoMessage(" Commands may experience higher latency due to geographic distance between regions."),
); );
terminal.writeln(formatInfoMessage("2. Data Compliance Considerations:")); terminal.writeln(formatInfoMessage("2. Data Transfers:"));
terminal.writeln( terminal.writeln(
formatInfoMessage( formatInfoMessage(
" Data processed through this shell could temporarily reside in a different geographic region,", " Data processed through this Cloud Shell service can be processed outside of your tenant's geographical region, compliance boundary or national cloud instance.",
), ),
); );
terminal.writeln(
formatInfoMessage(" which may affect compliance with data residency requirements or regulations specific"),
);
terminal.writeln(formatInfoMessage(" to your organization."));
terminal.writeln(""); terminal.writeln("");
terminal.writeln("\x1b[94mFor more information on Azure Cosmos DB data governance and compliance, please visit:"); terminal.writeln("\x1b[94mFor more information on Azure Cosmos DB data residency, please visit:");
terminal.writeln("\x1b[94mhttps://learn.microsoft.com/en-us/azure/cosmos-db/data-residency\x1b[0m"); terminal.writeln("\x1b[94mhttps://learn.microsoft.com/en-us/azure/cosmos-db/data-residency\x1b[0m");
// Ask for user consent for region // Ask for user consent for region

View File

@@ -258,14 +258,7 @@ Key limitations:
### Data Residency ### Data Residency
Data residency requirements may not be fully satisfied when using CloudShell due to limited regional availability. CloudShell services are currently available in the following regions: Data residency requirements may not be fully satisfied when using CloudShell due to limited regional availability.
| Geography | Regions |
|-----------|---------|
| Americas | East US, West US 2, South Central US, West Central US |
| Europe | West Europe, North Europe |
| Asia Pacific | Southeast Asia, Japan East, Australia East |
| Middle East | UAE North |
**Note:** For up-to-date supported regions, refer to the region configuration in: **Note:** For up-to-date supported regions, refer to the region configuration in:
`src/Explorer/CloudShell/Configuration/RegionConfig.ts` `src/Explorer/CloudShell/Configuration/RegionConfig.ts`

View File

@@ -1,4 +1,4 @@
import { AbstractShellHandler, DISABLE_HISTORY, START_MARKER, EXIT_COMMAND } from "./AbstractShellHandler"; import { AbstractShellHandler, DISABLE_HISTORY, EXIT_COMMAND, START_MARKER } from "./AbstractShellHandler";
// Mock implementation for testing // Mock implementation for testing
class MockShellHandler extends AbstractShellHandler { class MockShellHandler extends AbstractShellHandler {
@@ -18,8 +18,8 @@ class MockShellHandler extends AbstractShellHandler {
return "mock-endpoint"; return "mock-endpoint";
} }
getTerminalSuppressedData(): string { getTerminalSuppressedData(): string[] {
return "suppressed-data"; return ["suppressed-data"];
} }
} }
@@ -90,7 +90,7 @@ describe("AbstractShellHandler", () => {
}); });
it("should return the terminal suppressed data", () => { it("should return the terminal suppressed data", () => {
expect(shellHandler.getTerminalSuppressedData()).toBe("suppressed-data"); expect(shellHandler.getTerminalSuppressedData()).toEqual(["suppressed-data"]);
}); });
}); });
}); });

View File

@@ -13,7 +13,13 @@ export const DISABLE_HISTORY = `set +o history`;
* Command that displays an error message and exits the shell session. * Command that displays an error message and exits the shell session.
* Used when shell initialization or connection fails. * Used when shell initialization or connection fails.
*/ */
export const EXIT_COMMAND = ` printf "\\033[1;31mSession ended. Please close this tab and initiate a new shell session if needed.\\033[0m\\n" && exit`; export const EXIT_COMMAND = ` printf "\\033[1;31mSession ended. Please close this tab and initiate a new shell session if needed.\\033[0m\\n" && disown -a && exit`;
/**
* This command runs mongosh in no-database and quiet mode,
* and evaluates the `disableTelemetry()` function to turn off telemetry collection.
*/
export const DISABLE_TELEMETRY_COMMAND = `mongosh --nodb --quiet --eval "disableTelemetry()"`;
/** /**
* Abstract class that defines the interface for shell-specific handlers * Abstract class that defines the interface for shell-specific handlers
@@ -31,7 +37,8 @@ export abstract class AbstractShellHandler {
abstract getShellName(): string; abstract getShellName(): string;
abstract getSetUpCommands(): string[]; abstract getSetUpCommands(): string[];
abstract getConnectionCommand(): string; abstract getConnectionCommand(): string;
abstract getTerminalSuppressedData(): string; abstract getTerminalSuppressedData(): string[];
updateTerminalData?(data: string): string;
/** /**
* Constructs the complete initialization command sequence for the shell. * Constructs the complete initialization command sequence for the shell.
@@ -77,7 +84,7 @@ export abstract class AbstractShellHandler {
* is not already present in the environment. * is not already present in the environment.
*/ */
protected mongoShellSetupCommands(): string[] { protected mongoShellSetupCommands(): string[] {
const PACKAGE_VERSION: string = "2.5.0"; const PACKAGE_VERSION: string = "2.5.5";
return [ return [
"if ! command -v mongosh &> /dev/null; then echo '⚠️ mongosh not found. Installing...'; fi", "if ! command -v mongosh &> /dev/null; then echo '⚠️ mongosh not found. Installing...'; fi",
`if ! command -v mongosh &> /dev/null; then curl -LO https://downloads.mongodb.com/compass/mongosh-${PACKAGE_VERSION}-linux-x64.tgz; fi`, `if ! command -v mongosh &> /dev/null; then curl -LO https://downloads.mongodb.com/compass/mongosh-${PACKAGE_VERSION}-linux-x64.tgz; fi`,
@@ -85,7 +92,7 @@ export abstract class AbstractShellHandler {
`if ! command -v mongosh &> /dev/null; then mkdir -p ~/mongosh/bin && mv mongosh-${PACKAGE_VERSION}-linux-x64/bin/mongosh ~/mongosh/bin/ && chmod +x ~/mongosh/bin/mongosh; fi`, `if ! command -v mongosh &> /dev/null; then mkdir -p ~/mongosh/bin && mv mongosh-${PACKAGE_VERSION}-linux-x64/bin/mongosh ~/mongosh/bin/ && chmod +x ~/mongosh/bin/mongosh; fi`,
`if ! command -v mongosh &> /dev/null; then rm -rf mongosh-${PACKAGE_VERSION}-linux-x64 mongosh-${PACKAGE_VERSION}-linux-x64.tgz; fi`, `if ! command -v mongosh &> /dev/null; then rm -rf mongosh-${PACKAGE_VERSION}-linux-x64 mongosh-${PACKAGE_VERSION}-linux-x64.tgz; fi`,
"if ! command -v mongosh &> /dev/null; then echo 'export PATH=$HOME/mongosh/bin:$PATH' >> ~/.bashrc; fi", "if ! command -v mongosh &> /dev/null; then echo 'export PATH=$HOME/mongosh/bin:$PATH' >> ~/.bashrc; fi",
"source ~/.bashrc", "if ! command -v mongosh &> /dev/null; then source ~/.bashrc; fi",
]; ];
} }
} }

View File

@@ -94,7 +94,7 @@ describe("CassandraShellHandler", () => {
}); });
test("should return the correct terminal suppressed data", () => { test("should return the correct terminal suppressed data", () => {
expect(handler.getTerminalSuppressedData()).toBe(""); expect(handler.getTerminalSuppressedData()).toEqual([""]);
}); });
test("should include the correct package version in setup commands", () => { test("should include the correct package version in setup commands", () => {

View File

@@ -41,7 +41,7 @@ export class CassandraShellHandler extends AbstractShellHandler {
return `cqlsh ${getHostFromUrl(this._endpoint)} 10350 -u ${dbName} -p ${this._key} --ssl`; return `cqlsh ${getHostFromUrl(this._endpoint)} 10350 -u ${dbName} -p ${this._key} --ssl`;
} }
public getTerminalSuppressedData(): string { public getTerminalSuppressedData(): string[] {
return ""; return [""];
} }
} }

View File

@@ -69,7 +69,7 @@ describe("MongoShellHandler", () => {
expect(Array.isArray(commands)).toBe(true); expect(Array.isArray(commands)).toBe(true);
expect(commands.length).toBe(7); expect(commands.length).toBe(7);
expect(commands[1]).toContain("mongosh-2.5.0-linux-x64.tgz"); expect(commands[1]).toContain("mongosh-2.5.5-linux-x64.tgz");
}); });
}); });
@@ -91,7 +91,7 @@ describe("MongoShellHandler", () => {
const command = mongoShellHandler.getConnectionCommand(); const command = mongoShellHandler.getConnectionCommand();
expect(command).toBe( expect(command).toBe(
"mongosh mongodb://test-mongo.documents.azure.com:10255?appName=CosmosExplorerTerminal --username test-account --password test-key --tls --tlsAllowInvalidCertificates", 'mongosh --nodb --quiet --eval "disableTelemetry()" && mongosh mongodb://test-mongo.documents.azure.com:10255?appName=CosmosExplorerTerminal --username test-account --password test-key --tls --tlsAllowInvalidCertificates',
); );
expect(CommonUtils.getHostFromUrl).toHaveBeenCalledWith("https://test-mongo.documents.azure.com:443/"); expect(CommonUtils.getHostFromUrl).toHaveBeenCalledWith("https://test-mongo.documents.azure.com:443/");
@@ -124,7 +124,7 @@ describe("MongoShellHandler", () => {
describe("getTerminalSuppressedData", () => { describe("getTerminalSuppressedData", () => {
it("should return the correct warning message", () => { it("should return the correct warning message", () => {
expect(mongoShellHandler.getTerminalSuppressedData()).toBe("Warning: Non-Genuine MongoDB Detected"); expect(mongoShellHandler.getTerminalSuppressedData()).toEqual(["Warning: Non-Genuine MongoDB Detected"]);
}); });
}); });
}); });

View File

@@ -1,6 +1,6 @@
import { userContext } from "../../../../UserContext"; import { userContext } from "../../../../UserContext";
import { getHostFromUrl } from "../Utils/CommonUtils"; import { getHostFromUrl } from "../Utils/CommonUtils";
import { AbstractShellHandler } from "./AbstractShellHandler"; import { AbstractShellHandler, DISABLE_TELEMETRY_COMMAND } from "./AbstractShellHandler";
export class MongoShellHandler extends AbstractShellHandler { export class MongoShellHandler extends AbstractShellHandler {
private _key: string; private _key: string;
@@ -29,6 +29,8 @@ export class MongoShellHandler extends AbstractShellHandler {
return "echo 'Database name not found.'"; return "echo 'Database name not found.'";
} }
return ( return (
DISABLE_TELEMETRY_COMMAND +
" && " +
"mongosh mongodb://" + "mongosh mongodb://" +
getHostFromUrl(this._endpoint) + getHostFromUrl(this._endpoint) +
":10255?appName=" + ":10255?appName=" +
@@ -41,7 +43,7 @@ export class MongoShellHandler extends AbstractShellHandler {
); );
} }
public getTerminalSuppressedData(): string { public getTerminalSuppressedData(): string[] {
return "Warning: Non-Genuine MongoDB Detected"; return ["Warning: Non-Genuine MongoDB Detected"];
} }
} }

View File

@@ -58,7 +58,7 @@ describe("PostgresShellHandler", () => {
}); });
it("should return empty string for terminal suppressed data", () => { it("should return empty string for terminal suppressed data", () => {
expect(postgresShellHandler.getTerminalSuppressedData()).toBe(""); expect(postgresShellHandler.getTerminalSuppressedData()).toEqual([""]);
}); });
}); });
}); });

View File

@@ -57,7 +57,7 @@ export class PostgresShellHandler extends AbstractShellHandler {
return `psql -h "${this._endpoint}" -p 5432 -d "citus" -U "${loginName}" --set=sslmode=require --set=application_name=${this.APP_NAME}`; return `psql -h "${this._endpoint}" -p 5432 -d "citus" -U "${loginName}" --set=sslmode=require --set=application_name=${this.APP_NAME}`;
} }
public getTerminalSuppressedData(): string { public getTerminalSuppressedData(): string[] {
return ""; return [""];
} }
} }

View File

@@ -45,7 +45,7 @@ describe("VCoreMongoShellHandler", () => {
expect(Array.isArray(commands)).toBe(true); expect(Array.isArray(commands)).toBe(true);
expect(commands.length).toBe(7); expect(commands.length).toBe(7);
expect(commands[1]).toContain("mongosh-2.5.0-linux-x64.tgz"); expect(commands[1]).toContain("mongosh-2.5.5-linux-x64.tgz");
expect(commands[0]).toContain("mongosh not found"); expect(commands[0]).toContain("mongosh not found");
}); });
@@ -57,7 +57,10 @@ describe("VCoreMongoShellHandler", () => {
}); });
it("should return the correct terminal suppressed data", () => { it("should return the correct terminal suppressed data", () => {
expect(vcoreMongoShellHandler.getTerminalSuppressedData()).toBe("Warning: Non-Genuine MongoDB Detected"); expect(vcoreMongoShellHandler.getTerminalSuppressedData()).toEqual([
"Warning: Non-Genuine MongoDB Detected",
"Telemetry is now disabled.",
]);
}); });
}); });
}); });

View File

@@ -1,8 +1,13 @@
import { userContext } from "../../../../UserContext"; import { userContext } from "../../../../UserContext";
import { AbstractShellHandler } from "./AbstractShellHandler"; import { AbstractShellHandler, DISABLE_TELEMETRY_COMMAND } from "./AbstractShellHandler";
export class VCoreMongoShellHandler extends AbstractShellHandler { export class VCoreMongoShellHandler extends AbstractShellHandler {
private _endpoint: string | undefined; private _endpoint: string | undefined;
private _textFilterRules: string[] = [
"For mongosh info see: https://www.mongodb.com/docs/mongodb-shell/",
"disableTelemetry() command",
"https://www.mongodb.com/legal/privacy-policy",
];
constructor() { constructor() {
super(); super();
@@ -23,10 +28,22 @@ export class VCoreMongoShellHandler extends AbstractShellHandler {
} }
const userName = userContext.vcoreMongoConnectionParams.adminLogin; const userName = userContext.vcoreMongoConnectionParams.adminLogin;
return `mongosh "mongodb+srv://${userName}:@${this._endpoint}/?authMechanism=SCRAM-SHA-256&retrywrites=false&maxIdleTimeMS=120000&appName=${this.APP_NAME}"`;
const connectionUri = `mongodb+srv://${userName}:@${this._endpoint}/?authMechanism=SCRAM-SHA-256&retrywrites=false&maxIdleTimeMS=120000&appName=${this.APP_NAME}`;
return `${DISABLE_TELEMETRY_COMMAND} && mongosh "${connectionUri}"`;
} }
public getTerminalSuppressedData(): string { public getTerminalSuppressedData(): string[] {
return "Warning: Non-Genuine MongoDB Detected"; return ["Warning: Non-Genuine MongoDB Detected", "Telemetry is now disabled."];
}
updateTerminalData(content: string): string {
const updatedContent = content
.split("\n")
.filter((line) => !this._textFilterRules.some((part) => line.includes(part)))
.filter((line, idx, arr) => (arr.length > 3 && idx <= arr.length - 3 ? !["", "\r"].includes(line) : true)) // Filter out empty lines and carriage returns, but keep the last 3 lines if they exist
.join("\n");
return updatedContent;
} }
} }

View File

@@ -135,11 +135,13 @@ export class AttachAddon implements ITerminalAddon {
} }
if (this._allowTerminalWrite) { if (this._allowTerminalWrite) {
const updatedData = this._shellHandler?.updateTerminalData(data) ?? data;
const suppressedData = this._shellHandler?.getTerminalSuppressedData(); const suppressedData = this._shellHandler?.getTerminalSuppressedData();
const hasSuppressedData = suppressedData && suppressedData.length > 0;
if (!hasSuppressedData || !data.includes(suppressedData)) { const shouldNotWrite = suppressedData.filter(Boolean).some((item) => updatedData.includes(item));
terminal.write(data);
if (!shouldNotWrite) {
terminal.write(updatedData);
} }
} }

View File

@@ -26,7 +26,6 @@ import { useDialog } from "Explorer/Controls/Dialog";
import { EditorReact } from "Explorer/Controls/Editor/EditorReact"; import { EditorReact } from "Explorer/Controls/Editor/EditorReact";
import { InputDataList, InputDatalistDropdownOptionSection } from "Explorer/Controls/InputDataList/InputDataList"; import { InputDataList, InputDatalistDropdownOptionSection } from "Explorer/Controls/InputDataList/InputDataList";
import { ProgressModalDialog } from "Explorer/Controls/ProgressModalDialog"; import { ProgressModalDialog } from "Explorer/Controls/ProgressModalDialog";
import Explorer from "Explorer/Explorer";
import { useCommandBar } from "Explorer/Menus/CommandBar/CommandBarComponentAdapter"; import { useCommandBar } from "Explorer/Menus/CommandBar/CommandBarComponentAdapter";
import { querySampleDocuments, readSampleDocument } from "Explorer/QueryCopilot/QueryCopilotUtilities"; import { querySampleDocuments, readSampleDocument } from "Explorer/QueryCopilot/QueryCopilotUtilities";
import { import {
@@ -64,7 +63,7 @@ import * as Logger from "../../../Common/Logger";
import * as MongoProxyClient from "../../../Common/MongoProxyClient"; import * as MongoProxyClient from "../../../Common/MongoProxyClient";
import * as DataModels from "../../../Contracts/DataModels"; import * as DataModels from "../../../Contracts/DataModels";
import * as ViewModels from "../../../Contracts/ViewModels"; import * as ViewModels from "../../../Contracts/ViewModels";
import { CollectionBase } from "../../../Contracts/ViewModels"; import { CollectionBase, UploadDetailsRecord } from "../../../Contracts/ViewModels";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor"; import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import * as QueryUtils from "../../../Utils/QueryUtils"; import * as QueryUtils from "../../../Utils/QueryUtils";
import { defaultQueryFields, extractPartitionKeyValues } from "../../../Utils/QueryUtils"; import { defaultQueryFields, extractPartitionKeyValues } from "../../../Utils/QueryUtils";
@@ -309,7 +308,6 @@ type UiKeyboardEvent = (e: KeyboardEvent | React.SyntheticEvent<Element, Event>)
// Export to expose to unit tests // Export to expose to unit tests
export type ButtonsDependencies = { export type ButtonsDependencies = {
_collection: ViewModels.CollectionBase;
selectedRows: Set<TableRowId>; selectedRows: Set<TableRowId>;
editorState: ViewModels.DocumentExplorerState; editorState: ViewModels.DocumentExplorerState;
isPreferredApiMongoDB: boolean; isPreferredApiMongoDB: boolean;
@@ -320,26 +318,7 @@ export type ButtonsDependencies = {
onSaveExistingDocumentClick: UiKeyboardEvent; onSaveExistingDocumentClick: UiKeyboardEvent;
onRevertExistingDocumentClick: UiKeyboardEvent; onRevertExistingDocumentClick: UiKeyboardEvent;
onDeleteExistingDocumentsClick: UiKeyboardEvent; onDeleteExistingDocumentsClick: UiKeyboardEvent;
}; onUploadDocumentsClick: UiKeyboardEvent;
const createUploadButton = (container: Explorer): CommandButtonComponentProps => {
const label = "Upload Item";
return {
id: UPLOAD_BUTTON_ID,
iconSrc: UploadIcon,
iconAlt: label,
onCommandClick: () => {
const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection();
selectedCollection && container.openUploadItemsPane();
},
commandButtonLabel: label,
ariaLabel: label,
hasPopup: true,
disabled:
useSelectedNode.getState().isDatabaseNodeOrNoneSelected() ||
!useClientWriteEnabled.getState().clientWriteEnabled ||
useSelectedNode.getState().isQueryCopilotCollectionSelected(),
};
}; };
// Export to expose to unit tests // Export to expose to unit tests
@@ -352,7 +331,6 @@ export const UPLOAD_BUTTON_ID = "uploadItemBtn";
// Export to expose in unit tests // Export to expose in unit tests
export const getTabsButtons = ({ export const getTabsButtons = ({
_collection,
selectedRows, selectedRows,
editorState, editorState,
isPreferredApiMongoDB, isPreferredApiMongoDB,
@@ -363,6 +341,7 @@ export const getTabsButtons = ({
onSaveExistingDocumentClick, onSaveExistingDocumentClick,
onRevertExistingDocumentClick, onRevertExistingDocumentClick,
onDeleteExistingDocumentsClick, onDeleteExistingDocumentsClick,
onUploadDocumentsClick,
}: ButtonsDependencies): CommandButtonComponentProps[] => { }: ButtonsDependencies): CommandButtonComponentProps[] => {
if (isFabric() && userContext.fabricContext?.isReadOnly) { if (isFabric() && userContext.fabricContext?.isReadOnly) {
// All the following buttons require write access // All the following buttons require write access
@@ -474,7 +453,20 @@ export const getTabsButtons = ({
} }
if (!isPreferredApiMongoDB) { if (!isPreferredApiMongoDB) {
buttons.push(createUploadButton(_collection.container)); const label = "Upload Item";
buttons.push({
id: UPLOAD_BUTTON_ID,
iconSrc: UploadIcon,
iconAlt: label,
onCommandClick: onUploadDocumentsClick,
commandButtonLabel: label,
ariaLabel: label,
hasPopup: true,
disabled:
useSelectedNode.getState().isDatabaseNodeOrNoneSelected() ||
!useClientWriteEnabled.getState().clientWriteEnabled ||
useSelectedNode.getState().isQueryCopilotCollectionSelected(),
});
} }
return buttons; return buttons;
@@ -679,6 +671,7 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
collection: CollectionBase; collection: CollectionBase;
}>(undefined); }>(undefined);
const [bulkDeleteMode, setBulkDeleteMode] = useState<"inProgress" | "completed" | "aborting" | "aborted">(undefined); const [bulkDeleteMode, setBulkDeleteMode] = useState<"inProgress" | "completed" | "aborting" | "aborted">(undefined);
const [abortController, setAbortController] = useState<AbortController | undefined>(undefined);
const setKeyboardActions = useKeyboardActionGroup(KeyboardActionGroup.ACTIVE_TAB); const setKeyboardActions = useKeyboardActionGroup(KeyboardActionGroup.ACTIVE_TAB);
@@ -706,13 +699,19 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
return; return;
} }
if ( if (bulkDeleteProcess.pendingIds.length === 0 && bulkDeleteProcess.throttledIds.length === 0) {
(bulkDeleteProcess.pendingIds.length === 0 && bulkDeleteProcess.throttledIds.length === 0) || // Successfully deleted all documents
bulkDeleteMode === "aborting"
) {
// Successfully deleted all documents or operation was aborted
bulkDeleteOperation.onCompleted(bulkDeleteProcess.successfulIds); bulkDeleteOperation.onCompleted(bulkDeleteProcess.successfulIds);
setBulkDeleteMode(bulkDeleteMode === "aborting" ? "aborted" : "completed"); setBulkDeleteMode("completed");
return;
}
if (bulkDeleteMode === "aborting") {
// Operation was aborted
abortController?.abort();
bulkDeleteOperation.onCompleted(bulkDeleteProcess.successfulIds);
setBulkDeleteMode("aborted");
setAbortController(undefined);
return; return;
} }
@@ -720,8 +719,10 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
const newPendingIds = bulkDeleteProcess.pendingIds.concat(bulkDeleteProcess.throttledIds); const newPendingIds = bulkDeleteProcess.pendingIds.concat(bulkDeleteProcess.throttledIds);
const timeout = bulkDeleteProcess.beforeExecuteMs || 0; const timeout = bulkDeleteProcess.beforeExecuteMs || 0;
const ac = new AbortController();
setAbortController(ac);
setTimeout(() => { setTimeout(() => {
deleteNoSqlDocuments(bulkDeleteOperation.collection, [...newPendingIds]) deleteNoSqlDocuments(bulkDeleteOperation.collection, [...newPendingIds], ac.signal)
.then((deleteResult) => { .then((deleteResult) => {
let retryAfterMilliseconds = 0; let retryAfterMilliseconds = 0;
const newSuccessful: DocumentId[] = []; const newSuccessful: DocumentId[] = [];
@@ -877,7 +878,6 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
} }
updateNavbarWithTabsButtons(isTabActive, { updateNavbarWithTabsButtons(isTabActive, {
_collection,
selectedRows, selectedRows,
editorState, editorState,
isPreferredApiMongoDB, isPreferredApiMongoDB,
@@ -888,6 +888,7 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
onSaveExistingDocumentClick, onSaveExistingDocumentClick,
onRevertExistingDocumentClick, onRevertExistingDocumentClick,
onDeleteExistingDocumentsClick, onDeleteExistingDocumentsClick,
onUploadDocumentsClick,
}); });
}, []); }, []);
@@ -1293,24 +1294,47 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
); );
}, [deleteDocuments, documentIds, isPreferredApiMongoDB, selectedRows]); }, [deleteDocuments, documentIds, isPreferredApiMongoDB, selectedRows]);
const onUploadDocumentsClick = useCallback((): void => {
if (!isPreferredApiMongoDB) {
const onSuccessUpload = (data: UploadDetailsRecord[]) => {
const addedIdsSet = new Set(
data
.reduce(
(result: ItemDefinition[], record) =>
result.concat(record.resources && record.resources.length ? record.resources : []),
[],
)
.map((document) => {
const partitionKeyValueArray: PartitionKey[] = extractPartitionKeyValues(
document,
partitionKey as PartitionKeyDefinition,
);
return newDocumentId(
document as ItemDefinition & Resource,
partitionKeyProperties,
partitionKeyValueArray as string[],
);
}),
);
const documents = new Set(documentIds);
addedIdsSet.forEach((item) => documents.add(item));
setDocumentIds(Array.from(documents));
setSelectedDocumentContent(undefined);
setClickedRowIndex(undefined);
setSelectedRows(new Set());
setEditorState(ViewModels.DocumentExplorerState.noDocumentSelected);
};
_collection.container.openUploadItemsPane(onSuccessUpload);
}
}, [_collection.container, documentIds, isPreferredApiMongoDB, newDocumentId, partitionKey, partitionKeyProperties]);
// If editor state changes, update the nav // If editor state changes, update the nav
useEffect( useEffect(
() => () =>
updateNavbarWithTabsButtons(isTabActive, { updateNavbarWithTabsButtons(isTabActive, {
_collection,
selectedRows,
editorState,
isPreferredApiMongoDB,
clientWriteEnabled,
onNewDocumentClick,
onSaveNewDocumentClick,
onRevertNewDocumentClick,
onSaveExistingDocumentClick,
onRevertExistingDocumentClick: onRevertExistingDocumentClick,
onDeleteExistingDocumentsClick: onDeleteExistingDocumentsClick,
}),
[
_collection,
selectedRows, selectedRows,
editorState, editorState,
isPreferredApiMongoDB, isPreferredApiMongoDB,
@@ -1321,6 +1345,20 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
onSaveExistingDocumentClick, onSaveExistingDocumentClick,
onRevertExistingDocumentClick, onRevertExistingDocumentClick,
onDeleteExistingDocumentsClick, onDeleteExistingDocumentsClick,
onUploadDocumentsClick,
}),
[
selectedRows,
editorState,
isPreferredApiMongoDB,
clientWriteEnabled,
onNewDocumentClick,
onSaveNewDocumentClick,
onRevertNewDocumentClick,
onSaveExistingDocumentClick,
onRevertExistingDocumentClick,
onDeleteExistingDocumentsClick,
onUploadDocumentsClick,
isTabActive, isTabActive,
], ],
); );

View File

@@ -45,7 +45,7 @@ export interface IGraphConfig {
interface GraphTabOptions extends ViewModels.TabOptions { interface GraphTabOptions extends ViewModels.TabOptions {
account: DatabaseAccount; account: DatabaseAccount;
masterKey: string; password: string;
collectionId: string; collectionId: string;
databaseId: string; databaseId: string;
collectionPartitionKeyProperty: string; collectionPartitionKeyProperty: string;
@@ -107,7 +107,7 @@ export default class GraphTab extends TabsBase {
graphBackendEndpoint: GraphTab.getGremlinEndpoint(options.account), graphBackendEndpoint: GraphTab.getGremlinEndpoint(options.account),
databaseId: options.databaseId, databaseId: options.databaseId,
collectionId: options.collectionId, collectionId: options.collectionId,
masterKey: options.masterKey, password: options.password,
onLoadStartKey: options.onLoadStartKey, onLoadStartKey: options.onLoadStartKey,
onLoadStartKeyChange: (onLoadStartKey: number): void => { onLoadStartKeyChange: (onLoadStartKey: number): void => {
if (onLoadStartKey === undefined) { if (onLoadStartKey === undefined) {

View File

@@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable no-console */ /* eslint-disable no-console */
import { FeedOptions, QueryOperationOptions } from "@azure/cosmos"; import { FeedOptions } from "@azure/cosmos";
import { AuthType } from "AuthType"; import { AuthType } from "AuthType";
import QueryError, { createMonacoErrorLocationResolver, createMonacoMarkersForQueryErrors } from "Common/QueryError"; import QueryError, { createMonacoErrorLocationResolver, createMonacoMarkersForQueryErrors } from "Common/QueryError";
import { SplitterDirection } from "Common/Splitter"; import { SplitterDirection } from "Common/Splitter";
@@ -19,7 +19,7 @@ import { CosmosFluentProvider } from "Explorer/Theme/ThemeUtil";
import { useSelectedNode } from "Explorer/useSelectedNode"; import { useSelectedNode } from "Explorer/useSelectedNode";
import { KeyboardAction } from "KeyboardShortcuts"; import { KeyboardAction } from "KeyboardShortcuts";
import { QueryConstants } from "Shared/Constants"; import { QueryConstants } from "Shared/Constants";
import { LocalStorageUtility, StorageKey, getRUThreshold, ruThresholdEnabled } from "Shared/StorageUtility"; import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
import { Action } from "Shared/Telemetry/TelemetryConstants"; import { Action } from "Shared/Telemetry/TelemetryConstants";
import { Allotment } from "allotment"; import { Allotment } from "allotment";
import { useClientWriteEnabled } from "hooks/useClientWriteEnabled"; import { useClientWriteEnabled } from "hooks/useClientWriteEnabled";
@@ -369,22 +369,9 @@ class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps,
this.setState({ this.setState({
isExecutionError: false, isExecutionError: false,
}); });
this.props.tabsBaseInstance.isExecutionWarning(false);
let queryOperationOptions: QueryOperationOptions;
if (userContext.apiType === "SQL" && ruThresholdEnabled()) {
const ruThreshold: number = getRUThreshold();
queryOperationOptions = {
ruCapPerOperation: ruThreshold,
} as QueryOperationOptions;
}
const queryDocuments = async (firstItemIndex: number) => const queryDocuments = async (firstItemIndex: number) =>
await queryDocumentsPage( await queryDocumentsPage(this.props.collection && this.props.collection.id(), this._iterator, firstItemIndex);
this.props.collection && this.props.collection.id(),
this._iterator,
firstItemIndex,
queryOperationOptions,
);
this.props.tabsBaseInstance.isExecuting(true); this.props.tabsBaseInstance.isExecuting(true);
this.setState({ this.setState({
isExecuting: true, isExecuting: true,
@@ -424,6 +411,9 @@ class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps,
firstItemIndex, firstItemIndex,
queryDocuments, queryDocuments,
); );
if (queryResults.ruThresholdExceeded) {
this.props.tabsBaseInstance.isExecutionWarning(true);
}
this.setState({ queryResults, errors: [] }); this.setState({ queryResults, errors: [] });
} catch (error) { } catch (error) {
this.props.tabsBaseInstance.isExecutionError(true); this.props.tabsBaseInstance.isExecutionError(true);

View File

@@ -32,6 +32,7 @@ enum ResultsTabs {
const ResultsTab: React.FC<ResultsViewProps> = ({ queryResults, isMongoDB, executeQueryDocumentsPage }) => { const ResultsTab: React.FC<ResultsViewProps> = ({ queryResults, isMongoDB, executeQueryDocumentsPage }) => {
const styles = useQueryTabStyles(); const styles = useQueryTabStyles();
/* eslint-disable react/prop-types */
const queryResultsString = queryResults const queryResultsString = queryResults
? isMongoDB ? isMongoDB
? MongoUtility.tojson(queryResults.documents, undefined, false) ? MongoUtility.tojson(queryResults.documents, undefined, false)
@@ -47,6 +48,172 @@ const ResultsTab: React.FC<ResultsViewProps> = ({ queryResults, isMongoDB, execu
await executeQueryDocumentsPage(firstItemIndex + itemCount - 1); await executeQueryDocumentsPage(firstItemIndex + itemCount - 1);
}; };
const ExportResults: React.FC = () => {
const [showDropdown, setShowDropdown] = useState(false);
const dropdownRef = React.useRef<HTMLDivElement>(null);
React.useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
setShowDropdown(false);
}
};
if (showDropdown) {
document.addEventListener("mousedown", handleClickOutside);
}
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, [showDropdown]);
const escapeCsvValue = (value: string): string => {
return `"${value.replace(/"/g, '""')}"`;
};
const formatValueForCsv = (value: string | object): string => {
if (value === null || value === undefined) {
return "";
}
if (typeof value === "object") {
return escapeCsvValue(JSON.stringify(value));
}
return escapeCsvValue(String(value));
};
const exportToCsv = () => {
try {
const allHeadersSet = new Set<string>();
queryResults.documents.forEach((doc) => {
Object.keys(doc).forEach((key) => allHeadersSet.add(key));
});
const allHeaders = Array.from(allHeadersSet);
const csvHeader = allHeaders.map(escapeCsvValue).join(",");
const csvData = queryResults.documents
.map((doc) =>
allHeaders.map((header) => (doc[header] !== undefined ? formatValueForCsv(doc[header]) : "")).join(","),
)
.join("\n");
const csvContent = `sep=,\n${csvHeader}\n${csvData}`;
downloadFile(csvContent, "query-results.csv", "text/csv");
} catch (error) {
console.error("Failed to export CSV:", error);
}
};
const exportToJson = () => {
try {
downloadFile(queryResultsString, "query-results.json", "application/json");
} catch (error) {
console.error("Failed to export JSON:", error);
}
};
const downloadFile = (content: string, fileName: string, contentType: string) => {
const blob = new Blob([content], { type: contentType });
const url = URL.createObjectURL(blob);
const downloadLink = document.createElement("a");
downloadLink.href = url;
downloadLink.download = fileName;
document.body.appendChild(downloadLink);
downloadLink.click();
document.body.removeChild(downloadLink);
setTimeout(() => URL.revokeObjectURL(url), 100);
};
const handleExport = (format: "CSV" | "JSON") => {
setShowDropdown(false);
if (format === "CSV") {
exportToCsv();
} else {
exportToJson();
}
};
const handleKeyDown = (e: React.KeyboardEvent, format: "CSV" | "JSON") => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
handleExport(format);
} else if (e.key === "Escape") {
setShowDropdown(false);
}
};
return (
<div style={{ position: "relative", display: "inline-block" }} ref={dropdownRef}>
<Button
onClick={() => setShowDropdown((v) => !v)}
size="small"
appearance="transparent"
icon={<ArrowDownloadRegular />}
title="Download Query Results"
aria-haspopup="listbox"
aria-expanded={showDropdown}
/>
{showDropdown && (
<div
style={{
position: "absolute",
right: 0,
zIndex: 10,
background: "white",
border: "1px solid #ccc",
borderRadius: 2,
minWidth: 60,
boxShadow: "0 2px 8px rgba(0,0,0,0.15)",
marginTop: 4,
}}
role="listbox"
tabIndex={-1}
>
<button
style={{
display: "block",
width: "100%",
padding: "8px 16px",
background: "none",
border: "none",
textAlign: "left",
cursor: "pointer",
transition: "background 0.2s",
}}
onMouseOver={(e) => (e.currentTarget.style.background = "#f3f3f3")}
onMouseOut={(e) => (e.currentTarget.style.background = "none")}
onClick={() => handleExport("JSON")}
onKeyDown={(e) => handleKeyDown(e, "JSON")}
role="option"
tabIndex={0}
>
JSON
</button>
<button
style={{
display: "block",
width: "100%",
padding: "8px 16px",
background: "none",
border: "none",
textAlign: "left",
cursor: "pointer",
transition: "background 0.2s",
}}
onMouseOver={(e) => (e.currentTarget.style.background = "#f3f3f3")}
onMouseOut={(e) => (e.currentTarget.style.background = "none")}
onClick={() => handleExport("CSV")}
onKeyDown={(e) => handleKeyDown(e, "CSV")}
role="option"
tabIndex={0}
>
CSV
</button>
</div>
)}
</div>
);
};
return ( return (
<> <>
<div className={styles.queryResultsBar}> <div className={styles.queryResultsBar}>
@@ -67,6 +234,7 @@ const ResultsTab: React.FC<ResultsViewProps> = ({ queryResults, isMongoDB, execu
aria-label="Copy" aria-label="Copy"
onClick={onClickCopyResults} onClick={onClickCopyResults}
/> />
<ExportResults />
</div> </div>
<div className={styles.queryResultsViewer}> <div className={styles.queryResultsViewer}>
<EditorReact language={"json"} content={queryResultsString} isReadOnly={true} ariaLabel={"Query results"} /> <EditorReact language={"json"} content={queryResultsString} isReadOnly={true} ariaLabel={"Query results"} />

View File

@@ -18,6 +18,7 @@ import React, { MutableRefObject, useEffect, useRef, useState } from "react";
import loadingIcon from "../../../images/circular_loader_black_16x16.gif"; import loadingIcon from "../../../images/circular_loader_black_16x16.gif";
import errorIcon from "../../../images/close-black.svg"; import errorIcon from "../../../images/close-black.svg";
import errorQuery from "../../../images/error_no_outline.svg"; import errorQuery from "../../../images/error_no_outline.svg";
import warningIconSvg from "../../../images/warning.svg";
import { useObservable } from "../../hooks/useObservable"; import { useObservable } from "../../hooks/useObservable";
import { ReactTabKind, useTabs } from "../../hooks/useTabs"; import { ReactTabKind, useTabs } from "../../hooks/useTabs";
import TabsBase from "./TabsBase"; import TabsBase from "./TabsBase";
@@ -117,6 +118,9 @@ function TabNav({ tab, active, tabKind }: { tab?: Tab; active: boolean; tabKind?
> >
<span className="statusIconContainer" style={{ width: tabKind === ReactTabKind.Home ? 0 : 18 }}> <span className="statusIconContainer" style={{ width: tabKind === ReactTabKind.Home ? 0 : 18 }}>
{useObservable(tab?.isExecutionError || ko.observable(false)) && <ErrorIcon tab={tab} active={active} />} {useObservable(tab?.isExecutionError || ko.observable(false)) && <ErrorIcon tab={tab} active={active} />}
{useObservable(tab?.isExecutionWarning || ko.observable(false)) && (
<WarningIcon tab={tab} active={active} />
)}
{isTabExecuting(tab, tabKind) && ( {isTabExecuting(tab, tabKind) && (
<img className="loadingIcon" title="Loading" src={loadingIcon} alt="Loading" /> <img className="loadingIcon" title="Loading" src={loadingIcon} alt="Loading" />
)} )}
@@ -194,6 +198,20 @@ const ErrorIcon = ({ tab, active }: { tab: Tab; active: boolean }) => (
</div> </div>
); );
const WarningIcon = ({ tab, active }: { tab: Tab; active: boolean }) => (
<div
id="warningStatusIcon"
role="button"
title="Click to view more details"
tabIndex={active ? 0 : undefined}
className={active ? "actionsEnabled warningIconContainer" : "warningIconContainer"}
onClick={({ nativeEvent: e }) => tab.onErrorDetailsClick(undefined, e)}
onKeyPress={({ nativeEvent: e }) => tab.onErrorDetailsKeyPress(undefined, e)}
>
<img src={warningIconSvg} alt="Warning Icon" style={{ height: 15, marginBottom: 5 }} />
</div>
);
function TabPane({ tab, active }: { tab: Tab; active: boolean }) { function TabPane({ tab, active }: { tab: Tab; active: boolean }) {
const ref = useRef<HTMLDivElement>(); const ref = useRef<HTMLDivElement>();
const attrs = { const attrs = {

View File

@@ -27,6 +27,7 @@ export default class TabsBase extends WaitsForTemplateViewModel {
public tabTitle: ko.Observable<string>; public tabTitle: ko.Observable<string>;
public tabPath: ko.Observable<string>; public tabPath: ko.Observable<string>;
public isExecutionError = ko.observable(false); public isExecutionError = ko.observable(false);
public isExecutionWarning = ko.observable(false);
public isExecuting = ko.observable(false); public isExecuting = ko.observable(false);
protected _theme: string; protected _theme: string;
public onLoadStartKey: number; public onLoadStartKey: number;

View File

@@ -8,6 +8,7 @@ import {
import { useNotebook } from "Explorer/Notebook/useNotebook"; import { useNotebook } from "Explorer/Notebook/useNotebook";
import { DocumentsTabV2 } from "Explorer/Tabs/DocumentsTabV2/DocumentsTabV2"; import { DocumentsTabV2 } from "Explorer/Tabs/DocumentsTabV2/DocumentsTabV2";
import { isFabricMirrored } from "Platform/Fabric/FabricUtil"; import { isFabricMirrored } from "Platform/Fabric/FabricUtil";
import { useDataplaneRbacAuthorization } from "Utils/AuthorizationUtils";
import * as ko from "knockout"; import * as ko from "knockout";
import * as _ from "underscore"; import * as _ from "underscore";
import * as Constants from "../../Common/Constants"; import * as Constants from "../../Common/Constants";
@@ -21,7 +22,7 @@ import { readTriggers } from "../../Common/dataAccess/readTriggers";
import { readUserDefinedFunctions } from "../../Common/dataAccess/readUserDefinedFunctions"; import { readUserDefinedFunctions } from "../../Common/dataAccess/readUserDefinedFunctions";
import * as DataModels from "../../Contracts/DataModels"; import * as DataModels from "../../Contracts/DataModels";
import * as ViewModels from "../../Contracts/ViewModels"; import * as ViewModels from "../../Contracts/ViewModels";
import { UploadDetailsRecord } from "../../Contracts/ViewModels"; import { BulkInsertResult, UploadDetailsRecord } from "../../Contracts/ViewModels";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants"; import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
@@ -479,9 +480,8 @@ export default class Collection implements ViewModels.Collection {
node: this, node: this,
title: title, title: title,
tabPath: "", tabPath: "",
password: useDataplaneRbacAuthorization(userContext) ? userContext.aadToken : userContext.masterKey || "",
collection: this, collection: this,
masterKey: userContext.masterKey || "",
collectionPartitionKeyProperty: this.partitionKeyProperties?.[0], collectionPartitionKeyProperty: this.partitionKeyProperties?.[0],
collectionId: this.id(), collectionId: this.id(),
databaseId: this.databaseId, databaseId: this.databaseId,
@@ -737,7 +737,7 @@ export default class Collection implements ViewModels.Collection {
title: title, title: title,
tabPath: "", tabPath: "",
collection: this, collection: this,
masterKey: userContext.masterKey || "", password: useDataplaneRbacAuthorization(userContext) ? userContext.aadToken : userContext.masterKey || "",
collectionPartitionKeyProperty: this.partitionKeyProperties?.[0], collectionPartitionKeyProperty: this.partitionKeyProperties?.[0],
collectionId: this.id(), collectionId: this.id(),
databaseId: this.databaseId, databaseId: this.databaseId,
@@ -1092,17 +1092,13 @@ export default class Collection implements ViewModels.Collection {
}); });
} }
public async bulkInsertDocuments(documents: JSONObject[]): Promise<{ public async bulkInsertDocuments(documents: JSONObject[]): Promise<BulkInsertResult> {
numSucceeded: number; const stats: BulkInsertResult = {
numFailed: number;
numThrottled: number;
errors: string[];
}> {
const stats = {
numSucceeded: 0, numSucceeded: 0,
numFailed: 0, numFailed: 0,
numThrottled: 0, numThrottled: 0,
errors: [] as string[], errors: [] as string[],
resources: [],
}; };
const chunkSize = 100; // 100 is the max # of bulk operations the SDK currently accepts const chunkSize = 100; // 100 is the max # of bulk operations the SDK currently accepts
@@ -1120,6 +1116,7 @@ export default class Collection implements ViewModels.Collection {
responses.forEach((response, index) => { responses.forEach((response, index) => {
if (response.statusCode === 201) { if (response.statusCode === 201) {
stats.numSucceeded++; stats.numSucceeded++;
stats.resources.push(response.resourceBody);
} else if (response.statusCode === 429) { } else if (response.statusCode === 429) {
documentsToAttempt.push(attemptedDocuments[index]); documentsToAttempt.push(attemptedDocuments[index]);
} else if (response.statusCode === 409) { } else if (response.statusCode === 409) {
@@ -1152,18 +1149,22 @@ export default class Collection implements ViewModels.Collection {
numFailed: 0, numFailed: 0,
numThrottled: 0, numThrottled: 0,
errors: [], errors: [],
resources: [],
}; };
try { try {
const parsedContent = JSON.parse(documentContent); const parsedContent = JSON.parse(documentContent);
if (Array.isArray(parsedContent)) { if (Array.isArray(parsedContent)) {
const { numSucceeded, numFailed, numThrottled, errors } = await this.bulkInsertDocuments(parsedContent); const { numSucceeded, numFailed, numThrottled, errors, resources } =
await this.bulkInsertDocuments(parsedContent);
record.numSucceeded = numSucceeded; record.numSucceeded = numSucceeded;
record.numFailed = numFailed; record.numFailed = numFailed;
record.numThrottled = numThrottled; record.numThrottled = numThrottled;
record.errors = errors; record.errors = errors;
record.resources = record.resources.concat(resources);
} else { } else {
await createDocument(this, parsedContent); const resource = await createDocument(this, parsedContent);
record.resources.push(resource);
record.numSucceeded++; record.numSucceeded++;
} }

View File

@@ -8,6 +8,11 @@ exports[`createDatabaseTreeNodes generates the correct tree structure for the Ca
"children": [ "children": [
{ {
"contextMenu": [ "contextMenu": [
{
"iconSrc": {},
"label": "Open Cassandra Shell",
"onClick": [Function],
},
{ {
"iconSrc": {}, "iconSrc": {},
"label": "Delete Table", "label": "Delete Table",
@@ -23,6 +28,11 @@ exports[`createDatabaseTreeNodes generates the correct tree structure for the Ca
], ],
"className": "collectionNode", "className": "collectionNode",
"contextMenu": [ "contextMenu": [
{
"iconSrc": {},
"label": "Open Cassandra Shell",
"onClick": [Function],
},
{ {
"iconSrc": {}, "iconSrc": {},
"label": "Delete Table", "label": "Delete Table",
@@ -45,6 +55,11 @@ exports[`createDatabaseTreeNodes generates the correct tree structure for the Ca
"children": [ "children": [
{ {
"contextMenu": [ "contextMenu": [
{
"iconSrc": {},
"label": "Open Cassandra Shell",
"onClick": [Function],
},
{ {
"iconSrc": {}, "iconSrc": {},
"label": "Delete Table", "label": "Delete Table",
@@ -65,6 +80,11 @@ exports[`createDatabaseTreeNodes generates the correct tree structure for the Ca
], ],
"className": "collectionNode", "className": "collectionNode",
"contextMenu": [ "contextMenu": [
{
"iconSrc": {},
"label": "Open Cassandra Shell",
"onClick": [Function],
},
{ {
"iconSrc": {}, "iconSrc": {},
"label": "Delete Table", "label": "Delete Table",
@@ -123,6 +143,11 @@ exports[`createDatabaseTreeNodes generates the correct tree structure for the Ca
"children": [ "children": [
{ {
"contextMenu": [ "contextMenu": [
{
"iconSrc": {},
"label": "Open Cassandra Shell",
"onClick": [Function],
},
{ {
"iconSrc": {}, "iconSrc": {},
"label": "Delete Table", "label": "Delete Table",
@@ -138,6 +163,11 @@ exports[`createDatabaseTreeNodes generates the correct tree structure for the Ca
], ],
"className": "collectionNode", "className": "collectionNode",
"contextMenu": [ "contextMenu": [
{
"iconSrc": {},
"label": "Open Cassandra Shell",
"onClick": [Function],
},
{ {
"iconSrc": {}, "iconSrc": {},
"label": "Delete Table", "label": "Delete Table",
@@ -187,6 +217,11 @@ exports[`createDatabaseTreeNodes generates the correct tree structure for the Ca
"children": [ "children": [
{ {
"contextMenu": [ "contextMenu": [
{
"iconSrc": {},
"label": "Open Cassandra Shell",
"onClick": [Function],
},
{ {
"iconSrc": {}, "iconSrc": {},
"label": "Delete Table", "label": "Delete Table",
@@ -257,6 +292,11 @@ exports[`createDatabaseTreeNodes generates the correct tree structure for the Ca
], ],
"className": "collectionNode", "className": "collectionNode",
"contextMenu": [ "contextMenu": [
{
"iconSrc": {},
"label": "Open Cassandra Shell",
"onClick": [Function],
},
{ {
"iconSrc": {}, "iconSrc": {},
"label": "Delete Table", "label": "Delete Table",
@@ -323,7 +363,7 @@ exports[`createDatabaseTreeNodes generates the correct tree structure for the Mo
}, },
{ {
"iconSrc": {}, "iconSrc": {},
"label": "New Shell", "label": "Open Mongo Shell",
"onClick": [Function], "onClick": [Function],
}, },
{ {
@@ -354,7 +394,7 @@ exports[`createDatabaseTreeNodes generates the correct tree structure for the Mo
}, },
{ {
"iconSrc": {}, "iconSrc": {},
"label": "New Shell", "label": "Open Mongo Shell",
"onClick": [Function], "onClick": [Function],
}, },
{ {
@@ -386,7 +426,7 @@ exports[`createDatabaseTreeNodes generates the correct tree structure for the Mo
}, },
{ {
"iconSrc": {}, "iconSrc": {},
"label": "New Shell", "label": "Open Mongo Shell",
"onClick": [Function], "onClick": [Function],
}, },
{ {
@@ -422,7 +462,7 @@ exports[`createDatabaseTreeNodes generates the correct tree structure for the Mo
}, },
{ {
"iconSrc": {}, "iconSrc": {},
"label": "New Shell", "label": "Open Mongo Shell",
"onClick": [Function], "onClick": [Function],
}, },
{ {
@@ -490,7 +530,7 @@ exports[`createDatabaseTreeNodes generates the correct tree structure for the Mo
}, },
{ {
"iconSrc": {}, "iconSrc": {},
"label": "New Shell", "label": "Open Mongo Shell",
"onClick": [Function], "onClick": [Function],
}, },
{ {
@@ -521,7 +561,7 @@ exports[`createDatabaseTreeNodes generates the correct tree structure for the Mo
}, },
{ {
"iconSrc": {}, "iconSrc": {},
"label": "New Shell", "label": "Open Mongo Shell",
"onClick": [Function], "onClick": [Function],
}, },
{ {
@@ -580,7 +620,7 @@ exports[`createDatabaseTreeNodes generates the correct tree structure for the Mo
}, },
{ {
"iconSrc": {}, "iconSrc": {},
"label": "New Shell", "label": "Open Mongo Shell",
"onClick": [Function], "onClick": [Function],
}, },
{ {
@@ -666,7 +706,7 @@ exports[`createDatabaseTreeNodes generates the correct tree structure for the Mo
}, },
{ {
"iconSrc": {}, "iconSrc": {},
"label": "New Shell", "label": "Open Mongo Shell",
"onClick": [Function], "onClick": [Function],
}, },
{ {

View File

@@ -21,7 +21,7 @@ import { useCommandBar } from "../Menus/CommandBar/CommandBarComponentAdapter";
import { useSelectedNode } from "../useSelectedNode"; import { useSelectedNode } from "../useSelectedNode";
export const shouldShowScriptNodes = (): boolean => { export const shouldShowScriptNodes = (): boolean => {
return !isFabric() && (userContext.apiType === "SQL" || userContext.apiType === "Gremlin"); return !isFabric() && configContext.platform !== Platform.Emulator && (userContext.apiType === "SQL" || userContext.apiType === "Gremlin");
}; };
const TreeDatabaseIcon = <DatabaseRegular fontSize={16} />; const TreeDatabaseIcon = <DatabaseRegular fontSize={16} />;

View File

@@ -34,7 +34,8 @@ const App: React.FunctionComponent = () => {
const [isOpen, { setTrue: openPanel, setFalse: dismissPanel }] = useBoolean(false); const [isOpen, { setTrue: openPanel, setFalse: dismissPanel }] = useBoolean(false);
const config = useConfig(); const config = useConfig();
const { isLoggedIn, armToken, graphToken, account, tenantId, logout, login, switchTenant, authFailure } = const { isLoggedIn, armToken, graphToken, account, tenantId, logout, login, switchTenant, authFailure } =
useAADAuth(); useAADAuth(config);
const [databaseAccount, setDatabaseAccount] = React.useState<DatabaseAccount>(); const [databaseAccount, setDatabaseAccount] = React.useState<DatabaseAccount>();
const [authType, setAuthType] = React.useState<AuthType>(encryptedToken ? AuthType.EncryptedToken : undefined); const [authType, setAuthType] = React.useState<AuthType>(encryptedToken ? AuthType.EncryptedToken : undefined);
const [connectionString, setConnectionString] = React.useState<string>(); const [connectionString, setConnectionString] = React.useState<string>();

View File

@@ -252,7 +252,7 @@ export class PhoenixClient {
private getPhoenixControlPlanePathPrefix(): string { private getPhoenixControlPlanePathPrefix(): string {
if (!this.armResourceId) { if (!this.armResourceId) {
throw new Error("The Phoenix client was not initialized properly: missing ARM resourcce id"); throw new Error("The Phoenix client was not initialized properly: missing ARM resource id");
} }
const toolsEndpoint = const toolsEndpoint =

View File

@@ -111,7 +111,7 @@ export function extractFeatures(given = new URLSearchParams(window.location.sear
copilotChatFixedMonacoEditorHeight: "true" === get("copilotchatfixedmonacoeditorheight"), copilotChatFixedMonacoEditorHeight: "true" === get("copilotchatfixedmonacoeditorheight"),
enablePriorityBasedExecution: "true" === get("enableprioritybasedexecution"), enablePriorityBasedExecution: "true" === get("enableprioritybasedexecution"),
disableConnectionStringLogin: "true" === get("disableconnectionstringlogin"), disableConnectionStringLogin: "true" === get("disableconnectionstringlogin"),
enableCloudShell: "true" === get("enablecloudshell"), enableCloudShell: true,
}; };
} }

View File

@@ -269,7 +269,7 @@ export const getOfferingIds = async (regions: Array<RegionItem>): Promise<Offeri
host: configContext.CATALOG_ENDPOINT, host: configContext.CATALOG_ENDPOINT,
path: getOfferingIdPathForRegion(), path: getOfferingIdPathForRegion(),
method: "GET", method: "GET",
apiVersion: "2023-05-01-preview", apiVersion: configContext.CATALOG_API_VERSION,
queryParams: { queryParams: {
filter: filter:
"armRegionNameeq '" + "armRegionNameeq '" +

View File

@@ -21,6 +21,7 @@ export enum StorageKey {
DatabaseAccountId, DatabaseAccountId,
EncryptedKeyToken, EncryptedKeyToken,
IsCrossPartitionQueryEnabled, IsCrossPartitionQueryEnabled,
QueryControlEnabled,
MaxDegreeOfParellism, MaxDegreeOfParellism,
IsGraphAutoVizDisabled, IsGraphAutoVizDisabled,
TenantId, TenantId,

View File

@@ -91,5 +91,5 @@ export const getItemName = (): string => {
}; };
export const isDataplaneRbacSupported = (apiType: string): boolean => { export const isDataplaneRbacSupported = (apiType: string): boolean => {
return apiType === "SQL" || apiType === "Tables"; return apiType === "SQL" || apiType === "Tables" || apiType === "Gremlin";
}; };

View File

@@ -1,10 +1,51 @@
import { AuthType } from "../AuthType"; import { AuthType } from "../AuthType";
import * as Constants from "../Common/Constants"; import * as Constants from "../Common/Constants";
import { updateUserContext } from "../UserContext"; import { ApiType, updateUserContext, userContext } from "../UserContext";
import * as AuthorizationUtils from "./AuthorizationUtils"; import * as AuthorizationUtils from "./AuthorizationUtils";
jest.mock("../Explorer/Explorer"); jest.mock("../Explorer/Explorer");
describe("AuthorizationUtils", () => { describe("AuthorizationUtils", () => {
const setAadDataPlane = (enabled: boolean) => {
updateUserContext({
features: {
enableAadDataPlane: enabled,
canExceedMaximumValue: false,
cosmosdb: false,
enableChangeFeedPolicy: false,
enableFixedCollectionWithSharedThroughput: false,
enableKOPanel: false,
enableNotebooks: false,
enableReactPane: false,
enableRightPanelV2: false,
enableSchema: false,
enableSDKoperations: false,
enableSpark: false,
enableTtl: false,
executeSproc: false,
enableResourceGraph: false,
enableKoResourceTree: false,
enableThroughputBuckets: false,
hostedDataExplorer: false,
sandboxNotebookOutputs: false,
showMinRUSurvey: false,
ttl90Days: false,
enableThroughputCap: false,
enableHierarchicalKeys: false,
enableCopilot: false,
disableCopilotPhoenixGateaway: false,
enableCopilotFullSchema: false,
copilotChatFixedMonacoEditorHeight: false,
enablePriorityBasedExecution: false,
disableConnectionStringLogin: false,
enableCloudShell: false,
autoscaleDefault: false,
partitionKeyDefault: false,
partitionKeyDefault2: false,
notebooksDownBanner: false,
},
});
};
describe("getAuthorizationHeader()", () => { describe("getAuthorizationHeader()", () => {
it("should return authorization header if authentication type is AAD", () => { it("should return authorization header if authentication type is AAD", () => {
updateUserContext({ updateUserContext({
@@ -54,4 +95,41 @@ describe("AuthorizationUtils", () => {
).toBeDefined(); ).toBeDefined();
}); });
}); });
describe("useDataplaneRbacAuthorization()", () => {
it("should return true if enableAadDataPlane feature flag is set", () => {
setAadDataPlane(true);
expect(AuthorizationUtils.useDataplaneRbacAuthorization(userContext)).toBe(true);
});
it("should return true if dataPlaneRbacEnabled is set to true and API supports RBAC", () => {
setAadDataPlane(false);
["SQL", "Tables", "Gremlin"].forEach((type) => {
updateUserContext({
dataPlaneRbacEnabled: true,
apiType: type as ApiType,
});
expect(AuthorizationUtils.useDataplaneRbacAuthorization(userContext)).toBe(true);
});
});
it("should return false if dataPlaneRbacEnabled is set to true and API does not support RBAC", () => {
setAadDataPlane(false);
["Mongo", "Cassandra", "Postgres", "VCoreMongo"].forEach((type) => {
updateUserContext({
dataPlaneRbacEnabled: true,
apiType: type as ApiType,
});
expect(AuthorizationUtils.useDataplaneRbacAuthorization(userContext)).toBe(false);
});
});
it("should return false if dataPlaneRbacEnabled is set to false", () => {
setAadDataPlane(false);
updateUserContext({
dataPlaneRbacEnabled: false,
});
expect(AuthorizationUtils.useDataplaneRbacAuthorization(userContext)).toBe(false);
});
});
}); });

View File

@@ -1,5 +1,6 @@
import * as msal from "@azure/msal-browser"; import * as msal from "@azure/msal-browser";
import { Action, ActionModifiers } from "Shared/Telemetry/TelemetryConstants"; import { Action, ActionModifiers } from "Shared/Telemetry/TelemetryConstants";
import { isDataplaneRbacSupported } from "Utils/APITypeUtils";
import { AuthType } from "../AuthType"; import { AuthType } from "../AuthType";
import * as Constants from "../Common/Constants"; import * as Constants from "../Common/Constants";
import * as Logger from "../Common/Logger"; import * as Logger from "../Common/Logger";
@@ -7,7 +8,7 @@ import { configContext } from "../ConfigContext";
import { DatabaseAccount } from "../Contracts/DataModels"; import { DatabaseAccount } from "../Contracts/DataModels";
import * as ViewModels from "../Contracts/ViewModels"; import * as ViewModels from "../Contracts/ViewModels";
import { trace, traceFailure } from "../Shared/Telemetry/TelemetryProcessor"; import { trace, traceFailure } from "../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../UserContext"; import { UserContext, userContext } from "../UserContext";
export function getAuthorizationHeader(): ViewModels.AuthorizationTokenHeaderMetadata { export function getAuthorizationHeader(): ViewModels.AuthorizationTokenHeaderMetadata {
if (userContext.authType === AuthType.EncryptedToken) { if (userContext.authType === AuthType.EncryptedToken) {
@@ -179,3 +180,10 @@ export async function acquireTokenWithMsal(
} }
} }
} }
export function useDataplaneRbacAuthorization(userContext: UserContext): boolean {
return (
userContext.features.enableAadDataPlane ||
(userContext.dataPlaneRbacEnabled && isDataplaneRbacSupported(userContext.apiType))
);
}

View File

@@ -1,6 +1,7 @@
export const autoPilotThroughput1K = 1000; export const autoPilotThroughput1K = 1000;
export const autoPilotIncrementStep = 1000; export const autoPilotIncrementStep = 1000;
export const autoPilotThroughput4K = 4000; export const autoPilotThroughput4K = 4000;
export const autoPilotThroughput5K = 5000;
export const autoPilotThroughput10K = 10000; export const autoPilotThroughput10K = 10000;
export function isValidAutoPilotThroughput(maxThroughput: number): boolean { export function isValidAutoPilotThroughput(maxThroughput: number): boolean {

View File

@@ -1,3 +1,4 @@
import { isFabricNative } from "Platform/Fabric/FabricUtil";
import * as Constants from "../Common/Constants"; import * as Constants from "../Common/Constants";
import { userContext } from "../UserContext"; import { userContext } from "../UserContext";
@@ -18,9 +19,8 @@ export const isServerlessAccount = (): boolean => {
}; };
export const isVectorSearchEnabled = (): boolean => { export const isVectorSearchEnabled = (): boolean => {
return userContext.apiType === "SQL" && isCapabilityEnabled(Constants.CapabilityNames.EnableNoSQLVectorSearch); return (
}; userContext.apiType === "SQL" &&
(isCapabilityEnabled(Constants.CapabilityNames.EnableNoSQLVectorSearch) || isFabricNative())
export const isFullTextSearchEnabled = (): boolean => { );
return userContext.apiType === "SQL" && isCapabilityEnabled(Constants.CapabilityNames.EnableNoSQLFullTextSearch);
}; };

View File

@@ -45,32 +45,25 @@ export const defaultAllowedArmEndpoints: ReadonlyArray<string> = [
"https://management.chinacloudapi.cn", "https://management.chinacloudapi.cn",
]; ];
export const allowedAadEndpoints: ReadonlyArray<string> = [ export const defaultAllowedAadEndpoints: ReadonlyArray<string> = [
"https://login.microsoftonline.com/", "https://login.microsoftonline.com/",
"https://login.microsoftonline.us/", "https://login.microsoftonline.us/",
"https://login.partner.microsoftonline.cn/", "https://login.partner.microsoftonline.cn/",
]; ];
export const defaultAllowedGraphEndpoints: ReadonlyArray<string> = ["https://graph.microsoft.com"];
export const defaultAllowedBackendEndpoints: ReadonlyArray<string> = [ export const defaultAllowedBackendEndpoints: ReadonlyArray<string> = [
"https://localhost:12901",
"https://localhost:1234", "https://localhost:1234",
PortalBackendEndpoints.Development,
PortalBackendEndpoints.Mpac,
PortalBackendEndpoints.Prod,
PortalBackendEndpoints.Fairfax,
PortalBackendEndpoints.Mooncake,
]; ];
export const PortalBackendOutboundIPs: { [key: string]: string[] } = {
[PortalBackendEndpoints.Mpac]: ["13.91.105.215", "4.210.172.107"],
[PortalBackendEndpoints.Prod]: ["13.88.56.148", "40.91.218.243"],
[PortalBackendEndpoints.Fairfax]: ["52.247.163.6", "52.244.134.181"],
[PortalBackendEndpoints.Mooncake]: ["163.228.137.6", "143.64.170.142"],
};
export const MongoProxyOutboundIPs: { [key: string]: string[] } = {
[MongoProxyEndpoints.Mpac]: ["20.245.81.54", "40.118.23.126"],
[MongoProxyEndpoints.Prod]: ["40.80.152.199", "13.95.130.121"],
[MongoProxyEndpoints.Fairfax]: ["52.244.176.112", "52.247.148.42"],
[MongoProxyEndpoints.Mooncake]: ["52.131.240.99", "143.64.61.130"],
};
export const defaultAllowedMongoProxyEndpoints: ReadonlyArray<string> = [ export const defaultAllowedMongoProxyEndpoints: ReadonlyArray<string> = [
"https://localhost:1234",
MongoProxyEndpoints.Development, MongoProxyEndpoints.Development,
MongoProxyEndpoints.Mpac, MongoProxyEndpoints.Mpac,
MongoProxyEndpoints.Prod, MongoProxyEndpoints.Prod,
@@ -86,6 +79,14 @@ export const defaultAllowedCassandraProxyEndpoints: ReadonlyArray<string> = [
CassandraProxyEndpoints.Mooncake, CassandraProxyEndpoints.Mooncake,
]; ];
export const allowedCassandraProxyEndpoints_ToBeDeprecated: ReadonlyArray<string> = [
"https://main.documentdb.ext.azure.com",
"https://main.documentdb.ext.azure.cn",
"https://main.documentdb.ext.azure.us",
"https://main.cosmos.ext.azure",
"https://localhost:12901",
];
export const CassandraProxyOutboundIPs: { [key: string]: string[] } = { export const CassandraProxyOutboundIPs: { [key: string]: string[] } = {
[CassandraProxyEndpoints.Mpac]: ["40.113.96.14", "104.42.11.145"], [CassandraProxyEndpoints.Mpac]: ["40.113.96.14", "104.42.11.145"],
[CassandraProxyEndpoints.Prod]: ["137.117.230.240", "168.61.72.237"], [CassandraProxyEndpoints.Prod]: ["137.117.230.240", "168.61.72.237"],
@@ -93,11 +94,7 @@ export const CassandraProxyOutboundIPs: { [key: string]: string[] } = {
[CassandraProxyEndpoints.Mooncake]: ["40.73.99.146", "143.64.62.47"], [CassandraProxyEndpoints.Mooncake]: ["40.73.99.146", "143.64.62.47"],
}; };
export const allowedEmulatorEndpoints: ReadonlyArray<string> = ["https://localhost:8081"]; export const allowedEmulatorEndpoints: ReadonlyArray<string> = ["https://localhost:8081", "http://localhost:8081"];
export const allowedMongoBackendEndpoints: ReadonlyArray<string> = ["https://localhost:1234"];
export const allowedGraphEndpoints: ReadonlyArray<string> = ["https://graph.microsoft.com"];
export const allowedArcadiaEndpoints: ReadonlyArray<string> = ["https://workspaceartifacts.projectarcadia.net"]; export const allowedArcadiaEndpoints: ReadonlyArray<string> = ["https://workspaceartifacts.projectarcadia.net"];

View File

@@ -245,7 +245,7 @@ export function downloadItem(
}, },
"Cancel", "Cancel",
undefined, undefined,
container.getDownloadModalConent(name), container.getDownloadModalContent(name),
); );
} }
export async function downloadNotebookItem( export async function downloadNotebookItem(

View File

@@ -23,3 +23,7 @@ export const logConsoleError = (msg: string): void => {
export const logConsoleInfo = (msg: string): void => { export const logConsoleInfo = (msg: string): void => {
log(ConsoleDataType.Info, msg); log(ConsoleDataType.Info, msg);
}; };
export const logConsoleWarning = (msg: string): void => {
log(ConsoleDataType.Warning, msg);
};

View File

@@ -0,0 +1,112 @@
import { Platform, configContext } from "../ConfigContext";
/**
* Feature flags enumeration - centralized feature definitions
*/
export enum PlatformFeature {
// UI/Core Features
Queries = "Queries",
Notebooks = "Notebooks",
SynapseLink = "SynapseLink",
VSCodeIntegration = "VSCodeIntegration",
GlobalSecondaryIndex = "GlobalSecondaryIndex",
DataPlaneRbac = "DataPlaneRbac",
EntraIDLogin = "EntraIDLogin",
EntreIDRbac = "EntreIDRbac",
RetrySettings = "RetrySettings",
GraphAutoVizOption = "GraphAutoVizOption",
CrossPartitionOption = "CrossPartitionOption",
EnhancedQueryControl = "EnhancedQueryControl",
ParallelismOption = "ParallelismOption",
EnableEntraIdRbac = "EnableEntraIdRbac",
PriorityBasedExecution = "PriorityBasedExecution",
RegionSelection = "RegionSelection",
Copilot = "Copilot",
CloudShell = "CloudShell",
ContainerPagination = "ContainerPagination",
FullTextSearch = "FullTextSearch",
VectorSearch = "VectorSearch",
ThroughputBucketing = "ThroughputBucketing",
ComputedProperties = "ComputedProperties",
AnalyticalStore = "AnalyticalStore",
// CRUD Operations - Database
CreateDatabase = "CreateDatabase",
ReadDatabase = "ReadDatabase",
DeleteDatabase = "DeleteDatabase",
// CRUD Operations - Collection
CreateCollection = "CreateCollection",
ReadCollection = "ReadCollection",
UpdateCollection = "UpdateCollection",
DeleteCollection = "DeleteCollection",
// CRUD Operations - Document
CreateDocument = "CreateDocument",
ReadDocument = "ReadDocument",
UpdateDocument = "UpdateDocument",
DeleteDocument = "DeleteDocument",
// Advanced Database Features
StoredProcedures = "StoredProcedures",
UDF = "UDF",
Trigger = "Trigger",
}
/**
* Feature matrix per platform.
* - Only list platforms that have restrictions. If a platform is not present, all features are considered supported.
* - Start with VNextEmulator today; add more platforms/flags here later without touching calling code.
*/
const FEATURE_MATRIX: ReadonlyMap<Platform, ReadonlySet<PlatformFeature>> = new Map([
[
Platform.VNextEmulator,
new Set<PlatformFeature>([
PlatformFeature.Queries,
PlatformFeature.CreateDatabase,
PlatformFeature.ReadDatabase,
PlatformFeature.DeleteDatabase,
PlatformFeature.CreateCollection,
PlatformFeature.ReadCollection,
PlatformFeature.UpdateCollection,
PlatformFeature.DeleteCollection,
PlatformFeature.CreateDocument,
PlatformFeature.ReadDocument,
PlatformFeature.UpdateDocument,
PlatformFeature.DeleteDocument,
]),
],
]);
/**
* Central feature flag function - checks if a feature is enabled for current platform
* @param feature The feature to check
* @param platform Optional platform override, defaults to current platform
* @returns True if the feature is enabled for the platform, false otherwise
*/
export const isFeatureSupported = (feature: PlatformFeature, platform?: Platform): boolean => {
const currentPlatform = platform ?? configContext.platform;
if (currentPlatform !== Platform.VNextEmulator) {
return true;
}
// VNextEmulator: check from the feature matrix
const vnextFeatures = FEATURE_MATRIX.get(Platform.VNextEmulator);
return vnextFeatures?.has(feature) ?? false;
};
export const areAdvancedScriptsSupported = (platform?: Platform): boolean => {
const currentPlatform = platform ?? configContext.platform;
if (currentPlatform !== Platform.VNextEmulator) {
return true;
}
// Otherwise, require all script features to be enabled
return (
isFeatureSupported(PlatformFeature.StoredProcedures, currentPlatform) &&
isFeatureSupported(PlatformFeature.UDF, currentPlatform) &&
isFeatureSupported(PlatformFeature.Trigger, currentPlatform)
);
};

View File

@@ -1,4 +1,7 @@
import { PartitionKey, PartitionKeyDefinition } from "@azure/cosmos"; import { PartitionKey, PartitionKeyDefinition } from "@azure/cosmos";
import { getRUThreshold, ruThresholdEnabled } from "Shared/StorageUtility";
import { userContext } from "UserContext";
import { logConsoleWarning } from "Utils/NotificationConsoleUtils";
import * as DataModels from "../Contracts/DataModels"; import * as DataModels from "../Contracts/DataModels";
import * as ViewModels from "../Contracts/ViewModels"; import * as ViewModels from "../Contracts/ViewModels";
@@ -86,6 +89,18 @@ export const queryPagesUntilContentPresent = async (
results.roundTrips = roundTrips; results.roundTrips = roundTrips;
results.requestCharge = Number(results.requestCharge) + netRequestCharge; results.requestCharge = Number(results.requestCharge) + netRequestCharge;
netRequestCharge = Number(results.requestCharge); netRequestCharge = Number(results.requestCharge);
if (results.hasMoreResults && userContext.apiType === "SQL" && ruThresholdEnabled()) {
const ruThreshold: number = getRUThreshold();
if (netRequestCharge > ruThreshold) {
logConsoleWarning(
`Warning: Query has exceeded the Request Unit threshold of ${ruThreshold} RUs. Query results show only those documents returned before the threshold was exceeded`,
);
results.ruThresholdExceeded = true;
return results;
}
}
const resultsMetadata = { const resultsMetadata = {
hasMoreResults: results.hasMoreResults, hasMoreResults: results.hasMoreResults,
itemCount: results.itemCount, itemCount: results.itemCount,

View File

@@ -3,13 +3,13 @@
Run "npm run generateARMClients" to regenerate Run "npm run generateARMClients" to regenerate
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-12-01-preview/cosmos-db.json Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2025-05-01-preview/cosmos-db.json
*/ */
import { configContext } from "../../../../ConfigContext";
import { armRequest } from "../../request"; import { armRequest } from "../../request";
import * as Types from "./types"; import * as Types from "./types";
const apiVersion = "2024-12-01-preview"; import { configContext } from "../../../../ConfigContext";
const apiVersion = "2025-05-01-preview";
/* Lists the Cassandra keyspaces under an existing Azure Cosmos DB database account. */ /* Lists the Cassandra keyspaces under an existing Azure Cosmos DB database account. */
export async function listCassandraKeyspaces( export async function listCassandraKeyspaces(

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