Compare commits

...

59 Commits

Author SHA1 Message Date
Sung-Hyun Kang
1faf8983f4 Add unique keys feature back 2025-10-30 15:38:52 -05:00
Sung-Hyun Kang
8cf4a94355 Add parameterized ejs 2025-10-30 15:33:40 -05:00
Sung-Hyun Kang
d7fd733564 Fix package-lock.json 2025-10-24 15:12:08 -05:00
Sung-Hyun Kang
b5725df9ed fix preview/package-lock 2025-10-24 15:00:10 -05:00
Sung-Hyun Kang
2617a7ffe5 Fix Quickstart and UI 2025-10-22 17:05:19 -05:00
Sung-Hyun Kang
2194bb4961 Ignore IIF statements if emulators 2025-10-21 16:06:39 -05:00
Sung-Hyun Kang
3e86a6be9d Added extra checks for VNextEmulator 2025-10-21 12:02:47 -05:00
sunghyunkang1111
44f834b198 Vnext emulator fix (#2213)
* Added feature flags to the components

* Remove config.json
2025-09-25 19:15:58 -05:00
sunghyunkang1111
59190f2376 Added VNext feature matrix (#2200) 2025-08-20 11:53:49 -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
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
118 changed files with 3512 additions and 37164 deletions

View File

@@ -23,8 +23,6 @@ src/Common/MongoUtility.ts
src/Common/NotificationsClientBase.ts
src/Common/QueriesClient.ts
src/Common/Splitter.ts
src/Controls/Heatmap/Heatmap.test.ts
src/Controls/Heatmap/Heatmap.ts
src/Definitions/datatables.d.ts
src/Definitions/gif.d.ts
src/Definitions/globals.d.ts
@@ -145,4 +143,6 @@ src/Explorer/Tree/ResourceTreeAdapter.tsx
__mocks__/monaco-editor.ts
src/Explorer/Tree/ResourceTree.tsx
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"
uses: Azure/login@v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
client-id: ${{ secrets.E2E_TESTS_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_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']}}
run: npx playwright test --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --workers=3
- name: Upload blob report to GitHub Actions Artifacts

View File

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

2
.npmrc
View File

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

View File

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

View File

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

View File

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

117
package-lock.json generated
View File

@@ -10,7 +10,7 @@
"hasInstallScript": true,
"dependencies": {
"@azure/arm-cosmosdb": "9.1.0",
"@azure/cosmos": "4.3.0",
"@azure/cosmos": "4.5.0",
"@azure/cosmos-language-service": "0.0.5",
"@azure/identity": "4.5.0",
"@azure/msal-browser": "2.14.2",
@@ -205,6 +205,58 @@
"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": {
"version": "1.2.6",
"license": "MIT",
@@ -391,24 +443,25 @@
"license": "0BSD"
},
"node_modules/@azure/cosmos": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/@azure/cosmos/-/cosmos-4.3.0.tgz",
"integrity": "sha512-0Ls3l1uWBBSphx6YRhnM+w7rSvq8qVugBCdO6kSiNuRYXEf6+YWLjbzz4e7L2kkz/6ScFdZIOJYP+XtkiRYOhA==",
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/@azure/cosmos/-/cosmos-4.5.0.tgz",
"integrity": "sha512-JsTh4twb6FcwP7rJwxQiNZQ/LGtuF6gmciaxY9Rnp6/A325Lhsw/SH4R2ArpT0yCvozbZpweIwdPfUkXVBtp5w==",
"license": "MIT",
"dependencies": {
"@azure/abort-controller": "^2.0.0",
"@azure/core-auth": "^1.7.1",
"@azure/core-rest-pipeline": "^1.15.1",
"@azure/core-tracing": "^1.1.1",
"@azure/core-util": "^1.8.1",
"@azure/keyvault-keys": "^4.8.0",
"@azure/abort-controller": "^2.1.2",
"@azure/core-auth": "^1.9.0",
"@azure/core-rest-pipeline": "^1.19.1",
"@azure/core-tracing": "^1.2.0",
"@azure/core-util": "^1.11.0",
"@azure/keyvault-keys": "^4.9.0",
"@azure/logger": "^1.1.4",
"fast-json-stable-stringify": "^2.1.0",
"jsbi": "^4.3.0",
"priorityqueuejs": "^2.0.0",
"semaphore": "^1.1.0",
"tslib": "^2.6.2"
"tslib": "^2.8.1"
},
"engines": {
"node": ">=18.0.0"
"node": ">=20.0.0"
}
},
"node_modules/@azure/cosmos-language-service": {
@@ -438,8 +491,9 @@
}
},
"node_modules/@azure/cosmos/node_modules/tslib": {
"version": "2.6.2",
"license": "0BSD"
"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/identity": {
"version": "4.5.0",
@@ -7891,7 +7945,7 @@
}
},
"node_modules/@nteract/data-explorer/node_modules/cross-spawn": {
"version": "6.0.5",
"version": "7.0.5",
"license": "MIT",
"dependencies": {
"nice-try": "^1.0.4",
@@ -8015,7 +8069,7 @@
"version": "1.0.0",
"license": "MIT",
"dependencies": {
"cross-spawn": "^6.0.0",
"cross-spawn": "^7.0.5",
"get-stream": "^4.0.0",
"is-stream": "^1.1.0",
"npm-run-path": "^2.0.0",
@@ -16443,7 +16497,7 @@
}
},
"node_modules/cross-spawn": {
"version": "7.0.3",
"version": "7.0.5",
"license": "MIT",
"dependencies": {
"path-key": "^3.1.0",
@@ -18357,7 +18411,7 @@
"@nodelib/fs.walk": "^1.2.8",
"ajv": "^6.12.4",
"chalk": "^4.0.0",
"cross-spawn": "^7.0.2",
"cross-spawn": "^7.0.5",
"debug": "^4.3.2",
"doctrine": "^3.0.0",
"escape-string-regexp": "^4.0.0",
@@ -18944,7 +18998,7 @@
"integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==",
"devOptional": true,
"dependencies": {
"cross-spawn": "^7.0.3",
"cross-spawn": "^7.0.5",
"get-stream": "^6.0.0",
"human-signals": "^2.1.0",
"is-stream": "^2.0.0",
@@ -19209,7 +19263,7 @@
"methods": "~1.1.2",
"on-finished": "2.4.1",
"parseurl": "~1.3.3",
"path-to-regexp": "0.1.7",
"path-to-regexp": "0.1.12",
"proxy-addr": "~2.0.7",
"qs": "6.11.0",
"range-parser": "~1.2.1",
@@ -19245,7 +19299,7 @@
"license": "MIT"
},
"node_modules/express/node_modules/path-to-regexp": {
"version": "0.1.7",
"version": "0.1.12",
"dev": true,
"license": "MIT"
},
@@ -27178,11 +27232,6 @@
"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": {
"version": "0.1.1",
"license": "MIT"
@@ -30538,7 +30587,7 @@
"@yarnpkg/lockfile": "^1.1.0",
"chalk": "^4.1.2",
"ci-info": "^3.7.0",
"cross-spawn": "^7.0.3",
"cross-spawn": "^7.0.5",
"find-yarn-workspace-root": "^2.0.0",
"fs-extra": "^9.0.0",
"json-stable-stringify": "^1.0.2",
@@ -31516,7 +31565,7 @@
"address": "^1.1.2",
"browserslist": "^4.18.1",
"chalk": "^4.1.2",
"cross-spawn": "^7.0.3",
"cross-spawn": "^7.0.5",
"detect-port-alt": "^1.1.6",
"escape-string-regexp": "^4.0.0",
"filesize": "^8.0.6",
@@ -32972,7 +33021,7 @@
}
},
"node_modules/sane/node_modules/cross-spawn": {
"version": "6.0.5",
"version": "7.0.5",
"license": "MIT",
"dependencies": {
"nice-try": "^1.0.4",
@@ -32989,7 +33038,7 @@
"version": "1.0.0",
"license": "MIT",
"dependencies": {
"cross-spawn": "^6.0.0",
"cross-spawn": "^7.0.5",
"get-stream": "^4.0.0",
"is-stream": "^1.1.0",
"npm-run-path": "^2.0.0",
@@ -35958,7 +36007,7 @@
"@webpack-cli/serve": "^2.0.5",
"colorette": "^2.0.14",
"commander": "^10.0.1",
"cross-spawn": "^7.0.3",
"cross-spawn": "^7.0.5",
"envinfo": "^7.7.3",
"fastest-levenshtein": "^1.0.12",
"import-local": "^3.0.2",
@@ -36483,7 +36532,7 @@
}
},
"node_modules/windows-release/node_modules/cross-spawn": {
"version": "6.0.5",
"version": "7.0.5",
"license": "MIT",
"dependencies": {
"nice-try": "^1.0.4",
@@ -36500,7 +36549,7 @@
"version": "1.0.0",
"license": "MIT",
"dependencies": {
"cross-spawn": "^6.0.0",
"cross-spawn": "^7.0.5",
"get-stream": "^4.0.0",
"is-stream": "^1.1.0",
"npm-run-path": "^2.0.0",

View File

@@ -5,7 +5,7 @@
"main": "index.js",
"dependencies": {
"@azure/arm-cosmosdb": "9.1.0",
"@azure/cosmos": "4.3.0",
"@azure/cosmos": "4.5.0",
"@azure/cosmos-language-service": "0.0.5",
"@azure/identity": "4.5.0",
"@azure/msal-browser": "2.14.2",
@@ -206,9 +206,11 @@
"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: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:fast": "webpack --mode development --progress",
"copyToConsumers": "node copyToConsumers",
"copyToProxy": "rm -rf ./utils/local-proxy/dist && cp -r ./dist ./utils/local-proxy",
"test": "rimraf coverage && jest",
"test:debug": "jest --runInBand",
"test:e2e": "jest -c ./jest.config.playwright.js --detectOpenHandles",

36205
preview/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -138,15 +138,6 @@ export enum MongoBackendEndpointType {
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 {
public static readonly Development: string = "https://localhost:7235";
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 { checkDatabaseResourceTokensValidity, isFabricMirroredKey } from "Platform/Fabric/FabricUtil";
import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
import { useDataplaneRbacAuthorization } from "Utils/AuthorizationUtils";
import { AuthType } from "../AuthType";
import { PriorityLevel } from "../Common/Constants";
import * as Logger from "../Common/Logger";
import { Platform, configContext } from "../ConfigContext";
import { FabricArtifactInfo, updateUserContext, userContext } from "../UserContext";
import { isDataplaneRbacSupported } from "../Utils/APITypeUtils";
import { logConsoleError } from "../Utils/NotificationConsoleUtils";
import * as PriorityBasedExecutionUtils from "../Utils/PriorityBasedExecutionUtils";
import { EmulatorMasterKey, HttpHeaders } from "./Constants";
@@ -20,8 +20,7 @@ const _global = typeof self === "undefined" ? window : self;
export const tokenProvider = async (requestInfo: Cosmos.RequestInfo) => {
const { verb, resourceId, resourceType, headers } = requestInfo;
const dataPlaneRBACOptionEnabled = userContext.dataPlaneRbacEnabled && isDataplaneRbacSupported(userContext.apiType);
if (userContext.features.enableAadDataPlane || dataPlaneRBACOptionEnabled) {
if (useDataplaneRbacAuthorization(userContext)) {
Logger.logInfo(
`AAD Data Plane Feature flag set to ${userContext.features.enableAadDataPlane} for account with disable local auth ${userContext.databaseAccount.properties.disableLocalAuth} `,
"Explorer/tokenProvider",
@@ -37,7 +36,7 @@ export const tokenProvider = async (requestInfo: Cosmos.RequestInfo) => {
return authorizationToken;
}
if (configContext.platform === Platform.Emulator) {
if (configContext.platform === Platform.Emulator || configContext.platform === Platform.VNextEmulator) {
// TODO This SDK method mutates the headers object. Find a better one or fix the SDK.
await Cosmos.setAuthorizationTokenHeaderUsingMasterKey(verb, resourceId, resourceType, headers, EmulatorMasterKey);
return decodeURIComponent(headers.authorization);
@@ -120,7 +119,7 @@ export const requestPlugin: Cosmos.Plugin<any> = async (requestContext, diagnost
};
export const endpoint = () => {
if (configContext.platform === Platform.Emulator) {
if (configContext.platform === Platform.Emulator || configContext.platform === Platform.VNextEmulator) {
// In worker scope, _global(self).parent does not exist
const location = _global.parent ? _global.parent.location : _global.location;
return configContext.EMULATOR_ENDPOINT || location.origin;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -443,6 +443,7 @@ export interface DataExplorerInputsFrame {
[key: string]: string;
};
feedbackPolicies?: any;
aadToken?: string;
}
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

@@ -10,6 +10,7 @@ import { useDatabases } from "Explorer/useDatabases";
import { isFabric, isFabricNative } from "Platform/Fabric/FabricUtil";
import { Action } from "Shared/Telemetry/TelemetryConstants";
import { traceOpen } from "Shared/Telemetry/TelemetryProcessor";
import { areAdvancedScriptsSupported } from "Utils/PlatformFeatureUtils";
import { ReactTabKind, useTabs } from "hooks/useTabs";
import React from "react";
import AddCollectionIcon from "../../images/AddCollection.svg";
@@ -130,6 +131,7 @@ export const createCollectionContextMenuButton = (
}
if (
areAdvancedScriptsSupported(configContext.platform) &&
configContext.platform !== Platform.Fabric &&
(userContext.apiType === "SQL" || userContext.apiType === "Gremlin")
) {

View File

@@ -15,6 +15,7 @@ import { useDatabases } from "Explorer/useDatabases";
import { isFabricNative } from "Platform/Fabric/FabricUtil";
import { isVectorSearchEnabled } from "Utils/CapabilityUtils";
import { isRunningOnPublicCloud } from "Utils/CloudUtils";
import { isFeatureSupported, PlatformFeature } from "Utils/PlatformFeatureUtils";
import * as React from "react";
import DiscardIcon from "../../../../images/discard.svg";
import SaveIcon from "../../../../images/save-cosmos.svg";
@@ -60,15 +61,15 @@ import {
AddMongoIndexProps,
ChangeFeedPolicyState,
GeospatialConfigType,
MongoIndexTypes,
SettingsV2TabTypes,
TtlType,
getMongoNotification,
getTabTitle,
hasDatabaseSharedThroughput,
isDirty,
MongoIndexTypes,
parseConflictResolutionMode,
parseConflictResolutionProcedure,
SettingsV2TabTypes,
TtlType,
} from "./SettingsUtils";
interface SettingsV2TabInfo {
@@ -276,14 +277,14 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
this.saveSettingsButton = {
isEnabled: this.isSaveSettingsButtonEnabled,
isVisible: () => {
return true;
return isFeatureSupported(PlatformFeature.UpdateCollection);
},
};
this.discardSettingsChangesButton = {
isEnabled: this.isDiscardSettingsButtonEnabled,
isVisible: () => {
return true;
return isFeatureSupported(PlatformFeature.UpdateCollection);
},
};
@@ -1335,7 +1336,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
});
}
if (this.shouldShowComputedPropertiesEditor) {
if (isFeatureSupported(PlatformFeature.ComputedProperties) && this.shouldShowComputedPropertiesEditor) {
tabs.push({
tab: SettingsV2TabTypes.ComputedPropertiesTab,
content: <ComputedPropertiesComponent {...computedPropertiesComponentProps} />,

View File

@@ -45,7 +45,7 @@ export class ScaleComponent extends React.Component<ScaleComponentProps> {
constructor(props: ScaleComponentProps) {
super(props);
this.isEmulator = configContext.platform === Platform.Emulator;
this.isEmulator = configContext.platform === Platform.Emulator || configContext.platform === Platform.VNextEmulator;
this.offer = this.props.database?.offer() || this.props.collection?.offer();
this.databaseId = this.props.database?.id() || this.props.collection.databaseId;
this.collectionId = this.props.collection?.id();

View File

@@ -559,26 +559,81 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
private getThroughputTextField = (): JSX.Element => (
<>
{this.props.isAutoPilotSelected ? (
<TextField
label="Maximum RU/s required by this resource"
required
type="number"
id="autopilotInput"
key="auto pilot throughput input"
styles={getTextFieldStyles(this.props.maxAutoPilotThroughput, this.props.maxAutoPilotThroughputBaseline)}
disabled={this.overrideWithProvisionedThroughputSettings()}
step={AutoPilotUtils.autoPilotIncrementStep}
value={this.overrideWithProvisionedThroughputSettings() ? "" : this.props.maxAutoPilotThroughput?.toString()}
onChange={this.onAutoPilotThroughputChange}
min={autoPilotThroughput1K}
onGetErrorMessage={(value: string) => {
const sanitizedValue = getSanitizedInputValue(value);
return sanitizedValue % 1000
? "Throughput value must be in increments of 1000"
: this.props.throughputError;
}}
validateOnLoad={false}
/>
<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
required
type="number"
id="autopilotInput"
key="auto pilot throughput input"
styles={{
...getTextFieldStyles(this.props.maxAutoPilotThroughput, this.props.maxAutoPilotThroughputBaseline),
fieldGroup: { width: 100, height: 28 },
field: { fontSize: 14, fontWeight: 400 },
}}
disabled={this.overrideWithProvisionedThroughputSettings()}
step={AutoPilotUtils.autoPilotIncrementStep}
value={
this.overrideWithProvisionedThroughputSettings() ? "" : this.props.maxAutoPilotThroughput?.toString()
}
onChange={this.onAutoPilotThroughputChange}
min={autoPilotThroughput1K}
onGetErrorMessage={(value: string) => {
const sanitizedValue = getSanitizedInputValue(value);
return sanitizedValue % 1000
? "Throughput value must be in increments of 1000"
: this.props.throughputError;
}}
validateOnLoad={false}
/>
</Stack>
</Stack>
) : (
<TextField
required

View File

@@ -157,35 +157,148 @@ exports[`ThroughputInputAutoPilotV3Component autopilot input visible 1`] = `
}
}
>
<StyledTextFieldBase
disabled={true}
id="autopilotInput"
key="auto pilot throughput input"
label="Maximum RU/s required by this resource"
min={1000}
onChange={[Function]}
onGetErrorMessage={[Function]}
required={true}
step={1000}
styles={
<Stack
horizontal={true}
tokens={
{
"fieldGroup": {
"borderColor": "",
"height": 25,
"selectors": {
":disabled": {
"backgroundColor": undefined,
"borderColor": undefined,
},
},
"width": 300,
},
"childrenGap": 8,
}
}
type="number"
validateOnLoad={false}
value=""
/>
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
disabled={true}
id="autopilotInput"
key="auto pilot throughput input"
min={1000}
onChange={[Function]}
onGetErrorMessage={[Function]}
required={true}
step={1000}
styles={
{
"field": {
"fontSize": 14,
"fontWeight": 400,
},
"fieldGroup": {
"height": 28,
"width": 100,
},
}
}
type="number"
validateOnLoad={false}
value=""
/>
</Stack>
</Stack>
<Stack>
<Stack>
<Stack

View File

@@ -5,13 +5,13 @@ import { useDatabases } from "Explorer/useDatabases";
import React, { FunctionComponent, useEffect, useState } from "react";
import * as Constants from "../../../Common/Constants";
import { InfoTooltip } from "../../../Common/Tooltip/InfoTooltip";
import { isFabricNative } from "../../../Platform/Fabric/FabricUtil";
import * as SharedConstants from "../../../Shared/Constants";
import { userContext } from "../../../UserContext";
import { getCollectionName } from "../../../Utils/APITypeUtils";
import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
import * as PricingUtils from "../../../Utils/PricingUtils";
import "./ThroughputInput.less";
import { isFabricNative } from "../../../Platform/Fabric/FabricUtil";
export interface ThroughputInputProps {
isDatabase: boolean;
@@ -41,11 +41,12 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
let defaultThroughput: number;
const workloadType: Constants.WorkloadType = getWorkloadType();
if (
if (isFabricNative()) {
defaultThroughput = AutoPilotUtils.autoPilotThroughput5K;
} else if (
isFreeTier ||
isQuickstart ||
[Constants.WorkloadType.Learning, Constants.WorkloadType.DevelopmentTesting].includes(workloadType) ||
isFabricNative()
[Constants.WorkloadType.Learning, Constants.WorkloadType.DevelopmentTesting].includes(workloadType)
) {
defaultThroughput = AutoPilotUtils.autoPilotThroughput1K;
} else if (workloadType === Constants.WorkloadType.Production) {

View File

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

View File

@@ -3,16 +3,22 @@ import { Link } from "@fluentui/react/lib/Link";
import { isPublicInternetAccessAllowed } from "Common/DatabaseAccountUtility";
import { Environment, getEnvironment } from "Common/EnvironmentUtility";
import { sendMessage } from "Common/MessageHandler";
import { Platform, configContext } from "ConfigContext";
import { configContext, Platform } from "ConfigContext";
import { MessageTypes } from "Contracts/ExplorerContracts";
import { useDataPlaneRbac } from "Explorer/Panes/SettingsPane/SettingsPane";
import { getCopilotEnabled, isCopilotFeatureRegistered } from "Explorer/QueryCopilot/Shared/QueryCopilotClient";
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 { acquireMsalTokenForAccount } from "Utils/AuthorizationUtils";
import { allowedNotebookServerUrls, validateEndpoint } from "Utils/EndpointUtils";
import { featureRegistered } from "Utils/FeatureRegistrationUtils";
import { isFeatureSupported, PlatformFeature } from "Utils/PlatformFeatureUtils";
import { update } from "Utils/arm/generatedClients/cosmos/databaseAccounts";
import { useQueryCopilot } from "hooks/useQueryCopilot";
import * as ko from "knockout";
@@ -284,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 resourceId = encodeURIComponent(userContext.databaseAccount.id);
const database = encodeURIComponent(activeTab?.collection?.databaseId);
const container = encodeURIComponent(activeTab?.collection?.id());
const baseUrl = `vscode://ms-azuretools.vscode-cosmosdb?resourceId=${resourceId}`;
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 = {
linkProps: {
linkText: "Download Visual Studio Code",
@@ -1149,10 +1181,14 @@ export default class Explorer {
? this.refreshDatabaseForResourceToken()
: await this.refreshAllDatabases(); // await: we rely on the databases to be loaded before restoring the tabs further in the flow
}
await useNotebook.getState().refreshNotebooksEnabledStateForAccount();
if (!isFabricNative()) {
await useNotebook.getState().refreshNotebooksEnabledStateForAccount();
}
// TODO: remove reference to isNotebookEnabled and isNotebooksEnabledForAccount
const isNotebookEnabled =
isFeatureSupported(PlatformFeature.Notebooks) &&
configContext.platform !== Platform.Fabric &&
(userContext.features.notebooksDownBanner ||
useNotebook.getState().isPhoenixNotebooks ||
@@ -1160,7 +1196,11 @@ export default class Explorer {
useNotebook.getState().setIsNotebookEnabled(isNotebookEnabled);
useNotebook
.getState()
.setIsShellEnabled(useNotebook.getState().isPhoenixFeatures && isPublicInternetAccessAllowed());
.setIsShellEnabled(
isFeatureSupported(PlatformFeature.CloudShell) &&
useNotebook.getState().isPhoenixFeatures &&
isPublicInternetAccessAllowed(),
);
TelemetryProcessor.trace(Action.NotebookEnabled, ActionModifiers.Mark, {
isNotebookEnabled,
@@ -1171,7 +1211,7 @@ export default class Explorer {
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");
updateUserContext({ throughputBucketsEnabled });
}
@@ -1181,6 +1221,7 @@ export default class Explorer {
public async configureCopilot(): Promise<void> {
if (
!isFeatureSupported(PlatformFeature.Copilot) ||
userContext.apiType !== "SQL" ||
!userContext.subscriptionId ||
![Environment.Development, Environment.Mpac, Environment.Prod].includes(getEnvironment())

View File

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

View File

@@ -59,7 +59,7 @@ export interface GraphExplorerProps {
graphBackendEndpoint: string;
databaseId: string;
collectionId: string;
masterKey: string;
password: string;
onLoadStartKey: number;
onLoadStartKeyChange: (newKey: number) => void;
@@ -1300,7 +1300,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
endpoint: `wss://${this.props.graphBackendEndpoint}`,
databaseId: this.props.databaseId,
collectionId: this.props.collectionId,
masterKey: this.props.masterKey,
password: this.props.password,
maxResultSize: GraphExplorer.MAX_RESULT_SIZE,
});
}

View File

@@ -8,28 +8,28 @@ describe("Gremlin Client", () => {
endpoint: null,
collectionId: null,
databaseId: null,
masterKey: null,
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 databaseId = "databaseId";
const masterKey = "masterKey";
const testPassword = "password";
const gremlinClient = new GremlinClient();
gremlinClient.initialize({
endpoint: null,
collectionId,
databaseId,
masterKey,
maxResultSize: 0,
password: testPassword,
});
// User must includes these values
expect(gremlinClient.client.params.user.indexOf(collectionId)).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) => {

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,6 @@
import { KeyboardAction } from "KeyboardShortcuts";
import { isDataplaneRbacSupported } from "Utils/APITypeUtils";
import { areAdvancedScriptsSupported, isFeatureSupported, PlatformFeature } from "Utils/PlatformFeatureUtils";
import * as React from "react";
import { useEffect, useState } from "react";
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 { AuthType } from "../../../AuthType";
import * as Constants from "../../../Common/Constants";
import { Platform, configContext } from "../../../ConfigContext";
import { configContext, Platform } from "../../../ConfigContext";
import * as ViewModels from "../../../Contracts/ViewModels";
import { userContext } from "../../../UserContext";
import { isRunningOnNationalCloud } from "../../../Utils/CloudUtils";
@@ -52,6 +53,7 @@ export function createStaticCommandBarButtons(
};
if (
isFeatureSupported(PlatformFeature.SynapseLink) &&
configContext.platform !== Platform.Fabric &&
userContext.apiType !== "Tables" &&
userContext.apiType !== "Cassandra"
@@ -63,7 +65,9 @@ export function createStaticCommandBarButtons(
}
if (userContext.apiType !== "Gremlin") {
const addVsCode = createOpenVsCodeDialogButton(container);
buttons.push(addVsCode);
if (addVsCode) {
buttons.push(addVsCode);
}
}
}
@@ -242,11 +246,17 @@ export function createDivider(): CommandButtonComponentProps {
function areScriptsSupported(): boolean {
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 {
if (!isFeatureSupported(PlatformFeature.SynapseLink)) {
return undefined;
}
if (configContext.platform === Platform.Emulator) {
return undefined;
}
@@ -274,6 +284,10 @@ function createOpenSynapseLinkDialogButton(container: Explorer): CommandButtonCo
}
function createOpenVsCodeDialogButton(container: Explorer): CommandButtonComponentProps {
if (!isFeatureSupported(PlatformFeature.VSCodeIntegration)) {
return undefined;
}
const label = "Visual Studio Code";
return {
iconSrc: VSCodeIcon,

View File

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

View File

@@ -50,8 +50,10 @@ import * as TelemetryProcessor from "Shared/Telemetry/TelemetryProcessor";
import { userContext } from "UserContext";
import { getCollectionName } from "Utils/APITypeUtils";
import { isCapabilityEnabled, isServerlessAccount, isVectorSearchEnabled } from "Utils/CapabilityUtils";
import { isFeatureSupported, PlatformFeature } from "Utils/PlatformFeatureUtils";
import { getUpsellMessage } from "Utils/PricingUtils";
import { ValidCosmosDbIdDescription, ValidCosmosDbIdInputPattern } from "Utils/ValidationUtils";
import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
import { CollapsibleSectionComponent } from "../../Controls/CollapsiblePanel/CollapsibleSectionComponent";
import { ThroughputInput } from "../../Controls/ThroughputInput/ThroughputInput";
import { ContainerSampleGenerator } from "../../DataSamples/ContainerSampleGenerator";
@@ -60,7 +62,6 @@ import { useDatabases } from "../../useDatabases";
import { PanelFooterComponent } from "../PanelFooterComponent";
import { PanelInfoErrorComponent } from "../PanelInfoErrorComponent";
import { PanelLoadingScreen } from "../PanelLoadingScreen";
import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
export interface AddCollectionPanelProps {
explorer: Explorer;
@@ -123,7 +124,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
isSharded: userContext.apiType !== "Tables",
partitionKey: getPartitionKey(props.isQuickstart),
subPartitionKeys: [],
enableDedicatedThroughput: false,
enableDedicatedThroughput: isFabricNative(), // Dedicated throughput is only enabled in Fabric Native by default
createMongoWildCardIndex:
isCapabilityEnabled("EnableMongo") && !isCapabilityEnabled("EnableMongo16MBDocumentSupport"),
useHashV1: false,
@@ -336,7 +337,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
size={40}
className="panelTextField"
aria-label="New database id, Type a new database id"
autoFocus
tabIndex={0}
value={this.state.newDatabaseId}
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
@@ -407,9 +407,9 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
responsiveMode={999}
/>
)}
<Separator className="panelSeparator" style={{ marginTop: -4, marginBottom: -4 }} />
</Stack>
)}
<Separator className="panelSeparator" style={{ marginTop: -4, marginBottom: -4 }} />
<Stack>
<Stack horizontal style={{ marginTop: -5, marginBottom: 1 }}>
@@ -449,8 +449,9 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
this.setState({ collectionId: event.target.value })
}
/>
<Separator className="panelSeparator" style={{ marginTop: -5, marginBottom: -5 }} />
</Stack>
<Separator className="panelSeparator" style={{ marginTop: -5, marginBottom: -5 }} />
{this.shouldShowIndexingOptionsForFreeTierAccount() && (
<Stack>
<Stack horizontal style={{ marginTop: -4, marginBottom: -5 }}>
@@ -645,7 +646,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
</Stack>
);
})}
{!isFabricNative() && userContext.apiType === "SQL" && (
{userContext.apiType === "SQL" && (
<Stack className="panelGroupSpacing">
<DefaultButton
styles={{ root: { padding: 0, width: 200, height: 30 }, label: { fontSize: 12 } }}
@@ -709,7 +710,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
</Stack>
)}
{this.shouldShowCollectionThroughputInput() && (
{this.shouldShowCollectionThroughputInput() && !isFabricNative() && (
<ThroughputInput
showFreeTierExceedThroughputTooltip={isFreeTierAccount() && !isFirstResourceCreated}
isDatabase={false}
@@ -727,7 +728,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
/>
)}
{!isFabricNative() && userContext.apiType === "SQL" && (
{isFeatureSupported(PlatformFeature.UniqueKeys) && !isFabricNative() && userContext.apiType === "SQL" && (
<Stack style={{ marginTop: -2, marginBottom: -4 }}>
{UniqueKeysHeader()}
{this.state.uniqueKeys.map((uniqueKey: string, i: number): JSX.Element => {
@@ -742,7 +743,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
: "Comma separated paths e.g. /firstName,/address/zipCode"
}
className="panelTextField"
autoFocus
value={uniqueKey}
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
const uniqueKeys = this.state.uniqueKeys.map((uniqueKey: string, j: number) => {
@@ -777,7 +777,9 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
</Stack>
)}
<Separator className="panelSeparator" style={{ marginTop: -15, marginBottom: -4 }} />
{!isFabricNative() && userContext.apiType === "SQL" && (
<Separator className="panelSeparator" style={{ marginTop: -15, marginBottom: -4 }} />
)}
{shouldShowAnalyticalStoreOptions() && (
<Stack className="panelGroupSpacing" style={{ marginTop: -4 }}>
@@ -899,78 +901,86 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
</CollapsibleSectionComponent>
</Stack>
)}
{!isFabricNative() && userContext.apiType !== "Tables" && (
<CollapsibleSectionComponent
title="Advanced"
isExpandedByDefault={false}
onExpand={() => {
TelemetryProcessor.traceOpen(Action.ExpandAddCollectionPaneAdvancedSection);
scrollToSection("collapsibleAdvancedSectionContent");
}}
>
<Stack className="panelGroupSpacing" id="collapsibleAdvancedSectionContent">
{isCapabilityEnabled("EnableMongo") && !isCapabilityEnabled("EnableMongo16MBDocumentSupport") && (
<Stack className="panelGroupSpacing">
<Stack horizontal>
<span className="mandatoryStar">*&nbsp;</span>
<Text className="panelTextBold" variant="small">
Indexing
</Text>
<TooltipHost
directionalHint={DirectionalHint.bottomLeftEdge}
content="The _id field is indexed by default. Creating a wildcard index for all fields will optimize queries and is recommended for development."
>
<Icon
iconName="Info"
className="panelInfoIcon"
tabIndex={0}
ariaLabel="The _id field is indexed by default. Creating a wildcard index for all fields will optimize queries and is recommended for development."
/>
</TooltipHost>
{isFeatureSupported(PlatformFeature.AdvancedContainerSettings) &&
!isFabricNative() &&
userContext.apiType !== "Tables" && (
<CollapsibleSectionComponent
title="Advanced"
isExpandedByDefault={false}
onExpand={() => {
TelemetryProcessor.traceOpen(Action.ExpandAddCollectionPaneAdvancedSection);
scrollToSection("collapsibleAdvancedSectionContent");
}}
>
<Stack className="panelGroupSpacing" id="collapsibleAdvancedSectionContent">
{isCapabilityEnabled("EnableMongo") && !isCapabilityEnabled("EnableMongo16MBDocumentSupport") && (
<Stack className="panelGroupSpacing">
<Stack horizontal>
<span className="mandatoryStar">*&nbsp;</span>
<Text className="panelTextBold" variant="small">
Indexing
</Text>
<TooltipHost
directionalHint={DirectionalHint.bottomLeftEdge}
content="The _id field is indexed by default. Creating a wildcard index for all fields will optimize queries and is recommended for development."
>
<Icon
iconName="Info"
className="panelInfoIcon"
tabIndex={0}
ariaLabel="The _id field is indexed by default. Creating a wildcard index for all fields will optimize queries and is recommended for development."
/>
</TooltipHost>
</Stack>
<Checkbox
label="Create a Wildcard Index on all fields"
checked={this.state.createMongoWildCardIndex}
styles={{
text: { fontSize: 12 },
checkbox: { width: 12, height: 12 },
label: { padding: 0, alignItems: "center" },
}}
onChange={(ev: React.FormEvent<HTMLElement>, isChecked: boolean) =>
this.setState({ createMongoWildCardIndex: isChecked })
}
/>
</Stack>
)}
<Checkbox
label="Create a Wildcard Index on all fields"
checked={this.state.createMongoWildCardIndex}
styles={{
text: { fontSize: 12 },
checkbox: { width: 12, height: 12 },
label: { padding: 0, alignItems: "center" },
}}
onChange={(ev: React.FormEvent<HTMLElement>, isChecked: boolean) =>
this.setState({ createMongoWildCardIndex: isChecked })
}
/>
</Stack>
)}
{userContext.apiType === "SQL" && (
<Stack className="panelGroupSpacing">
<Checkbox
label="My application uses an older Cosmos .NET or Java SDK version (.NET V1 or Java V2)"
checked={this.state.useHashV1}
styles={{
text: { fontSize: 12 },
checkbox: { width: 12, height: 12 },
label: { padding: 0, alignItems: "center", wordWrap: "break-word", whiteSpace: "break-spaces" },
}}
onChange={(ev: React.FormEvent<HTMLElement>, isChecked: boolean) =>
this.setState({ useHashV1: isChecked, subPartitionKeys: [] })
}
/>
<Text variant="small">
<Icon iconName="InfoSolid" className="removeIcon" /> To ensure compatibility with older SDKs, the
created container will use a legacy partitioning scheme that supports partition key values of size
only up to 101 bytes. If this is enabled, you will not be able to use hierarchical partition keys.{" "}
<Link href="https://aka.ms/cosmos-large-pk" target="_blank">
Learn more
</Link>
</Text>
</Stack>
)}
</Stack>
</CollapsibleSectionComponent>
)}
{userContext.apiType === "SQL" && (
<Stack className="panelGroupSpacing">
<Checkbox
label="My application uses an older Cosmos .NET or Java SDK version (.NET V1 or Java V2)"
checked={this.state.useHashV1}
styles={{
text: { fontSize: 12 },
checkbox: { width: 12, height: 12 },
label: {
padding: 0,
alignItems: "center",
wordWrap: "break-word",
whiteSpace: "break-spaces",
},
}}
onChange={(ev: React.FormEvent<HTMLElement>, isChecked: boolean) =>
this.setState({ useHashV1: isChecked, subPartitionKeys: [] })
}
/>
<Text variant="small">
<Icon iconName="InfoSolid" className="removeIcon" /> To ensure compatibility with older SDKs,
the created container will use a legacy partitioning scheme that supports partition key values
of size only up to 101 bytes. If this is enabled, you will not be able to use hierarchical
partition keys.{" "}
<Link href="https://aka.ms/cosmos-large-pk" target="_blank">
Learn more
</Link>
</Text>
</Stack>
)}
</Stack>
</CollapsibleSectionComponent>
)}
</div>
<PanelFooterComponent buttonLabel="OK" isButtonDisabled={this.state.isThroughputCapExceeded} />
@@ -1133,7 +1143,11 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
// }
private shouldShowCollectionThroughputInput(): boolean {
if (isFabricNative() || isServerlessAccount()) {
if (!isFeatureSupported(PlatformFeature.ContainerThroughput)) {
return false;
}
if (isServerlessAccount()) {
return false;
}
@@ -1159,11 +1173,15 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
}
private shouldShowVectorSearchParameters() {
return isVectorSearchEnabled() && (isServerlessAccount() || this.shouldShowCollectionThroughputInput());
return (
isFeatureSupported(PlatformFeature.VectorSearch) &&
isVectorSearchEnabled() &&
(isServerlessAccount() || this.shouldShowCollectionThroughputInput())
);
}
private shouldShowFullTextSearchParameters() {
return !isFabricNative() && this.showFullTextSearch;
return isFeatureSupported(PlatformFeature.FullTextSearch) && !isFabricNative() && this.showFullTextSearch;
}
private parseUniqueKeys(): DataModels.UniqueKeyPolicy {
@@ -1354,8 +1372,8 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
// Throughput
if (isFabricNative()) {
// Fabric Native accounts are always autoscale and have a fixed throughput of 1K
autoPilotMaxThroughput = AutoPilotUtils.autoPilotThroughput1K;
// 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) {

View File

@@ -6,6 +6,7 @@ import { getFullTextLanguageOptions } from "Explorer/Controls/FullTextSeach/Full
import { isFabricNative } from "Platform/Fabric/FabricUtil";
import React from "react";
import { userContext } from "UserContext";
import { isFeatureSupported, PlatformFeature } from "Utils/PlatformFeatureUtils";
export function getPartitionKeyTooltipText(): string {
if (userContext.apiType === "Mongo") {
@@ -85,7 +86,11 @@ export function UniqueKeysHeader(): JSX.Element {
}
export function shouldShowAnalyticalStoreOptions(): boolean {
if (isFabricNative() || configContext.platform === Platform.Emulator) {
if (
!isFeatureSupported(PlatformFeature.AnalyticalStore) ||
isFabricNative() ||
configContext.platform === Platform.Emulator
) {
return false;
}

View File

@@ -93,7 +93,6 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
aria-label="New database id, Type a new database id"
aria-required={true}
autoComplete="off"
autoFocus={true}
className="panelTextField"
id="newDatabaseId"
name="newDatabaseId"
@@ -143,16 +142,16 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
</StyledTooltipHostBase>
</Stack>
</Stack>
</Stack>
<Separator
className="panelSeparator"
style={
{
"marginBottom": -4,
"marginTop": -4,
<Separator
className="panelSeparator"
style={
{
"marginBottom": -4,
"marginTop": -4,
}
}
}
/>
/>
</Stack>
<Stack>
<Stack
horizontal={true}
@@ -203,16 +202,16 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
type="text"
value=""
/>
</Stack>
<Separator
className="panelSeparator"
style={
{
"marginBottom": -5,
"marginTop": -5,
<Separator
className="panelSeparator"
style={
{
"marginBottom": -5,
"marginTop": -5,
}
}
}
/>
/>
</Stack>
<Stack>
<Stack
horizontal={true}

View File

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

View File

@@ -202,7 +202,7 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
const styles = useStyles();
const explorerVersion = configContext.gitSha;
const isEmulator = configContext.platform === Platform.Emulator;
const isEmulator = configContext.platform === Platform.Emulator || configContext.platform === Platform.VNextEmulator;
const shouldShowQueryPageOptions = userContext.apiType === "SQL";
const showRetrySettings =
(userContext.apiType === "SQL" || userContext.apiType === "Tables" || userContext.apiType === "Gremlin") &&
@@ -433,9 +433,6 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
);
}
logConsoleInfo(
`Updated query setting to ${LocalStorageUtility.getEntryString(StorageKey.SetPartitionKeyUndefined)}`,
);
refreshExplorer && (await explorer.refreshExplorer());
closeSidePanel();
};

View File

@@ -44,32 +44,26 @@ export const startCloudShellTerminal = async (terminal: Terminal, shellType: Ter
resolvedRegion = determineCloudShellRegion();
resolvedRegion = determineCloudShellRegion();
terminal.writeln(formatWarningMessage("⚠️ IMPORTANT: Azure Cloud Shell Region Notice ⚠️"));
terminal.writeln(
formatInfoMessage(
"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(" 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(
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("\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");
// Ask for user consent for region

View File

@@ -258,14 +258,7 @@ Key limitations:
### 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:
| 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 |
Data residency requirements may not be fully satisfied when using CloudShell due to limited regional availability.
**Note:** For up-to-date supported regions, refer to the region configuration in:
`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
class MockShellHandler extends AbstractShellHandler {
@@ -18,8 +18,8 @@ class MockShellHandler extends AbstractShellHandler {
return "mock-endpoint";
}
getTerminalSuppressedData(): string {
return "suppressed-data";
getTerminalSuppressedData(): string[] {
return ["suppressed-data"];
}
}
@@ -90,7 +90,7 @@ describe("AbstractShellHandler", () => {
});
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.
* 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
@@ -31,7 +37,8 @@ export abstract class AbstractShellHandler {
abstract getShellName(): string;
abstract getSetUpCommands(): string[];
abstract getConnectionCommand(): string;
abstract getTerminalSuppressedData(): string;
abstract getTerminalSuppressedData(): string[];
updateTerminalData?(data: string): string;
/**
* Constructs the complete initialization command sequence for the shell.
@@ -77,7 +84,7 @@ export abstract class AbstractShellHandler {
* is not already present in the environment.
*/
protected mongoShellSetupCommands(): string[] {
const PACKAGE_VERSION: string = "2.5.0";
const PACKAGE_VERSION: string = "2.5.5";
return [
"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`,
@@ -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 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",
"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", () => {
expect(handler.getTerminalSuppressedData()).toBe("");
expect(handler.getTerminalSuppressedData()).toEqual([""]);
});
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`;
}
public getTerminalSuppressedData(): string {
return "";
public getTerminalSuppressedData(): string[] {
return [""];
}
}

View File

@@ -69,7 +69,7 @@ describe("MongoShellHandler", () => {
expect(Array.isArray(commands)).toBe(true);
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();
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/");
@@ -124,7 +124,7 @@ describe("MongoShellHandler", () => {
describe("getTerminalSuppressedData", () => {
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 { getHostFromUrl } from "../Utils/CommonUtils";
import { AbstractShellHandler } from "./AbstractShellHandler";
import { AbstractShellHandler, DISABLE_TELEMETRY_COMMAND } from "./AbstractShellHandler";
export class MongoShellHandler extends AbstractShellHandler {
private _key: string;
@@ -29,6 +29,8 @@ export class MongoShellHandler extends AbstractShellHandler {
return "echo 'Database name not found.'";
}
return (
DISABLE_TELEMETRY_COMMAND +
" && " +
"mongosh mongodb://" +
getHostFromUrl(this._endpoint) +
":10255?appName=" +
@@ -41,7 +43,7 @@ export class MongoShellHandler extends AbstractShellHandler {
);
}
public getTerminalSuppressedData(): string {
return "Warning: Non-Genuine MongoDB Detected";
public getTerminalSuppressedData(): string[] {
return ["Warning: Non-Genuine MongoDB Detected"];
}
}

View File

@@ -58,7 +58,7 @@ describe("PostgresShellHandler", () => {
});
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}`;
}
public getTerminalSuppressedData(): string {
return "";
public getTerminalSuppressedData(): string[] {
return [""];
}
}

View File

@@ -45,7 +45,7 @@ describe("VCoreMongoShellHandler", () => {
expect(Array.isArray(commands)).toBe(true);
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");
});
@@ -57,7 +57,10 @@ describe("VCoreMongoShellHandler", () => {
});
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 { AbstractShellHandler } from "./AbstractShellHandler";
import { AbstractShellHandler, DISABLE_TELEMETRY_COMMAND } from "./AbstractShellHandler";
export class VCoreMongoShellHandler extends AbstractShellHandler {
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() {
super();
@@ -23,10 +28,22 @@ export class VCoreMongoShellHandler extends AbstractShellHandler {
}
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 {
return "Warning: Non-Genuine MongoDB Detected";
public getTerminalSuppressedData(): string[] {
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) {
const updatedData = this._shellHandler?.updateTerminalData(data) ?? data;
const suppressedData = this._shellHandler?.getTerminalSuppressedData();
const hasSuppressedData = suppressedData && suppressedData.length > 0;
if (!hasSuppressedData || !data.includes(suppressedData)) {
terminal.write(data);
const shouldNotWrite = suppressedData.filter(Boolean).some((item) => updatedData.includes(item));
if (!shouldNotWrite) {
terminal.write(updatedData);
}
}

View File

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

View File

@@ -8,6 +8,7 @@ import {
import { useNotebook } from "Explorer/Notebook/useNotebook";
import { DocumentsTabV2 } from "Explorer/Tabs/DocumentsTabV2/DocumentsTabV2";
import { isFabricMirrored } from "Platform/Fabric/FabricUtil";
import { useDataplaneRbacAuthorization } from "Utils/AuthorizationUtils";
import * as ko from "knockout";
import * as _ from "underscore";
import * as Constants from "../../Common/Constants";
@@ -479,9 +480,8 @@ export default class Collection implements ViewModels.Collection {
node: this,
title: title,
tabPath: "",
password: useDataplaneRbacAuthorization(userContext) ? userContext.aadToken : userContext.masterKey || "",
collection: this,
masterKey: userContext.masterKey || "",
collectionPartitionKeyProperty: this.partitionKeyProperties?.[0],
collectionId: this.id(),
databaseId: this.databaseId,
@@ -737,7 +737,7 @@ export default class Collection implements ViewModels.Collection {
title: title,
tabPath: "",
collection: this,
masterKey: userContext.masterKey || "",
password: useDataplaneRbacAuthorization(userContext) ? userContext.aadToken : userContext.masterKey || "",
collectionPartitionKeyProperty: this.partitionKeyProperties?.[0],
collectionId: this.id(),
databaseId: this.databaseId,

View File

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

View File

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

View File

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

View File

@@ -252,7 +252,7 @@ export class PhoenixClient {
private getPhoenixControlPlanePathPrefix(): string {
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 =

View File

@@ -111,7 +111,7 @@ export function extractFeatures(given = new URLSearchParams(window.location.sear
copilotChatFixedMonacoEditorHeight: "true" === get("copilotchatfixedmonacoeditorheight"),
enablePriorityBasedExecution: "true" === get("enableprioritybasedexecution"),
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,
path: getOfferingIdPathForRegion(),
method: "GET",
apiVersion: "2023-05-01-preview",
apiVersion: configContext.CATALOG_API_VERSION,
queryParams: {
filter:
"armRegionNameeq '" +

View File

@@ -91,5 +91,5 @@ export const getItemName = (): string => {
};
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 * as Constants from "../Common/Constants";
import { updateUserContext } from "../UserContext";
import { ApiType, updateUserContext, userContext } from "../UserContext";
import * as AuthorizationUtils from "./AuthorizationUtils";
jest.mock("../Explorer/Explorer");
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()", () => {
it("should return authorization header if authentication type is AAD", () => {
updateUserContext({
@@ -54,4 +95,41 @@ describe("AuthorizationUtils", () => {
).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 { Action, ActionModifiers } from "Shared/Telemetry/TelemetryConstants";
import { isDataplaneRbacSupported } from "Utils/APITypeUtils";
import { AuthType } from "../AuthType";
import * as Constants from "../Common/Constants";
import * as Logger from "../Common/Logger";
@@ -7,7 +8,7 @@ import { configContext } from "../ConfigContext";
import { DatabaseAccount } from "../Contracts/DataModels";
import * as ViewModels from "../Contracts/ViewModels";
import { trace, traceFailure } from "../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../UserContext";
import { UserContext, userContext } from "../UserContext";
export function getAuthorizationHeader(): ViewModels.AuthorizationTokenHeaderMetadata {
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 autoPilotIncrementStep = 1000;
export const autoPilotThroughput4K = 4000;
export const autoPilotThroughput5K = 5000;
export const autoPilotThroughput10K = 10000;
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 { userContext } from "../UserContext";
@@ -18,5 +19,8 @@ export const isServerlessAccount = (): boolean => {
};
export const isVectorSearchEnabled = (): boolean => {
return userContext.apiType === "SQL" && isCapabilityEnabled(Constants.CapabilityNames.EnableNoSQLVectorSearch);
return (
userContext.apiType === "SQL" &&
(isCapabilityEnabled(Constants.CapabilityNames.EnableNoSQLVectorSearch) || isFabricNative())
);
};

View File

@@ -45,32 +45,25 @@ export const defaultAllowedArmEndpoints: ReadonlyArray<string> = [
"https://management.chinacloudapi.cn",
];
export const allowedAadEndpoints: ReadonlyArray<string> = [
export const defaultAllowedAadEndpoints: ReadonlyArray<string> = [
"https://login.microsoftonline.com/",
"https://login.microsoftonline.us/",
"https://login.partner.microsoftonline.cn/",
];
export const defaultAllowedGraphEndpoints: ReadonlyArray<string> = ["https://graph.microsoft.com"];
export const defaultAllowedBackendEndpoints: ReadonlyArray<string> = [
"https://localhost:12901",
"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> = [
"https://localhost:1234",
MongoProxyEndpoints.Development,
MongoProxyEndpoints.Mpac,
MongoProxyEndpoints.Prod,
@@ -86,6 +79,14 @@ export const defaultAllowedCassandraProxyEndpoints: ReadonlyArray<string> = [
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[] } = {
[CassandraProxyEndpoints.Mpac]: ["40.113.96.14", "104.42.11.145"],
[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"],
};
export const allowedEmulatorEndpoints: ReadonlyArray<string> = ["https://localhost:8081"];
export const allowedMongoBackendEndpoints: ReadonlyArray<string> = ["https://localhost:1234"];
export const allowedGraphEndpoints: ReadonlyArray<string> = ["https://graph.microsoft.com"];
export const allowedEmulatorEndpoints: ReadonlyArray<string> = ["https://localhost:8081", "http://localhost:8081"];
export const allowedArcadiaEndpoints: ReadonlyArray<string> = ["https://workspaceartifacts.projectarcadia.net"];

View File

@@ -0,0 +1,116 @@
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",
UniqueKeys = "UniqueKeys",
ContainerThroughput = "ContainerThroughput",
AdvancedContainerSettings = "AdvancedContainerSettings",
// 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.UniqueKeys,
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,5 @@
import { PartitionKey, PartitionKeyDefinition } from "@azure/cosmos";
import { configContext, Platform } from "ConfigContext";
import { getRUThreshold, ruThresholdEnabled } from "Shared/StorageUtility";
import { userContext } from "UserContext";
import { logConsoleWarning } from "Utils/NotificationConsoleUtils";
@@ -66,7 +67,11 @@ export function buildDocumentsQueryPartitionProjections(
}
});
const fullAccess = `${collectionAlias}${projectedProperty}`;
if (!isSystemPartitionKey) {
if (
!isSystemPartitionKey &&
configContext.platform !== Platform.Emulator &&
configContext.platform !== Platform.VNextEmulator
) {
const wrappedProjection = `IIF(IS_DEFINED(${fullAccess}), ${fullAccess}, {})`;
projections.push(wrappedProjection);
} else {

View File

@@ -3,13 +3,13 @@
Run "npm run generateARMClients" to regenerate
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 * 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. */
export async function listCassandraKeyspaces(

View File

@@ -3,13 +3,13 @@
Run "npm run generateARMClients" to regenerate
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 * as Types from "./types";
const apiVersion = "2024-12-01-preview";
import { configContext } from "../../../../ConfigContext";
const apiVersion = "2025-05-01-preview";
/* Retrieves the metrics determined by the given filter for the given database account and collection. */
export async function listMetrics(

View File

@@ -3,13 +3,13 @@
Run "npm run generateARMClients" to regenerate
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 * as Types from "./types";
const apiVersion = "2024-12-01-preview";
import { configContext } from "../../../../ConfigContext";
const apiVersion = "2025-05-01-preview";
/* Retrieves the metrics determined by the given filter for the given collection, split by partition. */
export async function listMetrics(

View File

@@ -3,13 +3,13 @@
Run "npm run generateARMClients" to regenerate
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 * as Types from "./types";
const apiVersion = "2024-12-01-preview";
import { configContext } from "../../../../ConfigContext";
const apiVersion = "2025-05-01-preview";
/* Retrieves the metrics determined by the given filter for the given collection and region, split by partition. */
export async function listMetrics(

View File

@@ -3,13 +3,13 @@
Run "npm run generateARMClients" to regenerate
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 * as Types from "./types";
const apiVersion = "2024-12-01-preview";
import { configContext } from "../../../../ConfigContext";
const apiVersion = "2025-05-01-preview";
/* Retrieves the metrics determined by the given filter for the given database account, collection and region. */
export async function listMetrics(

View File

@@ -3,13 +3,13 @@
Run "npm run generateARMClients" to regenerate
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 * as Types from "./types";
const apiVersion = "2024-12-01-preview";
import { configContext } from "../../../../ConfigContext";
const apiVersion = "2025-05-01-preview";
/* Retrieves the metrics determined by the given filter for the given database account and database. */
export async function listMetrics(

View File

@@ -3,13 +3,13 @@
Run "npm run generateARMClients" to regenerate
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 * as Types from "./types";
const apiVersion = "2024-12-01-preview";
import { configContext } from "../../../../ConfigContext";
const apiVersion = "2025-05-01-preview";
/* Retrieves the metrics determined by the given filter for the given database account and region. */
export async function listMetrics(

View File

@@ -3,13 +3,13 @@
Run "npm run generateARMClients" to regenerate
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 * as Types from "./types";
const apiVersion = "2024-12-01-preview";
import { configContext } from "../../../../ConfigContext";
const apiVersion = "2025-05-01-preview";
/* Retrieves the properties of an existing Azure Cosmos DB database account. */
export async function get(

View File

@@ -3,13 +3,13 @@
Run "npm run generateARMClients" to regenerate
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 * as Types from "./types";
const apiVersion = "2024-12-01-preview";
import { configContext } from "../../../../ConfigContext";
const apiVersion = "2025-05-01-preview";
/* Lists the graphs under an existing Azure Cosmos DB database account. */
export async function listGraphs(

View File

@@ -3,13 +3,13 @@
Run "npm run generateARMClients" to regenerate
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 * as Types from "./types";
const apiVersion = "2024-12-01-preview";
import { configContext } from "../../../../ConfigContext";
const apiVersion = "2025-05-01-preview";
/* Lists the Gremlin databases under an existing Azure Cosmos DB database account. */
export async function listGremlinDatabases(

View File

@@ -3,13 +3,13 @@
Run "npm run generateARMClients" to regenerate
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 * as Types from "./types";
const apiVersion = "2024-12-01-preview";
import { configContext } from "../../../../ConfigContext";
const apiVersion = "2025-05-01-preview";
/* List Cosmos DB locations and their properties */
export async function list(subscriptionId: string): Promise<Types.LocationListResult | Types.CloudError> {

View File

@@ -3,13 +3,13 @@
Run "npm run generateARMClients" to regenerate
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 * as Types from "./types";
const apiVersion = "2024-12-01-preview";
import { configContext } from "../../../../ConfigContext";
const apiVersion = "2025-05-01-preview";
/* Lists the MongoDB databases under an existing Azure Cosmos DB database account. */
export async function listMongoDBDatabases(

View File

@@ -3,13 +3,13 @@
Run "npm run generateARMClients" to regenerate
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 * as Types from "./types";
const apiVersion = "2024-12-01-preview";
import { configContext } from "../../../../ConfigContext";
const apiVersion = "2025-05-01-preview";
/* Lists all of the available Cosmos DB Resource Provider operations. */
export async function list(): Promise<Types.OperationListResult> {

View File

@@ -3,13 +3,13 @@
Run "npm run generateARMClients" to regenerate
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 * as Types from "./types";
const apiVersion = "2024-12-01-preview";
import { configContext } from "../../../../ConfigContext";
const apiVersion = "2025-05-01-preview";
/* Retrieves the metrics determined by the given filter for the given partition key range id. */
export async function listMetrics(

View File

@@ -3,13 +3,13 @@
Run "npm run generateARMClients" to regenerate
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 * as Types from "./types";
const apiVersion = "2024-12-01-preview";
import { configContext } from "../../../../ConfigContext";
const apiVersion = "2025-05-01-preview";
/* Retrieves the metrics determined by the given filter for the given partition key range id and region. */
export async function listMetrics(

View File

@@ -3,13 +3,13 @@
Run "npm run generateARMClients" to regenerate
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 * as Types from "./types";
const apiVersion = "2024-12-01-preview";
import { configContext } from "../../../../ConfigContext";
const apiVersion = "2025-05-01-preview";
/* Retrieves the metrics determined by the given filter for the given database account. This url is only for PBS and Replication Latency data */
export async function listMetrics(

View File

@@ -3,13 +3,13 @@
Run "npm run generateARMClients" to regenerate
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 * as Types from "./types";
const apiVersion = "2024-12-01-preview";
import { configContext } from "../../../../ConfigContext";
const apiVersion = "2025-05-01-preview";
/* Retrieves the metrics determined by the given filter for the given account, source and target region. This url is only for PBS and Replication Latency data */
export async function listMetrics(

View File

@@ -3,13 +3,13 @@
Run "npm run generateARMClients" to regenerate
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 * as Types from "./types";
const apiVersion = "2024-12-01-preview";
import { configContext } from "../../../../ConfigContext";
const apiVersion = "2025-05-01-preview";
/* Retrieves the metrics determined by the given filter for the given account target region. This url is only for PBS and Replication Latency data */
export async function listMetrics(

View File

@@ -3,13 +3,13 @@
Run "npm run generateARMClients" to regenerate
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 * as Types from "./types";
const apiVersion = "2024-12-01-preview";
import { configContext } from "../../../../ConfigContext";
const apiVersion = "2025-05-01-preview";
/* Lists the SQL databases under an existing Azure Cosmos DB database account. */
export async function listSqlDatabases(

View File

@@ -3,13 +3,13 @@
Run "npm run generateARMClients" to regenerate
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 * as Types from "./types";
const apiVersion = "2024-12-01-preview";
import { configContext } from "../../../../ConfigContext";
const apiVersion = "2025-05-01-preview";
/* Lists the Tables under an existing Azure Cosmos DB database account. */
export async function listTables(

View File

@@ -3,7 +3,7 @@
Run "npm run generateARMClients" to regenerate
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
*/
/* The List operation response, that contains the client encryption keys and their properties. */
@@ -580,6 +580,8 @@ export interface DatabaseAccountGetProperties {
/* Flag to indicate enabling/disabling of Per-Region Per-partition autoscale Preview feature on the account */
enablePerRegionPerPartitionAutoscale?: boolean;
/* Flag to indicate if All Versions and Deletes Change feed feature is enabled on the account */
enableAllVersionsAndDeletesChangeFeed?: boolean;
}
/* Properties to create and update Azure Cosmos DB database accounts. */
@@ -682,6 +684,8 @@ export interface DatabaseAccountCreateUpdateProperties {
/* Flag to indicate enabling/disabling of Per-Region Per-partition autoscale Preview feature on the account */
enablePerRegionPerPartitionAutoscale?: boolean;
/* Flag to indicate if All Versions and Deletes Change feed feature is enabled on the account */
enableAllVersionsAndDeletesChangeFeed?: boolean;
}
/* Parameters to create and update Cosmos DB database accounts. */
@@ -787,6 +791,8 @@ export interface DatabaseAccountUpdateProperties {
/* Flag to indicate enabling/disabling of Per-Region Per-partition autoscale Preview feature on the account */
enablePerRegionPerPartitionAutoscale?: boolean;
/* Flag to indicate if All Versions and Deletes Change feed feature is enabled on the account */
enableAllVersionsAndDeletesChangeFeed?: boolean;
}
/* Parameters for patching Azure Cosmos DB database account properties. */
@@ -1216,6 +1222,8 @@ export interface PhysicalPartitionThroughputInfoResource {
id: string;
/* Throughput of a physical partition */
throughput?: number;
/* Target throughput of a physical partition */
targetThroughput?: number;
}
/* Cosmos DB client encryption key resource object. */
@@ -1285,12 +1293,16 @@ export interface SqlContainerResource {
/* The configuration for defining Materialized Views. This must be specified only for creating a Materialized View container. */
materializedViewDefinition?: MaterializedViewDefinition;
/* Materialized Views defined on the container. */
materializedViews?: MaterializedViewDetails[];
/* List of computed properties */
computedProperties?: ComputedProperty[];
/* The vector embedding policy for the container. */
vectorEmbeddingPolicy?: VectorEmbeddingPolicy;
/* The FullText policy for the container. */
fullTextPolicy?: FullTextPolicy;
}
@@ -1323,6 +1335,14 @@ export interface VectorEmbeddingPolicy {
vectorEmbeddings?: VectorEmbedding[];
}
/* Cosmos DB FullText Policy */
export interface FullTextPolicy {
/* The default language for a full text paths. */
defaultLanguage?: string;
/* List of FullText Paths */
fullTextPaths?: FullTextPath[];
}
/* undocumented */
export interface ExcludedPath {
/* The path for which the indexing behavior applies to. Index paths typically start with root and end with wildcard (/path/*) */
@@ -1361,7 +1381,7 @@ export interface VectorEmbedding {
/* The path to the vector field in the document. */
path: string;
/* Indicates the data type of vector. */
dataType: "float16" | "float32" | "uint8" | "int8";
dataType: "float32" | "uint8" | "int8";
/* The distance function to use for distance calculation in between vectors. */
distanceFunction: "euclidean" | "cosine" | "dotproduct";
@@ -1370,26 +1390,12 @@ export interface VectorEmbedding {
dimensions: number;
}
export interface FullTextPolicy {
/**
* The default language for the full text .
*/
defaultLanguage: string;
/**
* The paths to be indexed for full text search.
*/
fullTextPaths: FullTextPath[];
}
/* Represents the full text path specification. */
export interface FullTextPath {
/**
* The path to be indexed for full text search.
*/
/* The path to the full text field in the document. */
path: string;
/**
* The language for the full text path.
*/
language: string;
/* The language of the full text field in the document. */
language?: string;
}
/* List of composite path */
@@ -1493,6 +1499,14 @@ export interface MaterializedViewDefinition {
definition: string;
}
/* MaterializedViewDetails, contains Id & _rid fields of materialized view. */
export interface MaterializedViewDetails {
/* Id field of Materialized container. */
id?: string;
/* _rid field of Materialized container. */
_rid?: string;
}
/* Cosmos DB SQL storedProcedure resource object */
export interface SqlStoredProcedureResource {
/* Name of the Cosmos DB SQL storedProcedure */

View File

@@ -1,12 +1,9 @@
import * as msal from "@azure/msal-browser";
import { useBoolean } from "@fluentui/react-hooks";
import * as React from "react";
import { configContext } from "../ConfigContext";
import { ConfigContext } from "../ConfigContext";
import { acquireTokenWithMsal, getMsalInstance } from "../Utils/AuthorizationUtils";
const msalInstance = await getMsalInstance();
const cachedAccount = msalInstance.getAllAccounts()?.[0];
const cachedTenantId = localStorage.getItem("cachedTenantId");
interface ReturnType {
@@ -27,57 +24,97 @@ export interface AadAuthFailure {
failureLinkAction?: () => void;
}
export function useAADAuth(): ReturnType {
const [isLoggedIn, { setTrue: setLoggedIn, setFalse: setLoggedOut }] = useBoolean(
Boolean(cachedAccount && cachedTenantId) || false,
);
const [account, setAccount] = React.useState<msal.AccountInfo>(cachedAccount);
export function useAADAuth(config?: ConfigContext): ReturnType {
const [msalInstance, setMsalInstance] = React.useState<msal.PublicClientApplication | null>(null);
const [isLoggedIn, { setTrue: setLoggedIn, setFalse: setLoggedOut }] = useBoolean(false);
const [account, setAccount] = React.useState<msal.AccountInfo>(null);
const [tenantId, setTenantId] = React.useState<string>(cachedTenantId);
const [graphToken, setGraphToken] = React.useState<string>();
const [armToken, setArmToken] = React.useState<string>();
const [authFailure, setAuthFailure] = React.useState<AadAuthFailure>(undefined);
msalInstance.setActiveAccount(account);
const login = React.useCallback(async () => {
const response = await msalInstance.loginPopup({
redirectUri: configContext.msalRedirectURI,
scopes: [],
});
setLoggedIn();
setAccount(response.account);
setTenantId(response.tenantId);
localStorage.setItem("cachedTenantId", response.tenantId);
}, []);
const logout = React.useCallback(() => {
setLoggedOut();
localStorage.removeItem("cachedTenantId");
msalInstance.logoutRedirect();
}, []);
const switchTenant = React.useCallback(
async (id) => {
const response = await msalInstance.loginPopup({
redirectUri: configContext.msalRedirectURI,
authority: `${configContext.AAD_ENDPOINT}${id}`,
scopes: [],
// Initialize MSAL instance when config is available
React.useEffect(() => {
if (config && !msalInstance) {
getMsalInstance().then((instance) => {
setMsalInstance(instance);
const cachedAccount = instance.getAllAccounts()?.[0];
if (cachedAccount && cachedTenantId) {
setAccount(cachedAccount);
setLoggedIn();
instance.setActiveAccount(cachedAccount);
}
});
setTenantId(response.tenantId);
setAccount(response.account);
localStorage.setItem("cachedTenantId", response.tenantId);
},
[account, tenantId],
);
}
}, [config, msalInstance]);
const acquireTokens = React.useCallback(async () => {
if (!(account && tenantId)) {
React.useEffect(() => {
if (msalInstance && account) {
msalInstance.setActiveAccount(account);
}
}, [msalInstance, account]);
const login = React.useCallback(async () => {
if (!msalInstance || !config) {
return;
}
try {
const response = await msalInstance.loginPopup({
redirectUri: config.msalRedirectURI,
scopes: [],
});
setLoggedIn();
setAccount(response.account);
setTenantId(response.tenantId);
localStorage.setItem("cachedTenantId", response.tenantId);
} catch (error) {
setAuthFailure({
failureMessage: `Login failed: ${JSON.stringify(error)}`,
});
}
}, [msalInstance, config]);
const logout = React.useCallback(() => {
if (!msalInstance) {
return;
}
setLoggedOut();
localStorage.removeItem("cachedTenantId");
msalInstance.logoutRedirect();
}, [msalInstance]);
const switchTenant = React.useCallback(
async (id) => {
if (!msalInstance || !config) {
return;
}
try {
const response = await msalInstance.loginPopup({
redirectUri: config.msalRedirectURI,
authority: `${config.AAD_ENDPOINT}${id}`,
scopes: [],
});
setTenantId(response.tenantId);
setAccount(response.account);
localStorage.setItem("cachedTenantId", response.tenantId);
} catch (error) {
setAuthFailure({
failureMessage: `Tenant switch failed: ${JSON.stringify(error)}`,
});
}
},
[msalInstance, config],
);
const acquireTokens = React.useCallback(async () => {
if (!(account && tenantId && msalInstance && config)) {
return;
}
try {
const armToken = await acquireTokenWithMsal(msalInstance, {
authority: `${configContext.AAD_ENDPOINT}${tenantId}`,
scopes: [`${configContext.ARM_ENDPOINT}/.default`],
authority: `${config.AAD_ENDPOINT}${tenantId}`,
scopes: [`${config.ARM_ENDPOINT}/.default`],
});
setArmToken(armToken);
@@ -105,8 +142,8 @@ export function useAADAuth(): ReturnType {
try {
const graphToken = await acquireTokenWithMsal(msalInstance, {
authority: `${configContext.AAD_ENDPOINT}${tenantId}`,
scopes: [`${configContext.GRAPH_ENDPOINT}/.default`],
authority: `${config.AAD_ENDPOINT}${tenantId}`,
scopes: [`${config.GRAPH_ENDPOINT}/.default`],
});
setGraphToken(graphToken);
@@ -115,7 +152,7 @@ export function useAADAuth(): ReturnType {
// it's not critical if this fails.
console.warn("Error acquiring graph token: " + error);
}
}, [account, tenantId]);
}, [account, tenantId, msalInstance, config]);
React.useEffect(() => {
if (account && tenantId && !authFailure) {

View File

@@ -64,7 +64,8 @@ import {
getMsalInstance,
} from "../Utils/AuthorizationUtils";
import { isInvalidParentFrameOrigin, shouldProcessMessage } from "../Utils/MessageValidation";
import { getReadOnlyKeys, listKeys } from "../Utils/arm/generatedClients/cosmos/databaseAccounts";
import { get, getReadOnlyKeys, listKeys } from "../Utils/arm/generatedClients/cosmos/databaseAccounts";
import * as Types from "../Utils/arm/generatedClients/cosmos/types";
import { applyExplorerBindings } from "../applyExplorerBindings";
// This hook will create a new instance of Explorer.ts and bind it to the DOM
@@ -85,7 +86,7 @@ export function useKnockoutExplorer(platform: Platform): Explorer {
let explorer: Explorer;
if (platform === Platform.Hosted) {
explorer = await configureHosted();
} else if (platform === Platform.Emulator) {
} else if (platform === Platform.Emulator || platform === Platform.VNextEmulator) {
explorer = configureEmulator();
} else if (platform === Platform.Portal) {
explorer = await configurePortal();
@@ -346,6 +347,14 @@ async function configureHostedWithAAD(config: AAD): Promise<Explorer> {
}
}
try {
// TO DO - Remove once we have ARG API support for enableMaterializedViews property
const databaseAccount: Types.DatabaseAccountGetResults = await get(
subscriptionId,
account.resourceGroup,
account.name,
);
config.databaseAccount.properties.enableMaterializedViews = databaseAccount.properties?.enableMaterializedViews;
updateUserContext({
databaseAccount: config.databaseAccount,
});
@@ -885,6 +894,7 @@ function updateContextsFromPortalMessage(inputs: DataExplorerInputsFrame) {
const authorizationToken = inputs.authorizationToken || "";
const databaseAccount = inputs.databaseAccount;
const aadToken = inputs.aadToken || "";
updateConfigContext({
ARM_ENDPOINT: normalizeArmEndpoint(inputs.csmEndpoint || configContext.ARM_ENDPOINT),
@@ -897,6 +907,7 @@ function updateContextsFromPortalMessage(inputs: DataExplorerInputsFrame) {
updateUserContext({
authorizationToken,
aadToken,
databaseAccount,
resourceGroup: inputs.resourceGroup,
subscriptionId: inputs.subscriptionId,

View File

@@ -0,0 +1,200 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="height=device-height, width=device-width, initial-scale=1.0" />
<title>Azure Cosmos DB Emulator</title>
</head>
<body>
<div id="divQuickStart">
<div class="Introlines">
<p class="Introline1">Congratulations! Your Azure Cosmos DB emulator is running.</p>
<p class="Introline2">Now, let's connect a sample app to it.</p>
<div id="divQuickStartConnections">
<p class="Introline2">URI</p>
<input type="text" class="codeblock" readonly="readonly" value="http://localhost:8081" />
<p class="Introline2">Primary Key</p>
<input
type="text"
class="codeblock"
readonly="readonly"
value="C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw=="
/>
<p class="Introline2">Primary Connection String</p>
<input
type="text"
class="codeblock"
readonly="readonly"
value="AccountEndpoint=http://localhost:8081/;AccountKey=C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw=="
/>
</div>
<p class="Introline3"><b>Choose a platform</b></p>
</div>
<div class="container-fluid">
<ul class="nav nav-tabs qslevel">
<li class="active">
<a data-toggle="tab" href="#net">
<img class="qsmenuicons" src="../images/dotnet.png" alt=".NET" /> .NET
</a>
</li>
<li>
<a data-toggle="tab" href="#java"> <img class="qsmenuicons" src="../images/java.png" alt="Java" /> Java </a>
</li>
<li>
<a data-toggle="tab" href="#nodejs">
<img class="qsmenuicons" src="../images/nodejs.png" alt="Node.js" /> Node.js
</a>
</li>
<li>
<a data-toggle="tab" href="#python">
<img class="qsmenuicons" src="../images/python.png" alt="Python" /> Python
</a>
</li>
<li>
<a data-toggle="tab" href="#go"> <img class="qsmenuicons" src="../images/golang.svg" alt="Go" /> Go </a>
</li>
<li>
<a data-toggle="tab" href="#springboot">
<img class="qsmenuicons" src="../images/springboot.svg" alt="Spring Boot" /> Spring Boot
</a>
</li>
</ul>
<div class="tab-content">
<div id="net" class="tab-pane fade in active">
<div class="sampleApp">
<div class="numbersize numbersizePadding">1</div>
<div class="numberheading">
Create a new .NET app
<p>
Follow this
<a href="https://learn.microsoft.com/azure/cosmos-db/nosql/quickstart-dotnet" target="_blank"
>tutorial
</a>
to create a new .NET app connected to Azure Cosmos DB.
</p>
</div>
</div>
<div class="learn-more"></div>
</div>
<div id="java" class="tab-pane fade">
<div class="sampleApp">
<div class="numbersize numbersizePadding">1</div>
<div class="numberheading">
Create a new Java app
<p>
Follow this
<a href="https://learn.microsoft.com/azure/cosmos-db/nosql/quickstart-java" target="_blank"
>tutorial
</a>
to create a new Java app connected to Azure Cosmos DB.
</p>
</div>
</div>
<div class="learn-more"></div>
</div>
<div id="nodejs" class="tab-pane fade">
<div class="sampleApp">
<div class="numbersize numbersizePadding">1</div>
<div class="numberheading">
Create a new Node.js app
<p>
Follow this
<a
href="https://learn.microsoft.com/azure/cosmos-db/nosql/quickstart-nodejs?pivots=programming-language-ts"
target="_blank"
>tutorial
</a>
to create a new Node.js app connected to Azure Cosmos DB.
</p>
</div>
</div>
<div class="learn-more"></div>
</div>
<div id="python" class="tab-pane fade">
<div class="sampleApp">
<div class="numbersize numbersizePadding">1</div>
<div class="numberheading">
Create a new Python app
<p>
Follow this
<a href="https://aka.ms/cosmos-db-emulator/tutorial/python" target="_blank">tutorial</a>
to create a new Python app connected to Azure Cosmos DB.
</p>
</div>
</div>
<div class="learn-more"></div>
</div>
<div id="go" class="tab-pane fade">
<div class="sampleApp">
<div class="numbersize numbersizePadding">1</div>
<div class="numberheading">
Create a new Go app
<p>
Follow this
<a href="https://learn.microsoft.com/azure/cosmos-db/nosql/quickstart-go" target="_blank">tutorial</a>
to create a new Go app connected to Azure Cosmos DB.
</p>
</div>
</div>
<div class="learn-more"></div>
</div>
<div id="springboot" class="tab-pane fade">
<div class="sampleApp">
<div class="numbersize numbersizePadding">1</div>
<div class="numberheading">
Create a new Spring Boot app
<p>
Follow this
<a
href="https://learn.microsoft.com/azure/cosmos-db/nosql/tutorial-springboot-azure-kubernetes-service"
target="_blank"
>tutorial</a
>
to create a new Spring Boot app connected to Azure Cosmos DB.
</p>
</div>
</div>
<div class="learn-more"></div>
</div>
</div>
</div>
</div>
<template id="learnMoreTpl">
<div class="app-block">
<div class="numbersize">2</div>
<div class="numberheading">
Learn more about Azure Cosmos DB
<ul>
<li>
<a href="https://azurecosmosdb.github.io/gallery/?tags=example" target="_blank" class="atags"
>Code Samples</a
>
</li>
<li><a href="https://aka.ms/cosmos-db-emulator/docs" target="_blank" class="atags">Documentation</a></li>
<li><a href="https://aka.ms/cosmos-db-emulator/pricing" target="_blank" class="atags">Pricing</a></li>
<li>
<a href="https://cosmos.azure.com/capacitycalculator/" target="_blank" class="atags">Capacity planner</a>
</li>
<li><a href="https://aka.ms/cosmos-db-emulator/stackoverflow" target="_blank" class="atags">Forum</a></li>
</ul>
</div>
</div>
</template>
<script>
document.querySelectorAll(".learn-more").forEach((slot) => {
const node = document.getElementById("learnMoreTpl").content.cloneNode(true);
slot.appendChild(node);
});
</script>
</body>
</html>

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