mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-25 11:51:07 +00:00
Compare commits
55 Commits
sung_test
...
missing_pk
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e8db9b539 | ||
|
|
f94a452a98 | ||
|
|
6ce81099ef | ||
|
|
777e411f4f | ||
|
|
63d4b4f4ef | ||
|
|
eaf9a14e7d | ||
|
|
4b65760a1d | ||
|
|
ced2725476 | ||
|
|
b5d7423849 | ||
|
|
1529303107 | ||
|
|
083bccfda9 | ||
|
|
14c9874e5e | ||
|
|
a04eaff6be | ||
|
|
51a412e2c0 | ||
|
|
3fcbdf6152 | ||
|
|
8da078579e | ||
|
|
4ac41031e6 | ||
|
|
d7923db108 | ||
|
|
0170c9e1cc | ||
|
|
2730da7ab6 | ||
|
|
de2449ee25 | ||
|
|
99378582ce | ||
|
|
bd592d07af | ||
|
|
644f5941ec | ||
|
|
5997fabcda | ||
|
|
9fb006a996 | ||
|
|
c2b98c3e23 | ||
|
|
76d49d86d4 | ||
|
|
f01d4a5ae2 | ||
|
|
7893b89bf7 | ||
|
|
5945e3cb6b | ||
|
|
20eeed98e4 | ||
|
|
213d1c68fe | ||
|
|
ac53e1b3b5 | ||
|
|
2cab086268 | ||
|
|
c26f9a1ebb | ||
|
|
937451d844 | ||
|
|
bd7cd7ae8f | ||
|
|
6504358580 | ||
|
|
ce88659fca | ||
|
|
642c708e9c | ||
|
|
4156009d09 | ||
|
|
5c6abbd635 | ||
|
|
881726e9af | ||
|
|
5dfaa9f0f8 | ||
|
|
7015590d1a | ||
|
|
1d952a4ea2 | ||
|
|
05e2d0ac29 | ||
|
|
152c995ec0 | ||
|
|
2a81551a60 | ||
|
|
eceee36913 | ||
|
|
96faf92c12 | ||
|
|
07c4ca9c50 | ||
|
|
80781f7c8f | ||
|
|
aa39359460 |
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1 +1 @@
|
|||||||
[Preview this branch](https://cosmos-explorer-preview.azurewebsites.net/pull/EDIT_THIS_NUMBER_IN_THE_PR_DESCRIPTION?feature.someFeatureFlagYouMightNeed=true)
|
[Preview this branch](https://dataexplorer-preview.azurewebsites.net/pull/EDIT_THIS_NUMBER_IN_THE_PR_DESCRIPTION?feature.someFeatureFlagYouMightNeed=true)
|
||||||
|
|||||||
47
.github/workflows/ci.yml
vendored
47
.github/workflows/ci.yml
vendored
@@ -83,7 +83,7 @@ jobs:
|
|||||||
- run: npm ci
|
- run: npm ci
|
||||||
- run: npm run build:contracts
|
- run: npm run build:contracts
|
||||||
- name: Restore Build Cache
|
- name: Restore Build Cache
|
||||||
uses: actions/cache@v2
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: .cache
|
path: .cache
|
||||||
key: ${{ runner.os }}-build-cache
|
key: ${{ runner.os }}-build-cache
|
||||||
@@ -96,14 +96,16 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
name: dist
|
name: dist
|
||||||
path: dist/
|
path: dist/
|
||||||
|
- name: "Az CLI login"
|
||||||
|
uses: azure/login@v1
|
||||||
|
with:
|
||||||
|
client-id: ${{ secrets.AZURE_CLIENT_ID }}
|
||||||
|
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
|
||||||
|
subscription-id: ${{ secrets.PREVIEW_SUBSCRIPTION_ID }}
|
||||||
- name: Upload build to preview blob storage
|
- name: Upload build to preview blob storage
|
||||||
run: az storage blob upload-batch -d '$web' -s 'dist' --account-name cosmosexplorerpreview --destination-path "${{github.event.pull_request.head.sha || github.sha}}" --account-key="${PREVIEW_STORAGE_KEY}" --overwrite true
|
run: az storage blob upload-batch -d '$web' -s 'dist' --account-name ${{ secrets.PREVIEW_STORAGE_ACCOUNT_NAME }} --destination-path "${{github.event.pull_request.head.sha || github.sha}}" --auth-mode login --overwrite true
|
||||||
env:
|
|
||||||
PREVIEW_STORAGE_KEY: ${{ secrets.PREVIEW_STORAGE_KEY }}
|
|
||||||
- name: Upload preview config to blob storage
|
- name: Upload preview config to blob storage
|
||||||
run: az storage blob upload -c '$web' -f ./preview/config.json --account-name cosmosexplorerpreview --name "${{github.event.pull_request.head.sha || github.sha}}/config.json" --account-key="${PREVIEW_STORAGE_KEY}" --overwrite true
|
run: az storage blob upload -c '$web' -f ./preview/config.json --account-name ${{ secrets.PREVIEW_STORAGE_ACCOUNT_NAME }} --name "${{github.event.pull_request.head.sha || github.sha}}/config.json" --auth-mode login --overwrite true
|
||||||
env:
|
|
||||||
PREVIEW_STORAGE_KEY: ${{ secrets.PREVIEW_STORAGE_KEY }}
|
|
||||||
nuget:
|
nuget:
|
||||||
name: Publish Nuget
|
name: Publish Nuget
|
||||||
if: github.ref == 'refs/heads/master' || contains(github.ref, 'hotfix/') || contains(github.ref, 'release/')
|
if: github.ref == 'refs/heads/master' || contains(github.ref, 'hotfix/') || contains(github.ref, 'release/')
|
||||||
@@ -113,21 +115,21 @@ jobs:
|
|||||||
NUGET_SOURCE: ${{ secrets.NUGET_SOURCE }}
|
NUGET_SOURCE: ${{ secrets.NUGET_SOURCE }}
|
||||||
AZURE_DEVOPS_PAT: ${{ secrets.AZURE_DEVOPS_PAT }}
|
AZURE_DEVOPS_PAT: ${{ secrets.AZURE_DEVOPS_PAT }}
|
||||||
steps:
|
steps:
|
||||||
- uses: nuget/setup-nuget@v2
|
|
||||||
with:
|
|
||||||
nuget-api-key: ${{ secrets.NUGET_API_KEY }}
|
|
||||||
- name: Download Dist Folder
|
- name: Download Dist Folder
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: dist
|
name: dist
|
||||||
- run: cp ./configs/prod.json config.json
|
- run: cp ./configs/prod.json config.json
|
||||||
- run: nuget sources add -Name "ADO" -Source "$NUGET_SOURCE" -UserName "jawelton@microsoft.com" -Password "$AZURE_DEVOPS_PAT"
|
- run: dotnet nuget add source "$NUGET_SOURCE" --name "ADO" --username "jawelton@microsoft.com" --password "$AZURE_DEVOPS_PAT" --store-password-in-clear-text
|
||||||
- run: nuget pack -Version "2.0.0-github-${GITHUB_SHA}"
|
- run: dotnet pack DataExplorer.proj /p:PackageVersion="2.0.0-github-${GITHUB_SHA}"
|
||||||
- run: nuget push -SkipDuplicate -Source "$NUGET_SOURCE" -ApiKey Az *.nupkg
|
- run: dotnet nuget push "bin/Release/*.nupkg" --skip-duplicate --api-key Az --source="$NUGET_SOURCE"
|
||||||
|
- run: dotnet nuget remove source "ADO"
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
name: packages
|
name: Upload package to Artifacts
|
||||||
with:
|
with:
|
||||||
path: "*.nupkg"
|
name: prod-package
|
||||||
|
path: "bin/Release/*.nupkg"
|
||||||
|
|
||||||
nugetmpac:
|
nugetmpac:
|
||||||
name: Publish Nuget MPAC
|
name: Publish Nuget MPAC
|
||||||
if: github.ref == 'refs/heads/master' || contains(github.ref, 'hotfix/') || contains(github.ref, 'release/')
|
if: github.ref == 'refs/heads/master' || contains(github.ref, 'hotfix/') || contains(github.ref, 'release/')
|
||||||
@@ -137,22 +139,21 @@ jobs:
|
|||||||
NUGET_SOURCE: ${{ secrets.NUGET_SOURCE }}
|
NUGET_SOURCE: ${{ secrets.NUGET_SOURCE }}
|
||||||
AZURE_DEVOPS_PAT: ${{ secrets.AZURE_DEVOPS_PAT }}
|
AZURE_DEVOPS_PAT: ${{ secrets.AZURE_DEVOPS_PAT }}
|
||||||
steps:
|
steps:
|
||||||
- uses: nuget/setup-nuget@v2
|
|
||||||
with:
|
|
||||||
nuget-api-key: ${{ secrets.NUGET_API_KEY }}
|
|
||||||
- name: Download Dist Folder
|
- name: Download Dist Folder
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: dist
|
name: dist
|
||||||
- run: cp ./configs/mpac.json config.json
|
- run: cp ./configs/mpac.json config.json
|
||||||
- run: sed -i 's/Azure.Cosmos.DB.Data.Explorer/Azure.Cosmos.DB.Data.Explorer.MPAC/g' DataExplorer.nuspec
|
- run: sed -i 's/Azure.Cosmos.DB.Data.Explorer/Azure.Cosmos.DB.Data.Explorer.MPAC/g' DataExplorer.nuspec
|
||||||
- run: nuget sources add -Name "ADO" -Source "$NUGET_SOURCE" -UserName "jawelton@microsoft.com" -Password "$AZURE_DEVOPS_PAT"
|
- run: dotnet nuget add source "$NUGET_SOURCE" --name "ADO" --username "jawelton@microsoft.com" --password "$AZURE_DEVOPS_PAT" --store-password-in-clear-text
|
||||||
- run: nuget pack -Version "2.0.0-github-${GITHUB_SHA}"
|
- run: dotnet pack DataExplorer.proj /p:PackageVersion="2.0.0-github-${GITHUB_SHA}"
|
||||||
- run: nuget push -SkipDuplicate -Source "$NUGET_SOURCE" -ApiKey Az *.nupkg
|
- run: dotnet nuget push "bin/Release/*.nupkg" --skip-duplicate --api-key Az --source="$NUGET_SOURCE"
|
||||||
|
- run: dotnet nuget remove source "ADO"
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
name: packages
|
name: Upload package to Artifacts
|
||||||
with:
|
with:
|
||||||
path: "*.nupkg"
|
name: mpac-package
|
||||||
|
path: "bin/Release/*.nupkg"
|
||||||
|
|
||||||
playwright-tests:
|
playwright-tests:
|
||||||
name: "Run Playwright Tests (Shard ${{ matrix.shardIndex }} of ${{ matrix.shardTotal }})"
|
name: "Run Playwright Tests (Shard ${{ matrix.shardIndex }} of ${{ matrix.shardTotal }})"
|
||||||
|
|||||||
9
DataExplorer.proj
Normal file
9
DataExplorer.proj
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<NoBuild>true</NoBuild>
|
||||||
|
<IncludeBuildOutput>false</IncludeBuildOutput>
|
||||||
|
<NuspecFile>DataExplorer.nuspec</NuspecFile>
|
||||||
|
<NuspecProperties>version=$(PackageVersion)</NuspecProperties>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
||||||
@@ -61,6 +61,8 @@
|
|||||||
|
|
||||||
@GalleryBackgroundColor: #fdfdfd;
|
@GalleryBackgroundColor: #fdfdfd;
|
||||||
|
|
||||||
|
@LinkColor: #2d6da4;
|
||||||
|
|
||||||
//Icons
|
//Icons
|
||||||
@InfoIconColor: #0072c6;
|
@InfoIconColor: #0072c6;
|
||||||
@WarningIconColor: #db7500;
|
@WarningIconColor: #db7500;
|
||||||
@@ -246,6 +248,10 @@
|
|||||||
outline: 1px dashed @FocusColor;
|
outline: 1px dashed @FocusColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.focusedBorder() {
|
||||||
|
border: 1px dashed @FocusColor;
|
||||||
|
}
|
||||||
|
|
||||||
/************************************************************************************************
|
/************************************************************************************************
|
||||||
Common Toggle Switch
|
Common Toggle Switch
|
||||||
*************************************************************************************************/
|
*************************************************************************************************/
|
||||||
|
|||||||
@@ -1830,6 +1830,14 @@ input::-webkit-calendar-picker-indicator::after {
|
|||||||
transform: rotate(90deg);
|
transform: rotate(90deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.customAccordion button:focus {
|
||||||
|
.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
.customAccordion {
|
||||||
|
margin-top: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
.datalist-arrow:after:hover {
|
.datalist-arrow:after:hover {
|
||||||
content: "\276F";
|
content: "\276F";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -1906,13 +1914,20 @@ input::-webkit-calendar-picker-indicator::after {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.nav-tabs-margin {
|
.nav-tabs-margin {
|
||||||
height: 32px;
|
|
||||||
background-color: #f2f2f2;
|
background-color: #f2f2f2;
|
||||||
|
|
||||||
.nav-tabs {
|
.nav-tabs {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
margin-bottom: -0.5px;
|
||||||
|
|
||||||
|
li {
|
||||||
|
// Override the bootstrap defaults here to align with our layout constants.
|
||||||
|
margin-bottom: 0px;
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
567
package-lock.json
generated
567
package-lock.json
generated
@@ -12,8 +12,7 @@
|
|||||||
"@azure/arm-cosmosdb": "9.1.0",
|
"@azure/arm-cosmosdb": "9.1.0",
|
||||||
"@azure/cosmos": "4.2.0-beta.1",
|
"@azure/cosmos": "4.2.0-beta.1",
|
||||||
"@azure/cosmos-language-service": "0.0.5",
|
"@azure/cosmos-language-service": "0.0.5",
|
||||||
"@azure/identity": "1.5.2",
|
"@azure/identity": "4.5.0",
|
||||||
"@azure/ms-rest-nodeauth": "3.1.1",
|
|
||||||
"@azure/msal-browser": "2.14.2",
|
"@azure/msal-browser": "2.14.2",
|
||||||
"@babel/plugin-proposal-class-properties": "7.12.1",
|
"@babel/plugin-proposal-class-properties": "7.12.1",
|
||||||
"@babel/plugin-proposal-decorators": "7.12.12",
|
"@babel/plugin-proposal-decorators": "7.12.12",
|
||||||
@@ -87,7 +86,7 @@
|
|||||||
"mkdirp": "1.0.4",
|
"mkdirp": "1.0.4",
|
||||||
"monaco-editor": "0.44.0",
|
"monaco-editor": "0.44.0",
|
||||||
"ms": "2.1.3",
|
"ms": "2.1.3",
|
||||||
"p-retry": "4.6.2",
|
"p-retry": "6.2.1",
|
||||||
"patch-package": "8.0.0",
|
"patch-package": "8.0.0",
|
||||||
"plotly.js-cartesian-dist-min": "1.52.3",
|
"plotly.js-cartesian-dist-min": "1.52.3",
|
||||||
"post-robot": "10.0.42",
|
"post-robot": "10.0.42",
|
||||||
@@ -428,47 +427,68 @@
|
|||||||
"license": "0BSD"
|
"license": "0BSD"
|
||||||
},
|
},
|
||||||
"node_modules/@azure/identity": {
|
"node_modules/@azure/identity": {
|
||||||
"version": "1.5.2",
|
"version": "4.5.0",
|
||||||
"license": "MIT",
|
"resolved": "https://registry.npmjs.org/@azure/identity/-/identity-4.5.0.tgz",
|
||||||
|
"integrity": "sha512-EknvVmtBuSIic47xkOqyNabAme0RYTw52BTMz8eBgU1ysTyMrD1uOoM+JdS0J/4Yfp98IBT3osqq3BfwSaNaGQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@azure/core-auth": "^1.3.0",
|
"@azure/abort-controller": "^2.0.0",
|
||||||
"@azure/core-client": "^1.0.0",
|
"@azure/core-auth": "^1.9.0",
|
||||||
"@azure/core-rest-pipeline": "^1.1.0",
|
"@azure/core-client": "^1.9.2",
|
||||||
"@azure/core-tracing": "1.0.0-preview.12",
|
"@azure/core-rest-pipeline": "^1.17.0",
|
||||||
|
"@azure/core-tracing": "^1.0.0",
|
||||||
|
"@azure/core-util": "^1.11.0",
|
||||||
"@azure/logger": "^1.0.0",
|
"@azure/logger": "^1.0.0",
|
||||||
"@azure/msal-node": "1.0.0-beta.6",
|
"@azure/msal-browser": "^3.26.1",
|
||||||
"@types/stoppable": "^1.1.0",
|
"@azure/msal-node": "^2.15.0",
|
||||||
"axios": "^0.21.1",
|
|
||||||
"events": "^3.0.0",
|
"events": "^3.0.0",
|
||||||
"jws": "^4.0.0",
|
"jws": "^4.0.0",
|
||||||
"msal": "^1.0.2",
|
"open": "^8.0.0",
|
||||||
"open": "^7.0.0",
|
|
||||||
"qs": "^6.7.0",
|
|
||||||
"stoppable": "^1.1.0",
|
"stoppable": "^1.1.0",
|
||||||
"tslib": "^2.0.0",
|
|
||||||
"uuid": "^8.3.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12.0.0"
|
|
||||||
},
|
|
||||||
"optionalDependencies": {
|
|
||||||
"keytar": "^7.3.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@azure/identity/node_modules/@azure/core-tracing": {
|
|
||||||
"version": "1.0.0-preview.12",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@opentelemetry/api": "^1.0.0",
|
|
||||||
"tslib": "^2.2.0"
|
"tslib": "^2.2.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12.0.0"
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@azure/identity/node_modules/@azure/msal-browser": {
|
||||||
|
"version": "3.28.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-3.28.1.tgz",
|
||||||
|
"integrity": "sha512-OHHEWMB5+Zrix8yKvLVzU3rKDFvh7SOzAzXfICD7YgUXLxfHpTPX2pzOotrri1kskwhHqIj4a5LvhZlIqE7C7g==",
|
||||||
|
"dependencies": {
|
||||||
|
"@azure/msal-common": "14.16.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@azure/identity/node_modules/@azure/msal-common": {
|
||||||
|
"version": "14.16.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-14.16.0.tgz",
|
||||||
|
"integrity": "sha512-1KOZj9IpcDSwpNiQNjt0jDYZpQvNZay7QAEi/5DLubay40iGYtLzya/jbjRPLyOTZhEKyL1MzPuw2HqBCjceYA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@azure/identity/node_modules/open": {
|
||||||
|
"version": "8.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz",
|
||||||
|
"integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"define-lazy-prop": "^2.0.0",
|
||||||
|
"is-docker": "^2.1.1",
|
||||||
|
"is-wsl": "^2.2.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@azure/identity/node_modules/tslib": {
|
"node_modules/@azure/identity/node_modules/tslib": {
|
||||||
"version": "2.6.2",
|
"version": "2.8.1",
|
||||||
"license": "0BSD"
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||||
|
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
|
||||||
},
|
},
|
||||||
"node_modules/@azure/logger": {
|
"node_modules/@azure/logger": {
|
||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
@@ -484,10 +504,6 @@
|
|||||||
"version": "2.6.2",
|
"version": "2.6.2",
|
||||||
"license": "0BSD"
|
"license": "0BSD"
|
||||||
},
|
},
|
||||||
"node_modules/@azure/ms-rest-azure-env": {
|
|
||||||
"version": "2.0.0",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/@azure/ms-rest-azure-js": {
|
"node_modules/@azure/ms-rest-azure-js": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -559,15 +575,6 @@
|
|||||||
"node": ">=4.0"
|
"node": ">=4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@azure/ms-rest-nodeauth": {
|
|
||||||
"version": "3.1.1",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@azure/ms-rest-azure-env": "^2.0.0",
|
|
||||||
"@azure/ms-rest-js": "^2.0.4",
|
|
||||||
"adal-node": "^0.2.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@azure/msal-browser": {
|
"node_modules/@azure/msal-browser": {
|
||||||
"version": "2.14.2",
|
"version": "2.14.2",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -589,13 +596,24 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@azure/msal-node": {
|
"node_modules/@azure/msal-node": {
|
||||||
"version": "1.0.0-beta.6",
|
"version": "2.16.2",
|
||||||
"license": "MIT",
|
"resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-2.16.2.tgz",
|
||||||
|
"integrity": "sha512-An7l1hEr0w1HMMh1LU+rtDtqL7/jw74ORlc9Wnh06v7TU/xpG39/Zdr1ZJu3QpjUfKJ+E0/OXMW8DRSWTlh7qQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@azure/msal-common": "^4.0.0",
|
"@azure/msal-common": "14.16.0",
|
||||||
"axios": "^0.21.1",
|
"jsonwebtoken": "^9.0.0",
|
||||||
"jsonwebtoken": "^8.5.1",
|
|
||||||
"uuid": "^8.3.0"
|
"uuid": "^8.3.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@azure/msal-node/node_modules/@azure/msal-common": {
|
||||||
|
"version": "14.16.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-14.16.0.tgz",
|
||||||
|
"integrity": "sha512-1KOZj9IpcDSwpNiQNjt0jDYZpQvNZay7QAEi/5DLubay40iGYtLzya/jbjRPLyOTZhEKyL1MzPuw2HqBCjceYA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/code-frame": {
|
"node_modules/@babel/code-frame": {
|
||||||
@@ -10073,13 +10091,6 @@
|
|||||||
"@octokit/openapi-types": "^19.0.2"
|
"@octokit/openapi-types": "^19.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@opentelemetry/api": {
|
|
||||||
"version": "1.8.0",
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=8.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@phosphor/algorithm": {
|
"node_modules/@phosphor/algorithm": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"license": "BSD-3-Clause"
|
"license": "BSD-3-Clause"
|
||||||
@@ -12651,7 +12662,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/retry": {
|
"node_modules/@types/retry": {
|
||||||
"version": "0.12.0",
|
"version": "0.12.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz",
|
||||||
|
"integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/sanitize-html": {
|
"node_modules/@types/sanitize-html": {
|
||||||
@@ -12725,13 +12738,6 @@
|
|||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/stoppable": {
|
|
||||||
"version": "1.1.3",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@types/node": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@types/styled-components": {
|
"node_modules/@types/styled-components": {
|
||||||
"version": "5.1.1",
|
"version": "5.1.1",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@@ -13334,61 +13340,6 @@
|
|||||||
"node": ">=0.4.0"
|
"node": ">=0.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/adal-node": {
|
|
||||||
"version": "0.2.4",
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"dependencies": {
|
|
||||||
"@xmldom/xmldom": "^0.8.3",
|
|
||||||
"async": "^2.6.3",
|
|
||||||
"axios": "^0.21.1",
|
|
||||||
"date-utils": "*",
|
|
||||||
"jws": "3.x.x",
|
|
||||||
"underscore": ">= 1.3.1",
|
|
||||||
"uuid": "^3.1.0",
|
|
||||||
"xpath.js": "~1.1.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.6.15"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/adal-node/node_modules/@xmldom/xmldom": {
|
|
||||||
"version": "0.8.10",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/adal-node/node_modules/async": {
|
|
||||||
"version": "2.6.4",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"lodash": "^4.17.14"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/adal-node/node_modules/jwa": {
|
|
||||||
"version": "1.4.1",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"buffer-equal-constant-time": "1.0.1",
|
|
||||||
"ecdsa-sig-formatter": "1.0.11",
|
|
||||||
"safe-buffer": "^5.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/adal-node/node_modules/jws": {
|
|
||||||
"version": "3.2.2",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"jwa": "^1.4.1",
|
|
||||||
"safe-buffer": "^5.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/adal-node/node_modules/uuid": {
|
|
||||||
"version": "3.4.0",
|
|
||||||
"license": "MIT",
|
|
||||||
"bin": {
|
|
||||||
"uuid": "bin/uuid"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/address": {
|
"node_modules/address": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@@ -14021,13 +13972,6 @@
|
|||||||
"version": "1.12.0",
|
"version": "1.12.0",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/axios": {
|
|
||||||
"version": "0.21.4",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"follow-redirects": "^1.14.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/babel-core": {
|
"node_modules/babel-core": {
|
||||||
"version": "7.0.0-bridge.0",
|
"version": "7.0.0-bridge.0",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@@ -14731,7 +14675,7 @@
|
|||||||
},
|
},
|
||||||
"node_modules/base64-js": {
|
"node_modules/base64-js": {
|
||||||
"version": "1.5.1",
|
"version": "1.5.1",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "github",
|
"type": "github",
|
||||||
@@ -14803,8 +14747,9 @@
|
|||||||
},
|
},
|
||||||
"node_modules/bl": {
|
"node_modules/bl": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"buffer": "^5.5.0",
|
"buffer": "^5.5.0",
|
||||||
"inherits": "^2.0.4",
|
"inherits": "^2.0.4",
|
||||||
@@ -14813,7 +14758,7 @@
|
|||||||
},
|
},
|
||||||
"node_modules/bl/node_modules/buffer": {
|
"node_modules/bl/node_modules/buffer": {
|
||||||
"version": "5.7.1",
|
"version": "5.7.1",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "github",
|
"type": "github",
|
||||||
@@ -14829,6 +14774,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"base64-js": "^1.3.1",
|
"base64-js": "^1.3.1",
|
||||||
"ieee754": "^1.1.13"
|
"ieee754": "^1.1.13"
|
||||||
@@ -14836,8 +14782,9 @@
|
|||||||
},
|
},
|
||||||
"node_modules/bl/node_modules/readable-stream": {
|
"node_modules/bl/node_modules/readable-stream": {
|
||||||
"version": "3.6.2",
|
"version": "3.6.2",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"inherits": "^2.0.3",
|
"inherits": "^2.0.3",
|
||||||
"string_decoder": "^1.1.1",
|
"string_decoder": "^1.1.1",
|
||||||
@@ -15407,11 +15354,6 @@
|
|||||||
"node": ">=8.0"
|
"node": ">=8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/chownr": {
|
|
||||||
"version": "1.1.4",
|
|
||||||
"license": "ISC",
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"node_modules/chrome-trace-event": {
|
"node_modules/chrome-trace-event": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -17175,13 +17117,6 @@
|
|||||||
"version": "1.29.0",
|
"version": "1.29.0",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/date-utils": {
|
|
||||||
"version": "1.2.21",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">0.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/dayjs": {
|
"node_modules/dayjs": {
|
||||||
"version": "1.8.19",
|
"version": "1.8.19",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
@@ -17276,14 +17211,6 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/deep-extend": {
|
|
||||||
"version": "0.6.0",
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">=4.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/deep-is": {
|
"node_modules/deep-is": {
|
||||||
"version": "0.1.4",
|
"version": "0.1.4",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
@@ -17344,7 +17271,6 @@
|
|||||||
},
|
},
|
||||||
"node_modules/define-lazy-prop": {
|
"node_modules/define-lazy-prop": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
@@ -18976,14 +18902,6 @@
|
|||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/expand-template": {
|
|
||||||
"version": "2.0.3",
|
|
||||||
"license": "(MIT OR WTFPL)",
|
|
||||||
"optional": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/expect": {
|
"node_modules/expect": {
|
||||||
"version": "29.7.0",
|
"version": "29.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz",
|
||||||
@@ -19671,6 +19589,7 @@
|
|||||||
},
|
},
|
||||||
"node_modules/follow-redirects": {
|
"node_modules/follow-redirects": {
|
||||||
"version": "1.15.3",
|
"version": "1.15.3",
|
||||||
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "individual",
|
"type": "individual",
|
||||||
@@ -19978,11 +19897,6 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/fs-constants": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"node_modules/fs-extra": {
|
"node_modules/fs-extra": {
|
||||||
"version": "7.0.0",
|
"version": "7.0.0",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@@ -20197,11 +20111,6 @@
|
|||||||
"assert-plus": "^1.0.0"
|
"assert-plus": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/github-from-package": {
|
|
||||||
"version": "0.0.0",
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"node_modules/glob": {
|
"node_modules/glob": {
|
||||||
"version": "7.2.3",
|
"version": "7.2.3",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
@@ -21333,7 +21242,7 @@
|
|||||||
},
|
},
|
||||||
"node_modules/ieee754": {
|
"node_modules/ieee754": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "github",
|
"type": "github",
|
||||||
@@ -21522,7 +21431,7 @@
|
|||||||
},
|
},
|
||||||
"node_modules/ini": {
|
"node_modules/ini": {
|
||||||
"version": "1.3.8",
|
"version": "1.3.8",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/internal-slot": {
|
"node_modules/internal-slot": {
|
||||||
@@ -21892,6 +21801,18 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/is-network-error": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-tUdRRAnhT+OtCZR/LxZelH/C7QtjtFrTu5tXCA8pl55eTUElUHT+GPYV8MBMBvea/j+NxQqVt3LbWMRir7Gx9g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/is-number": {
|
"node_modules/is-number": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -27558,8 +27479,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/jsonwebtoken": {
|
"node_modules/jsonwebtoken": {
|
||||||
"version": "8.5.1",
|
"version": "9.0.2",
|
||||||
"license": "MIT",
|
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
|
||||||
|
"integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"jws": "^3.2.2",
|
"jws": "^3.2.2",
|
||||||
"lodash.includes": "^4.3.0",
|
"lodash.includes": "^4.3.0",
|
||||||
@@ -27570,16 +27492,17 @@
|
|||||||
"lodash.isstring": "^4.0.1",
|
"lodash.isstring": "^4.0.1",
|
||||||
"lodash.once": "^4.0.0",
|
"lodash.once": "^4.0.0",
|
||||||
"ms": "^2.1.1",
|
"ms": "^2.1.1",
|
||||||
"semver": "^5.6.0"
|
"semver": "^7.5.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=4",
|
"node": ">=12",
|
||||||
"npm": ">=1.4.28"
|
"npm": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/jsonwebtoken/node_modules/jwa": {
|
"node_modules/jsonwebtoken/node_modules/jwa": {
|
||||||
"version": "1.4.1",
|
"version": "1.4.1",
|
||||||
"license": "MIT",
|
"resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
|
||||||
|
"integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"buffer-equal-constant-time": "1.0.1",
|
"buffer-equal-constant-time": "1.0.1",
|
||||||
"ecdsa-sig-formatter": "1.0.11",
|
"ecdsa-sig-formatter": "1.0.11",
|
||||||
@@ -27588,12 +27511,24 @@
|
|||||||
},
|
},
|
||||||
"node_modules/jsonwebtoken/node_modules/jws": {
|
"node_modules/jsonwebtoken/node_modules/jws": {
|
||||||
"version": "3.2.2",
|
"version": "3.2.2",
|
||||||
"license": "MIT",
|
"resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
|
||||||
|
"integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"jwa": "^1.4.1",
|
"jwa": "^1.4.1",
|
||||||
"safe-buffer": "^5.0.1"
|
"safe-buffer": "^5.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/jsonwebtoken/node_modules/semver": {
|
||||||
|
"version": "7.6.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
|
||||||
|
"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
|
||||||
|
"bin": {
|
||||||
|
"semver": "bin/semver.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/jsprim": {
|
"node_modules/jsprim": {
|
||||||
"version": "1.4.2",
|
"version": "1.4.2",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -27646,16 +27581,6 @@
|
|||||||
"version": "2.6.0",
|
"version": "2.6.0",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/keytar": {
|
|
||||||
"version": "7.9.0",
|
|
||||||
"hasInstallScript": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
|
||||||
"node-addon-api": "^4.3.0",
|
|
||||||
"prebuild-install": "^7.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/keyv": {
|
"node_modules/keyv": {
|
||||||
"version": "4.5.4",
|
"version": "4.5.4",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -27972,7 +27897,8 @@
|
|||||||
},
|
},
|
||||||
"node_modules/lodash.includes": {
|
"node_modules/lodash.includes": {
|
||||||
"version": "4.3.0",
|
"version": "4.3.0",
|
||||||
"license": "MIT"
|
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="
|
||||||
},
|
},
|
||||||
"node_modules/lodash.invokemap": {
|
"node_modules/lodash.invokemap": {
|
||||||
"version": "4.6.0",
|
"version": "4.6.0",
|
||||||
@@ -27981,7 +27907,8 @@
|
|||||||
},
|
},
|
||||||
"node_modules/lodash.isboolean": {
|
"node_modules/lodash.isboolean": {
|
||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
"license": "MIT"
|
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="
|
||||||
},
|
},
|
||||||
"node_modules/lodash.isequal": {
|
"node_modules/lodash.isequal": {
|
||||||
"version": "4.5.0",
|
"version": "4.5.0",
|
||||||
@@ -27989,19 +27916,23 @@
|
|||||||
},
|
},
|
||||||
"node_modules/lodash.isinteger": {
|
"node_modules/lodash.isinteger": {
|
||||||
"version": "4.0.4",
|
"version": "4.0.4",
|
||||||
"license": "MIT"
|
"resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
|
||||||
|
"integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA=="
|
||||||
},
|
},
|
||||||
"node_modules/lodash.isnumber": {
|
"node_modules/lodash.isnumber": {
|
||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
"license": "MIT"
|
"resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw=="
|
||||||
},
|
},
|
||||||
"node_modules/lodash.isplainobject": {
|
"node_modules/lodash.isplainobject": {
|
||||||
"version": "4.0.6",
|
"version": "4.0.6",
|
||||||
"license": "MIT"
|
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
|
||||||
|
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="
|
||||||
},
|
},
|
||||||
"node_modules/lodash.isstring": {
|
"node_modules/lodash.isstring": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
"license": "MIT"
|
"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
|
||||||
|
"integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="
|
||||||
},
|
},
|
||||||
"node_modules/lodash.memoize": {
|
"node_modules/lodash.memoize": {
|
||||||
"version": "4.1.2",
|
"version": "4.1.2",
|
||||||
@@ -28013,7 +27944,8 @@
|
|||||||
},
|
},
|
||||||
"node_modules/lodash.once": {
|
"node_modules/lodash.once": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"license": "MIT"
|
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
|
||||||
|
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="
|
||||||
},
|
},
|
||||||
"node_modules/lodash.pullall": {
|
"node_modules/lodash.pullall": {
|
||||||
"version": "4.2.0",
|
"version": "4.2.0",
|
||||||
@@ -29526,11 +29458,6 @@
|
|||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/mkdirp-classic": {
|
|
||||||
"version": "0.5.3",
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"node_modules/moment": {
|
"node_modules/moment": {
|
||||||
"version": "2.29.4",
|
"version": "2.29.4",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -29597,16 +29524,6 @@
|
|||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/msal": {
|
|
||||||
"version": "1.4.18",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"tslib": "^1.9.3"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.8.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/multicast-dns": {
|
"node_modules/multicast-dns": {
|
||||||
"version": "7.2.5",
|
"version": "7.2.5",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@@ -29667,11 +29584,6 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/napi-build-utils": {
|
|
||||||
"version": "1.0.2",
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"node_modules/native-promise-only": {
|
"node_modules/native-promise-only": {
|
||||||
"version": "0.8.1",
|
"version": "0.8.1",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@@ -29756,58 +29668,12 @@
|
|||||||
"node": ">=12.0.0"
|
"node": ">=12.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/node-abi": {
|
|
||||||
"version": "3.60.0",
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
|
||||||
"semver": "^7.3.5"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/node-abi/node_modules/lru-cache": {
|
|
||||||
"version": "6.0.0",
|
|
||||||
"license": "ISC",
|
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
|
||||||
"yallist": "^4.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/node-abi/node_modules/semver": {
|
|
||||||
"version": "7.6.0",
|
|
||||||
"license": "ISC",
|
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
|
||||||
"lru-cache": "^6.0.0"
|
|
||||||
},
|
|
||||||
"bin": {
|
|
||||||
"semver": "bin/semver.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/node-abi/node_modules/yallist": {
|
|
||||||
"version": "4.0.0",
|
|
||||||
"license": "ISC",
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"node_modules/node-abort-controller": {
|
"node_modules/node-abort-controller": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/node-addon-api": {
|
|
||||||
"version": "4.3.0",
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"node_modules/node-dir": {
|
"node_modules/node-dir": {
|
||||||
"version": "0.1.17",
|
"version": "0.1.17",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@@ -30391,14 +30257,20 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/p-retry": {
|
"node_modules/p-retry": {
|
||||||
"version": "4.6.2",
|
"version": "6.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/p-retry/-/p-retry-6.2.1.tgz",
|
||||||
|
"integrity": "sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/retry": "0.12.0",
|
"@types/retry": "0.12.2",
|
||||||
|
"is-network-error": "^1.0.0",
|
||||||
"retry": "^0.13.1"
|
"retry": "^0.13.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=16.17"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/p-try": {
|
"node_modules/p-try": {
|
||||||
@@ -31006,80 +30878,6 @@
|
|||||||
"version": "4.2.0",
|
"version": "4.2.0",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/prebuild-install": {
|
|
||||||
"version": "7.1.2",
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
|
||||||
"detect-libc": "^2.0.0",
|
|
||||||
"expand-template": "^2.0.3",
|
|
||||||
"github-from-package": "0.0.0",
|
|
||||||
"minimist": "^1.2.3",
|
|
||||||
"mkdirp-classic": "^0.5.3",
|
|
||||||
"napi-build-utils": "^1.0.1",
|
|
||||||
"node-abi": "^3.3.0",
|
|
||||||
"pump": "^3.0.0",
|
|
||||||
"rc": "^1.2.7",
|
|
||||||
"simple-get": "^4.0.0",
|
|
||||||
"tar-fs": "^2.0.0",
|
|
||||||
"tunnel-agent": "^0.6.0"
|
|
||||||
},
|
|
||||||
"bin": {
|
|
||||||
"prebuild-install": "bin.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/prebuild-install/node_modules/decompress-response": {
|
|
||||||
"version": "6.0.0",
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
|
||||||
"mimic-response": "^3.1.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/prebuild-install/node_modules/mimic-response": {
|
|
||||||
"version": "3.1.0",
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/prebuild-install/node_modules/simple-get": {
|
|
||||||
"version": "4.0.1",
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/feross"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "patreon",
|
|
||||||
"url": "https://www.patreon.com/feross"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "consulting",
|
|
||||||
"url": "https://feross.org/support"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
|
||||||
"decompress-response": "^6.0.0",
|
|
||||||
"once": "^1.3.1",
|
|
||||||
"simple-concat": "^1.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/prelude-ls": {
|
"node_modules/prelude-ls": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -31509,28 +31307,6 @@
|
|||||||
"version": "0.5.1",
|
"version": "0.5.1",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/rc": {
|
|
||||||
"version": "1.2.8",
|
|
||||||
"license": "(BSD-2-Clause OR MIT OR Apache-2.0)",
|
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
|
||||||
"deep-extend": "^0.6.0",
|
|
||||||
"ini": "~1.3.0",
|
|
||||||
"minimist": "^1.2.0",
|
|
||||||
"strip-json-comments": "~2.0.1"
|
|
||||||
},
|
|
||||||
"bin": {
|
|
||||||
"rc": "cli.js"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/rc/node_modules/strip-json-comments": {
|
|
||||||
"version": "2.0.1",
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.10.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/re-resizable": {
|
"node_modules/re-resizable": {
|
||||||
"version": "6.9.11",
|
"version": "6.9.11",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -34439,45 +34215,6 @@
|
|||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tar-fs": {
|
|
||||||
"version": "2.1.1",
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
|
||||||
"chownr": "^1.1.1",
|
|
||||||
"mkdirp-classic": "^0.5.2",
|
|
||||||
"pump": "^3.0.0",
|
|
||||||
"tar-stream": "^2.1.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/tar-stream": {
|
|
||||||
"version": "2.2.0",
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
|
||||||
"bl": "^4.0.3",
|
|
||||||
"end-of-stream": "^1.4.1",
|
|
||||||
"fs-constants": "^1.0.0",
|
|
||||||
"inherits": "^2.0.3",
|
|
||||||
"readable-stream": "^3.1.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/tar-stream/node_modules/readable-stream": {
|
|
||||||
"version": "3.6.2",
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
|
||||||
"inherits": "^2.0.3",
|
|
||||||
"string_decoder": "^1.1.1",
|
|
||||||
"util-deprecate": "^1.0.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/tar/node_modules/chownr": {
|
"node_modules/tar/node_modules/chownr": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
|
||||||
@@ -36280,6 +36017,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/webpack-dev-server/node_modules/@types/retry": {
|
||||||
|
"version": "0.12.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz",
|
||||||
|
"integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/webpack-dev-server/node_modules/ajv": {
|
"node_modules/webpack-dev-server/node_modules/ajv": {
|
||||||
"version": "8.12.0",
|
"version": "8.12.0",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@@ -36327,6 +36071,20 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/webpack-dev-server/node_modules/p-retry": {
|
||||||
|
"version": "4.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz",
|
||||||
|
"integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/retry": "0.12.0",
|
||||||
|
"retry": "^0.13.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/webpack-dev-server/node_modules/rimraf": {
|
"node_modules/webpack-dev-server/node_modules/rimraf": {
|
||||||
"version": "3.0.2",
|
"version": "3.0.2",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@@ -36815,13 +36573,6 @@
|
|||||||
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
|
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/xpath.js": {
|
|
||||||
"version": "1.1.0",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/xtend": {
|
"node_modules/xtend": {
|
||||||
"version": "4.0.2",
|
"version": "4.0.2",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -7,8 +7,7 @@
|
|||||||
"@azure/arm-cosmosdb": "9.1.0",
|
"@azure/arm-cosmosdb": "9.1.0",
|
||||||
"@azure/cosmos": "4.2.0-beta.1",
|
"@azure/cosmos": "4.2.0-beta.1",
|
||||||
"@azure/cosmos-language-service": "0.0.5",
|
"@azure/cosmos-language-service": "0.0.5",
|
||||||
"@azure/identity": "1.5.2",
|
"@azure/identity": "4.5.0",
|
||||||
"@azure/ms-rest-nodeauth": "3.1.1",
|
|
||||||
"@azure/msal-browser": "2.14.2",
|
"@azure/msal-browser": "2.14.2",
|
||||||
"@babel/plugin-proposal-class-properties": "7.12.1",
|
"@babel/plugin-proposal-class-properties": "7.12.1",
|
||||||
"@babel/plugin-proposal-decorators": "7.12.12",
|
"@babel/plugin-proposal-decorators": "7.12.12",
|
||||||
@@ -82,7 +81,7 @@
|
|||||||
"mkdirp": "1.0.4",
|
"mkdirp": "1.0.4",
|
||||||
"monaco-editor": "0.44.0",
|
"monaco-editor": "0.44.0",
|
||||||
"ms": "2.1.3",
|
"ms": "2.1.3",
|
||||||
"p-retry": "4.6.2",
|
"p-retry": "6.2.1",
|
||||||
"patch-package": "8.0.0",
|
"patch-package": "8.0.0",
|
||||||
"plotly.js-cartesian-dist-min": "1.52.3",
|
"plotly.js-cartesian-dist-min": "1.52.3",
|
||||||
"post-robot": "10.0.42",
|
"post-robot": "10.0.42",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[defaults]
|
[defaults]
|
||||||
group = stfaul
|
group = dataexplorer-preview
|
||||||
sku = P1v2
|
sku = P1V2
|
||||||
appserviceplan = stfaul_asp_Linux_centralus_0
|
appserviceplan = dataexplorer-preview
|
||||||
location = centralus
|
location = westus2
|
||||||
web = cosmos-explorer-preview
|
web = dataexplorer-preview
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ Cosmos Explorer Preview makes it possible to try a working version of any commit
|
|||||||
|
|
||||||
Initial support is for Hosted (Connection string only) or the Azure Portal. Examples:
|
Initial support is for Hosted (Connection string only) or the Azure Portal. Examples:
|
||||||
|
|
||||||
Connection string URLs: https://cosmos-explorer-preview.azurewebsites.net/commit/COMMIT_SHA/hostedExplorer.html
|
Connection string URLs: https://dataexplorer-preview.azurewebsites.net/commit/COMMIT_SHA/hostedExplorer.html
|
||||||
Portal URLs: https://ms.portal.azure.com/?dataExplorerSource=https://cosmos-explorer-preview.azurewebsites.net/commit/COMMIT_SHA/explorer.html#home
|
Portal URLs: https://ms.portal.azure.com/?dataExplorerSource=https://dataexplorer-preview.azurewebsites.net/commit/COMMIT_SHA/explorer.html#home
|
||||||
|
|
||||||
In both cases replace `COMMIT_SHA` with the commit you want to view. It must have already completed its build on GitHub Actions.
|
In both cases replace `COMMIT_SHA` with the commit you want to view. It must have already completed its build on GitHub Actions.
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"PROXY_PATH": "/proxy",
|
"PROXY_PATH": "/proxy",
|
||||||
"msalRedirectURI": "https://cosmos-explorer-preview.azurewebsites.net/"
|
"msalRedirectURI": "https://dataexplorer-preview.azurewebsites.net/"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,15 @@ const { createProxyMiddleware } = require("http-proxy-middleware");
|
|||||||
const port = process.env.PORT || 3000;
|
const port = process.env.PORT || 3000;
|
||||||
const fetch = require("node-fetch");
|
const fetch = require("node-fetch");
|
||||||
|
|
||||||
const api = createProxyMiddleware("/api", {
|
const backendEndpoint = "https://cdb-ms-mpac-pbe.cosmos.azure.com";
|
||||||
target: "https://cdb-ms-mpac-pbe.cosmos.azure.com",
|
const previewSiteEndpoint = "https://dataexplorer-preview.azurewebsites.net";
|
||||||
|
const previewStorageWebsiteEndpoint = "https://dataexplorerpreview.z5.web.core.windows.net/";
|
||||||
|
const githubApiUrl = "https://api.github.com/repos/Azure/cosmos-explorer";
|
||||||
|
const githubPullRequestUrl = "https://github.com/Azure/cosmos-explorer/pull";
|
||||||
|
const azurePortalMpacEndpoint = "https://ms.portal.azure.com/";
|
||||||
|
|
||||||
|
const api = createProxyMiddleware({
|
||||||
|
target: backendEndpoint,
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
logLevel: "debug",
|
logLevel: "debug",
|
||||||
bypass: (req, res) => {
|
bypass: (req, res) => {
|
||||||
@@ -15,8 +22,8 @@ const api = createProxyMiddleware("/api", {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const proxy = createProxyMiddleware("/proxy", {
|
const proxy = createProxyMiddleware({
|
||||||
target: "https://cdb-ms-mpac-pbe.cosmos.azure.com",
|
target: backendEndpoint,
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
secure: false,
|
secure: false,
|
||||||
logLevel: "debug",
|
logLevel: "debug",
|
||||||
@@ -27,35 +34,38 @@ const proxy = createProxyMiddleware("/proxy", {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const commit = createProxyMiddleware("/commit", {
|
const commit = createProxyMiddleware({
|
||||||
target: "https://cosmosexplorerpreview.blob.core.windows.net",
|
target: previewStorageWebsiteEndpoint,
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
secure: false,
|
secure: false,
|
||||||
logLevel: "debug",
|
logLevel: "debug",
|
||||||
pathRewrite: { "^/commit": "$web/" },
|
pathRewrite: { "^/commit": "/" },
|
||||||
});
|
});
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
app.use(api);
|
app.use("/api", api);
|
||||||
app.use(proxy);
|
app.use("/proxy", proxy);
|
||||||
app.use(commit);
|
app.use("/commit", commit);
|
||||||
app.get("/pull/:pr(\\d+)", (req, res) => {
|
app.get("/pull/:pr(\\d+)", (req, res) => {
|
||||||
const pr = req.params.pr;
|
const pr = req.params.pr;
|
||||||
|
if (!/^\d+$/.test(pr)) {
|
||||||
|
return res.status(400).send("Invalid pull request number");
|
||||||
|
}
|
||||||
const [, query] = req.originalUrl.split("?");
|
const [, query] = req.originalUrl.split("?");
|
||||||
const search = new URLSearchParams(query);
|
const search = new URLSearchParams(query);
|
||||||
|
|
||||||
fetch("https://api.github.com/repos/Azure/cosmos-explorer/pulls/" + pr)
|
fetch(`${githubApiUrl}/pulls/${pr}`)
|
||||||
.then((response) => response.json())
|
.then((response) => response.json())
|
||||||
.then(({ head: { ref, sha } }) => {
|
.then(({ head: { ref, sha } }) => {
|
||||||
const prUrl = new URL("https://github.com/Azure/cosmos-explorer/pull/" + pr);
|
const prUrl = new URL(`${githubPullRequestUrl}/${pr}`);
|
||||||
prUrl.hash = ref;
|
prUrl.hash = ref;
|
||||||
search.set("feature.pr", prUrl.href);
|
search.set("feature.pr", prUrl.href);
|
||||||
|
|
||||||
const explorer = new URL("https://cosmos-explorer-preview.azurewebsites.net/commit/" + sha + "/explorer.html");
|
const explorer = new URL(`${previewSiteEndpoint}/commit/${sha}/explorer.html`);
|
||||||
explorer.search = search.toString();
|
explorer.search = search.toString();
|
||||||
|
|
||||||
const portal = new URL("https://ms.portal.azure.com/");
|
const portal = new URL(azurePortalMpacEndpoint);
|
||||||
portal.searchParams.set("dataExplorerSource", explorer.href);
|
portal.searchParams.set("dataExplorerSource", explorer.href);
|
||||||
|
|
||||||
return res.redirect(portal.href);
|
return res.redirect(portal.href);
|
||||||
@@ -63,12 +73,10 @@ app.get("/pull/:pr(\\d+)", (req, res) => {
|
|||||||
.catch(() => res.sendStatus(500));
|
.catch(() => res.sendStatus(500));
|
||||||
});
|
});
|
||||||
app.get("/", (req, res) => {
|
app.get("/", (req, res) => {
|
||||||
fetch("https://api.github.com/repos/Azure/cosmos-explorer/branches/master")
|
fetch(`${githubApiUrl}/branches/master`)
|
||||||
.then((response) => response.json())
|
.then((response) => response.json())
|
||||||
.then(({ commit: { sha } }) => {
|
.then(({ commit: { sha } }) => {
|
||||||
const explorer = new URL(
|
const explorer = new URL(`${previewSiteEndpoint}/commit/${sha}/hostedExplorer.html`);
|
||||||
"https://cosmos-explorer-preview.azurewebsites.net/commit/" + sha + "/hostedExplorer.html"
|
|
||||||
);
|
|
||||||
return res.redirect(explorer.href);
|
return res.redirect(explorer.href);
|
||||||
})
|
})
|
||||||
.catch(() => res.sendStatus(500));
|
.catch(() => res.sendStatus(500));
|
||||||
|
|||||||
37097
preview/package-lock.json
generated
37097
preview/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,7 @@
|
|||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"deploy": "az webapp up --name \"cosmos-explorer-preview\" --subscription \"cosmosdb-portalteam-generaltest-msft\" --resource-group \"stfaul\"",
|
"deploy": "az webapp up --name \"dataexplorer-preview\" --subscription \"cosmosdb-portalteam-runners\" --resource-group \"dataexplorer-preview\" --runtime \"NODE:18-lts\" --sku P1V2",
|
||||||
"start": "node index.js",
|
"start": "node index.js",
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
},
|
},
|
||||||
@@ -12,7 +12,8 @@
|
|||||||
"author": "Microsoft Corporation",
|
"author": "Microsoft Corporation",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"http-proxy-middleware": "^1.1.0",
|
"http-proxy-middleware": "^3.0.3",
|
||||||
|
"node": "^18.20.6",
|
||||||
"node-fetch": "^2.6.1"
|
"node-fetch": "^2.6.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -97,6 +97,12 @@ export enum CapacityMode {
|
|||||||
Serverless = "Serverless",
|
Serverless = "Serverless",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum WorkloadType {
|
||||||
|
Learning = "Learning",
|
||||||
|
DevelopmentTesting = "Development/Testing",
|
||||||
|
Production = "Production",
|
||||||
|
None = "None",
|
||||||
|
}
|
||||||
// flight names returned from the portal are always lowercase
|
// flight names returned from the portal are always lowercase
|
||||||
export class Flights {
|
export class Flights {
|
||||||
public static readonly SettingsV2 = "settingsv2";
|
public static readonly SettingsV2 = "settingsv2";
|
||||||
@@ -119,6 +125,7 @@ export class AfecFeatures {
|
|||||||
|
|
||||||
export class TagNames {
|
export class TagNames {
|
||||||
public static defaultExperience: string = "defaultExperience";
|
public static defaultExperience: string = "defaultExperience";
|
||||||
|
public static WorkloadType: string = "hidden-workload-type";
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MongoDBAccounts {
|
export class MongoDBAccounts {
|
||||||
@@ -518,6 +525,15 @@ export class PriorityLevel {
|
|||||||
public static readonly Default = "low";
|
public static readonly Default = "low";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class ariaLabelForLearnMoreLink {
|
||||||
|
public static readonly AnalyticalStore = "Learn more about analytical store.";
|
||||||
|
public static readonly AzureSynapseLink = "Learn more about Azure Synapse Link.";
|
||||||
|
}
|
||||||
|
|
||||||
|
export class FeedbackLabels {
|
||||||
|
public static readonly provideFeedback: string = "Provide feedback";
|
||||||
|
}
|
||||||
|
|
||||||
export const QueryCopilotSampleDatabaseId = "CopilotSampleDB";
|
export const QueryCopilotSampleDatabaseId = "CopilotSampleDB";
|
||||||
export const QueryCopilotSampleContainerId = "SampleContainer";
|
export const QueryCopilotSampleContainerId = "SampleContainer";
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
import * as Cosmos from "@azure/cosmos";
|
import * as Cosmos from "@azure/cosmos";
|
||||||
import { getAuthorizationTokenUsingResourceTokens } from "Common/getAuthorizationTokenUsingResourceTokens";
|
import { getAuthorizationTokenUsingResourceTokens } from "Common/getAuthorizationTokenUsingResourceTokens";
|
||||||
|
import { CosmosDbArtifactType } from "Contracts/FabricMessagesContract";
|
||||||
import { AuthorizationToken } from "Contracts/FabricMessageTypes";
|
import { AuthorizationToken } from "Contracts/FabricMessageTypes";
|
||||||
import { checkDatabaseResourceTokensValidity } from "Platform/Fabric/FabricUtil";
|
import { checkDatabaseResourceTokensValidity, isFabricMirroredKey } from "Platform/Fabric/FabricUtil";
|
||||||
import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
|
import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
|
||||||
import { useNewPortalBackendEndpoint } from "Utils/EndpointUtils";
|
|
||||||
import { AuthType } from "../AuthType";
|
import { AuthType } from "../AuthType";
|
||||||
import { BackendApi, PriorityLevel } from "../Common/Constants";
|
import { PriorityLevel } from "../Common/Constants";
|
||||||
import * as Logger from "../Common/Logger";
|
import * as Logger from "../Common/Logger";
|
||||||
import { Platform, configContext } from "../ConfigContext";
|
import { Platform, configContext } from "../ConfigContext";
|
||||||
import { updateUserContext, userContext } from "../UserContext";
|
import { FabricArtifactInfo, updateUserContext, userContext } from "../UserContext";
|
||||||
|
import { isDataplaneRbacSupported } from "../Utils/APITypeUtils";
|
||||||
import { logConsoleError } from "../Utils/NotificationConsoleUtils";
|
import { logConsoleError } from "../Utils/NotificationConsoleUtils";
|
||||||
import * as PriorityBasedExecutionUtils from "../Utils/PriorityBasedExecutionUtils";
|
import * as PriorityBasedExecutionUtils from "../Utils/PriorityBasedExecutionUtils";
|
||||||
import { EmulatorMasterKey, HttpHeaders } from "./Constants";
|
import { EmulatorMasterKey, HttpHeaders } from "./Constants";
|
||||||
@@ -19,7 +20,7 @@ const _global = typeof self === "undefined" ? window : self;
|
|||||||
export const tokenProvider = async (requestInfo: Cosmos.RequestInfo) => {
|
export const tokenProvider = async (requestInfo: Cosmos.RequestInfo) => {
|
||||||
const { verb, resourceId, resourceType, headers } = requestInfo;
|
const { verb, resourceId, resourceType, headers } = requestInfo;
|
||||||
|
|
||||||
const dataPlaneRBACOptionEnabled = userContext.dataPlaneRbacEnabled && userContext.apiType === "SQL";
|
const dataPlaneRBACOptionEnabled = userContext.dataPlaneRbacEnabled && isDataplaneRbacSupported(userContext.apiType);
|
||||||
if (userContext.features.enableAadDataPlane || dataPlaneRBACOptionEnabled) {
|
if (userContext.features.enableAadDataPlane || dataPlaneRBACOptionEnabled) {
|
||||||
Logger.logInfo(
|
Logger.logInfo(
|
||||||
`AAD Data Plane Feature flag set to ${userContext.features.enableAadDataPlane} for account with disable local auth ${userContext.databaseAccount.properties.disableLocalAuth} `,
|
`AAD Data Plane Feature flag set to ${userContext.features.enableAadDataPlane} for account with disable local auth ${userContext.databaseAccount.properties.disableLocalAuth} `,
|
||||||
@@ -42,7 +43,7 @@ export const tokenProvider = async (requestInfo: Cosmos.RequestInfo) => {
|
|||||||
return decodeURIComponent(headers.authorization);
|
return decodeURIComponent(headers.authorization);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (configContext.platform === Platform.Fabric) {
|
if (isFabricMirroredKey()) {
|
||||||
switch (requestInfo.resourceType) {
|
switch (requestInfo.resourceType) {
|
||||||
case Cosmos.ResourceType.conflicts:
|
case Cosmos.ResourceType.conflicts:
|
||||||
case Cosmos.ResourceType.container:
|
case Cosmos.ResourceType.container:
|
||||||
@@ -54,8 +55,13 @@ export const tokenProvider = async (requestInfo: Cosmos.RequestInfo) => {
|
|||||||
// User resource tokens
|
// User resource tokens
|
||||||
// TODO userContext.fabricContext.databaseConnectionInfo can be undefined
|
// TODO userContext.fabricContext.databaseConnectionInfo can be undefined
|
||||||
headers[HttpHeaders.msDate] = new Date().toUTCString();
|
headers[HttpHeaders.msDate] = new Date().toUTCString();
|
||||||
const resourceTokens = userContext.fabricContext.databaseConnectionInfo.resourceTokens;
|
const resourceTokens = (
|
||||||
checkDatabaseResourceTokensValidity(userContext.fabricContext.databaseConnectionInfo.resourceTokensTimestamp);
|
userContext.fabricContext.artifactInfo as FabricArtifactInfo[CosmosDbArtifactType.MIRRORED_KEY]
|
||||||
|
).resourceTokenInfo.resourceTokens;
|
||||||
|
checkDatabaseResourceTokensValidity(
|
||||||
|
(userContext.fabricContext.artifactInfo as FabricArtifactInfo[CosmosDbArtifactType.MIRRORED_KEY])
|
||||||
|
.resourceTokenInfo.resourceTokensTimestamp,
|
||||||
|
);
|
||||||
return getAuthorizationTokenUsingResourceTokens(resourceTokens, requestInfo.path, requestInfo.resourceId);
|
return getAuthorizationTokenUsingResourceTokens(resourceTokens, requestInfo.path, requestInfo.resourceId);
|
||||||
|
|
||||||
case Cosmos.ResourceType.none:
|
case Cosmos.ResourceType.none:
|
||||||
@@ -66,7 +72,9 @@ export const tokenProvider = async (requestInfo: Cosmos.RequestInfo) => {
|
|||||||
// For now, these operations aren't used, so fetching the authorization token is commented out.
|
// For now, these operations aren't used, so fetching the authorization token is commented out.
|
||||||
// This provider must return a real token to pass validation by the client, so we return the cached resource token
|
// This provider must return a real token to pass validation by the client, so we return the cached resource token
|
||||||
// (which is a valid token, but won't work for these operations).
|
// (which is a valid token, but won't work for these operations).
|
||||||
const resourceTokens2 = userContext.fabricContext.databaseConnectionInfo.resourceTokens;
|
const resourceTokens2 = (
|
||||||
|
userContext.fabricContext.artifactInfo as FabricArtifactInfo[CosmosDbArtifactType.MIRRORED_KEY]
|
||||||
|
).resourceTokenInfo.resourceTokens;
|
||||||
return getAuthorizationTokenUsingResourceTokens(resourceTokens2, requestInfo.path, requestInfo.resourceId);
|
return getAuthorizationTokenUsingResourceTokens(resourceTokens2, requestInfo.path, requestInfo.resourceId);
|
||||||
|
|
||||||
/* ************** TODO: Uncomment this code if we need to support these operations **************
|
/* ************** TODO: Uncomment this code if we need to support these operations **************
|
||||||
@@ -125,10 +133,6 @@ export async function getTokenFromAuthService(
|
|||||||
resourceType: string,
|
resourceType: string,
|
||||||
resourceId?: string,
|
resourceId?: string,
|
||||||
): Promise<AuthorizationToken> {
|
): Promise<AuthorizationToken> {
|
||||||
if (!useNewPortalBackendEndpoint(BackendApi.RuntimeProxy)) {
|
|
||||||
return getTokenFromAuthService_ToBeDeprecated(verb, resourceType, resourceId);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const host: string = configContext.PORTAL_BACKEND_ENDPOINT;
|
const host: string = configContext.PORTAL_BACKEND_ENDPOINT;
|
||||||
const response: Response = await _global.fetch(host + "/api/connectionstring/runtimeproxy/authorizationtokens", {
|
const response: Response = await _global.fetch(host + "/api/connectionstring/runtimeproxy/authorizationtokens", {
|
||||||
@@ -151,34 +155,6 @@ export async function getTokenFromAuthService(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getTokenFromAuthService_ToBeDeprecated(
|
|
||||||
verb: string,
|
|
||||||
resourceType: string,
|
|
||||||
resourceId?: string,
|
|
||||||
): Promise<AuthorizationToken> {
|
|
||||||
try {
|
|
||||||
const host = configContext.BACKEND_ENDPOINT;
|
|
||||||
const response = await _global.fetch(host + "/api/guest/runtimeproxy/authorizationTokens", {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"content-type": "application/json",
|
|
||||||
"x-ms-encrypted-auth-token": userContext.accessToken,
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
verb,
|
|
||||||
resourceType,
|
|
||||||
resourceId,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
//TODO I am not sure why we have to parse the JSON again here. fetch should do it for us when we call .json()
|
|
||||||
const result = JSON.parse(await response.json());
|
|
||||||
return result;
|
|
||||||
} catch (error) {
|
|
||||||
logConsoleError(`Failed to get authorization headers for ${resourceType}: ${getErrorMessage(error)}`);
|
|
||||||
return Promise.reject(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The Capability is a bitmap, which cosmosdb backend decodes as per the below enum
|
// The Capability is a bitmap, which cosmosdb backend decodes as per the below enum
|
||||||
enum SDKSupportedCapabilities {
|
enum SDKSupportedCapabilities {
|
||||||
None = 0,
|
None = 0,
|
||||||
@@ -203,8 +179,10 @@ export function client(): Cosmos.CosmosClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let _defaultHeaders: Cosmos.CosmosHeaders = {};
|
let _defaultHeaders: Cosmos.CosmosHeaders = {};
|
||||||
|
|
||||||
_defaultHeaders["x-ms-cosmos-sdk-supportedcapabilities"] =
|
_defaultHeaders["x-ms-cosmos-sdk-supportedcapabilities"] =
|
||||||
SDKSupportedCapabilities.None | SDKSupportedCapabilities.PartitionMerge;
|
SDKSupportedCapabilities.None | SDKSupportedCapabilities.PartitionMerge;
|
||||||
|
_defaultHeaders["x-ms-cosmos-throughput-bucket"] = 1;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
userContext.authType === AuthType.ConnectionString ||
|
userContext.authType === AuthType.ConnectionString ||
|
||||||
|
|||||||
34
src/Common/DatabaseAccountUtility.test.ts
Normal file
34
src/Common/DatabaseAccountUtility.test.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { WorkloadType } from "Common/Constants";
|
||||||
|
import { getWorkloadType } from "Common/DatabaseAccountUtility";
|
||||||
|
import { DatabaseAccount, Tags } from "Contracts/DataModels";
|
||||||
|
import { updateUserContext } from "UserContext";
|
||||||
|
|
||||||
|
describe("Database Account Utility", () => {
|
||||||
|
describe("Workload Type", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
updateUserContext({
|
||||||
|
databaseAccount: {
|
||||||
|
tags: {} as Tags,
|
||||||
|
} as DatabaseAccount,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Workload Type should return Learning", () => {
|
||||||
|
updateUserContext({
|
||||||
|
databaseAccount: {
|
||||||
|
tags: {
|
||||||
|
"hidden-workload-type": WorkloadType.Learning,
|
||||||
|
} as Tags,
|
||||||
|
} as DatabaseAccount,
|
||||||
|
});
|
||||||
|
|
||||||
|
const workloadType: WorkloadType = getWorkloadType();
|
||||||
|
expect(workloadType).toBe(WorkloadType.Learning);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Workload Type should return None", () => {
|
||||||
|
const workloadType: WorkloadType = getWorkloadType();
|
||||||
|
expect(workloadType).toBe(WorkloadType.None);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { TagNames, WorkloadType } from "Common/Constants";
|
||||||
|
import { Tags } from "Contracts/DataModels";
|
||||||
import { userContext } from "../UserContext";
|
import { userContext } from "../UserContext";
|
||||||
|
|
||||||
function isVirtualNetworkFilterEnabled() {
|
function isVirtualNetworkFilterEnabled() {
|
||||||
@@ -15,3 +17,12 @@ function isPrivateEndpointConnectionsEnabled() {
|
|||||||
export function isPublicInternetAccessAllowed(): boolean {
|
export function isPublicInternetAccessAllowed(): boolean {
|
||||||
return !isVirtualNetworkFilterEnabled() && !isIpRulesEnabled() && !isPrivateEndpointConnectionsEnabled();
|
return !isVirtualNetworkFilterEnabled() && !isIpRulesEnabled() && !isPrivateEndpointConnectionsEnabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getWorkloadType(): WorkloadType {
|
||||||
|
const tags: Tags = userContext?.databaseAccount?.tags;
|
||||||
|
const workloadType: WorkloadType = tags && (tags[TagNames.WorkloadType] as WorkloadType);
|
||||||
|
if (!workloadType) {
|
||||||
|
return WorkloadType.None;
|
||||||
|
}
|
||||||
|
return workloadType;
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,16 +4,8 @@ import { configContext, resetConfigContext, updateConfigContext } from "../Confi
|
|||||||
import { DatabaseAccount } from "../Contracts/DataModels";
|
import { DatabaseAccount } from "../Contracts/DataModels";
|
||||||
import { Collection } from "../Contracts/ViewModels";
|
import { Collection } from "../Contracts/ViewModels";
|
||||||
import DocumentId from "../Explorer/Tree/DocumentId";
|
import DocumentId from "../Explorer/Tree/DocumentId";
|
||||||
import { extractFeatures } from "../Platform/Hosted/extractFeatures";
|
|
||||||
import { updateUserContext } from "../UserContext";
|
import { updateUserContext } from "../UserContext";
|
||||||
import {
|
import { deleteDocuments, getEndpoint, queryDocuments, readDocument, updateDocument } from "./MongoProxyClient";
|
||||||
deleteDocument,
|
|
||||||
getEndpoint,
|
|
||||||
getFeatureEndpointOrDefault,
|
|
||||||
queryDocuments,
|
|
||||||
readDocument,
|
|
||||||
updateDocument,
|
|
||||||
} from "./MongoProxyClient";
|
|
||||||
|
|
||||||
const databaseId = "testDB";
|
const databaseId = "testDB";
|
||||||
|
|
||||||
@@ -196,20 +188,8 @@ describe("MongoProxyClient", () => {
|
|||||||
expect.any(Object),
|
expect.any(Object),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("builds the correct proxy URL in development", () => {
|
|
||||||
updateConfigContext({
|
|
||||||
MONGO_BACKEND_ENDPOINT: "https://localhost:1234",
|
|
||||||
globallyEnabledMongoAPIs: [],
|
|
||||||
});
|
|
||||||
updateDocument(databaseId, collection, documentId, "{}");
|
|
||||||
expect(window.fetch).toHaveBeenCalledWith(
|
|
||||||
`${configContext.MONGO_PROXY_ENDPOINT}/api/mongo/explorer`,
|
|
||||||
expect.any(Object),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
describe("deleteDocument", () => {
|
describe("deleteDocuments", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
resetConfigContext();
|
resetConfigContext();
|
||||||
updateUserContext({
|
updateUserContext({
|
||||||
@@ -226,9 +206,9 @@ describe("MongoProxyClient", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("builds the correct URL", () => {
|
it("builds the correct URL", () => {
|
||||||
deleteDocument(databaseId, collection, documentId);
|
deleteDocuments(databaseId, collection, [documentId]);
|
||||||
expect(window.fetch).toHaveBeenCalledWith(
|
expect(window.fetch).toHaveBeenCalledWith(
|
||||||
`${configContext.MONGO_PROXY_ENDPOINT}/api/mongo/explorer`,
|
`${configContext.MONGO_PROXY_ENDPOINT}/api/mongo/explorer/bulkdelete`,
|
||||||
expect.any(Object),
|
expect.any(Object),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -238,9 +218,9 @@ describe("MongoProxyClient", () => {
|
|||||||
MONGO_PROXY_ENDPOINT: "https://localhost:1234",
|
MONGO_PROXY_ENDPOINT: "https://localhost:1234",
|
||||||
globallyEnabledMongoAPIs: [],
|
globallyEnabledMongoAPIs: [],
|
||||||
});
|
});
|
||||||
deleteDocument(databaseId, collection, documentId);
|
deleteDocuments(databaseId, collection, [documentId]);
|
||||||
expect(window.fetch).toHaveBeenCalledWith(
|
expect(window.fetch).toHaveBeenCalledWith(
|
||||||
`${configContext.MONGO_PROXY_ENDPOINT}/api/mongo/explorer`,
|
`${configContext.MONGO_PROXY_ENDPOINT}/api/mongo/explorer/bulkdelete`,
|
||||||
expect.any(Object),
|
expect.any(Object),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -275,33 +255,4 @@ describe("MongoProxyClient", () => {
|
|||||||
expect(endpoint).toEqual(`${configContext.MONGO_PROXY_ENDPOINT}/api/connectionstring/mongo/explorer`);
|
expect(endpoint).toEqual(`${configContext.MONGO_PROXY_ENDPOINT}/api/connectionstring/mongo/explorer`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("getFeatureEndpointOrDefault", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
resetConfigContext();
|
|
||||||
updateConfigContext({
|
|
||||||
MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Prod,
|
|
||||||
globallyEnabledMongoAPIs: [],
|
|
||||||
});
|
|
||||||
const params = new URLSearchParams({
|
|
||||||
"feature.mongoProxyEndpoint": MongoProxyEndpoints.Prod,
|
|
||||||
"feature.mongoProxyAPIs": "readDocument|createDocument",
|
|
||||||
});
|
|
||||||
const features = extractFeatures(params);
|
|
||||||
updateUserContext({
|
|
||||||
authType: AuthType.AAD,
|
|
||||||
features: features,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("returns a local endpoint", () => {
|
|
||||||
const endpoint = getFeatureEndpointOrDefault("readDocument");
|
|
||||||
expect(endpoint).toEqual(`${configContext.MONGO_PROXY_ENDPOINT}/api/mongo/explorer`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("returns a production endpoint", () => {
|
|
||||||
const endpoint = getFeatureEndpointOrDefault("DeleteDocument");
|
|
||||||
expect(endpoint).toEqual(`${configContext.MONGO_PROXY_ENDPOINT}/api/mongo/explorer`);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,20 +1,13 @@
|
|||||||
import { Constants as CosmosSDKConstants } from "@azure/cosmos";
|
import { Constants as CosmosSDKConstants } from "@azure/cosmos";
|
||||||
import {
|
|
||||||
allowedMongoProxyEndpoints_ToBeDeprecated,
|
|
||||||
defaultAllowedMongoProxyEndpoints,
|
|
||||||
validateEndpoint,
|
|
||||||
} from "Utils/EndpointUtils";
|
|
||||||
import queryString from "querystring";
|
|
||||||
import { AuthType } from "../AuthType";
|
import { AuthType } from "../AuthType";
|
||||||
import { configContext } from "../ConfigContext";
|
import { configContext } from "../ConfigContext";
|
||||||
import * as DataModels from "../Contracts/DataModels";
|
import * as DataModels from "../Contracts/DataModels";
|
||||||
import { MessageTypes } from "../Contracts/ExplorerContracts";
|
import { MessageTypes } from "../Contracts/ExplorerContracts";
|
||||||
import { Collection } from "../Contracts/ViewModels";
|
import { Collection } from "../Contracts/ViewModels";
|
||||||
import DocumentId from "../Explorer/Tree/DocumentId";
|
import DocumentId from "../Explorer/Tree/DocumentId";
|
||||||
import { hasFlag } from "../Platform/Hosted/extractFeatures";
|
|
||||||
import { userContext } from "../UserContext";
|
import { userContext } from "../UserContext";
|
||||||
import { logConsoleError } from "../Utils/NotificationConsoleUtils";
|
import { logConsoleError } from "../Utils/NotificationConsoleUtils";
|
||||||
import { ApiType, ContentType, HttpHeaders, HttpStatusCodes, MongoProxyApi, MongoProxyEndpoints } from "./Constants";
|
import { ApiType, ContentType, HttpHeaders, HttpStatusCodes } from "./Constants";
|
||||||
import { MinimalQueryIterator } from "./IteratorUtilities";
|
import { MinimalQueryIterator } from "./IteratorUtilities";
|
||||||
import { sendMessage } from "./MessageHandler";
|
import { sendMessage } from "./MessageHandler";
|
||||||
|
|
||||||
@@ -67,10 +60,6 @@ export function queryDocuments(
|
|||||||
query: string,
|
query: string,
|
||||||
continuationToken?: string,
|
continuationToken?: string,
|
||||||
): Promise<QueryResponse> {
|
): Promise<QueryResponse> {
|
||||||
if (!useMongoProxyEndpoint(MongoProxyApi.ResourceList) || !useMongoProxyEndpoint(MongoProxyApi.QueryDocuments)) {
|
|
||||||
return queryDocuments_ToBeDeprecated(databaseId, collection, isResourceList, query, continuationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { databaseAccount } = userContext;
|
const { databaseAccount } = userContext;
|
||||||
const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint;
|
const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint;
|
||||||
const params = {
|
const params = {
|
||||||
@@ -89,7 +78,7 @@ export function queryDocuments(
|
|||||||
query,
|
query,
|
||||||
};
|
};
|
||||||
|
|
||||||
const endpoint = getFeatureEndpointOrDefault(MongoProxyApi.ResourceList) || "";
|
const endpoint = getEndpoint(configContext.MONGO_PROXY_ENDPOINT) || "";
|
||||||
|
|
||||||
const headers = {
|
const headers = {
|
||||||
...defaultHeaders,
|
...defaultHeaders,
|
||||||
@@ -127,76 +116,11 @@ export function queryDocuments(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function queryDocuments_ToBeDeprecated(
|
|
||||||
databaseId: string,
|
|
||||||
collection: Collection,
|
|
||||||
isResourceList: boolean,
|
|
||||||
query: string,
|
|
||||||
continuationToken?: string,
|
|
||||||
): Promise<QueryResponse> {
|
|
||||||
const { databaseAccount } = userContext;
|
|
||||||
const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint;
|
|
||||||
const params = {
|
|
||||||
db: databaseId,
|
|
||||||
coll: collection.id(),
|
|
||||||
resourceUrl: `${resourceEndpoint}dbs/${databaseId}/colls/${collection.id()}/docs/`,
|
|
||||||
rid: collection.rid,
|
|
||||||
rtype: "docs",
|
|
||||||
sid: userContext.subscriptionId,
|
|
||||||
rg: userContext.resourceGroup,
|
|
||||||
dba: databaseAccount.name,
|
|
||||||
pk:
|
|
||||||
collection && collection.partitionKey && !collection.partitionKey.systemKey
|
|
||||||
? collection.partitionKeyProperties?.[0]
|
|
||||||
: "",
|
|
||||||
};
|
|
||||||
|
|
||||||
const endpoint = getFeatureEndpointOrDefault("resourcelist") || "";
|
|
||||||
|
|
||||||
const headers = {
|
|
||||||
...defaultHeaders,
|
|
||||||
...authHeaders(),
|
|
||||||
[CosmosSDKConstants.HttpHeaders.IsQuery]: "true",
|
|
||||||
[CosmosSDKConstants.HttpHeaders.PopulateQueryMetrics]: "true",
|
|
||||||
[CosmosSDKConstants.HttpHeaders.EnableScanInQuery]: "true",
|
|
||||||
[CosmosSDKConstants.HttpHeaders.EnableCrossPartitionQuery]: "true",
|
|
||||||
[CosmosSDKConstants.HttpHeaders.ParallelizeCrossPartitionQuery]: "true",
|
|
||||||
[HttpHeaders.contentType]: "application/query+json",
|
|
||||||
};
|
|
||||||
|
|
||||||
if (continuationToken) {
|
|
||||||
headers[CosmosSDKConstants.HttpHeaders.Continuation] = continuationToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
const path = isResourceList ? "/resourcelist" : "";
|
|
||||||
|
|
||||||
return window
|
|
||||||
.fetch(`${endpoint}${path}?${queryString.stringify(params)}`, {
|
|
||||||
method: "POST",
|
|
||||||
body: JSON.stringify({ query }),
|
|
||||||
headers,
|
|
||||||
})
|
|
||||||
.then(async (response) => {
|
|
||||||
if (response.ok) {
|
|
||||||
return {
|
|
||||||
continuationToken: response.headers.get(CosmosSDKConstants.HttpHeaders.Continuation),
|
|
||||||
documents: (await response.json()).Documents as DataModels.DocumentId[],
|
|
||||||
headers: response.headers,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
await errorHandling(response, "querying documents", params);
|
|
||||||
return undefined;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function readDocument(
|
export function readDocument(
|
||||||
databaseId: string,
|
databaseId: string,
|
||||||
collection: Collection,
|
collection: Collection,
|
||||||
documentId: DocumentId,
|
documentId: DocumentId,
|
||||||
): Promise<DataModels.DocumentId> {
|
): Promise<DataModels.DocumentId> {
|
||||||
if (!useMongoProxyEndpoint(MongoProxyApi.ReadDocument)) {
|
|
||||||
return readDocument_ToBeDeprecated(databaseId, collection, documentId);
|
|
||||||
}
|
|
||||||
const { databaseAccount } = userContext;
|
const { databaseAccount } = userContext;
|
||||||
const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint;
|
const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint;
|
||||||
const idComponents = documentId.self.split("/");
|
const idComponents = documentId.self.split("/");
|
||||||
@@ -217,7 +141,7 @@ export function readDocument(
|
|||||||
: "",
|
: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
const endpoint = getFeatureEndpointOrDefault(MongoProxyApi.ReadDocument);
|
const endpoint = getEndpoint(configContext.MONGO_PROXY_ENDPOINT);
|
||||||
|
|
||||||
return window
|
return window
|
||||||
.fetch(endpoint, {
|
.fetch(endpoint, {
|
||||||
@@ -237,61 +161,12 @@ export function readDocument(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function readDocument_ToBeDeprecated(
|
|
||||||
databaseId: string,
|
|
||||||
collection: Collection,
|
|
||||||
documentId: DocumentId,
|
|
||||||
): Promise<DataModels.DocumentId> {
|
|
||||||
const { databaseAccount } = userContext;
|
|
||||||
const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint;
|
|
||||||
const idComponents = documentId.self.split("/");
|
|
||||||
const path = idComponents.slice(0, 4).join("/");
|
|
||||||
const rid = encodeURIComponent(idComponents[5]);
|
|
||||||
const params = {
|
|
||||||
db: databaseId,
|
|
||||||
coll: collection.id(),
|
|
||||||
resourceUrl: `${resourceEndpoint}${path}/${rid}`,
|
|
||||||
rid,
|
|
||||||
rtype: "docs",
|
|
||||||
sid: userContext.subscriptionId,
|
|
||||||
rg: userContext.resourceGroup,
|
|
||||||
dba: databaseAccount.name,
|
|
||||||
pk:
|
|
||||||
documentId && documentId.partitionKey && !documentId.partitionKey.systemKey
|
|
||||||
? documentId.partitionKeyProperties?.[0]
|
|
||||||
: "",
|
|
||||||
};
|
|
||||||
|
|
||||||
const endpoint = getFeatureEndpointOrDefault("readDocument");
|
|
||||||
|
|
||||||
return window
|
|
||||||
.fetch(`${endpoint}?${queryString.stringify(params)}`, {
|
|
||||||
method: "GET",
|
|
||||||
headers: {
|
|
||||||
...defaultHeaders,
|
|
||||||
...authHeaders(),
|
|
||||||
[CosmosSDKConstants.HttpHeaders.PartitionKey]: encodeURIComponent(
|
|
||||||
JSON.stringify(documentId.partitionKeyHeader()),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then(async (response) => {
|
|
||||||
if (response.ok) {
|
|
||||||
return response.json();
|
|
||||||
}
|
|
||||||
return await errorHandling(response, "reading document", params);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createDocument(
|
export function createDocument(
|
||||||
databaseId: string,
|
databaseId: string,
|
||||||
collection: Collection,
|
collection: Collection,
|
||||||
partitionKeyProperty: string,
|
partitionKeyProperty: string,
|
||||||
documentContent: unknown,
|
documentContent: unknown,
|
||||||
): Promise<DataModels.DocumentId> {
|
): Promise<DataModels.DocumentId> {
|
||||||
if (!useMongoProxyEndpoint(MongoProxyApi.CreateDocument)) {
|
|
||||||
return createDocument_ToBeDeprecated(databaseId, collection, partitionKeyProperty, documentContent);
|
|
||||||
}
|
|
||||||
const { databaseAccount } = userContext;
|
const { databaseAccount } = userContext;
|
||||||
const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint;
|
const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint;
|
||||||
const params = {
|
const params = {
|
||||||
@@ -308,7 +183,7 @@ export function createDocument(
|
|||||||
documentContent: JSON.stringify(documentContent),
|
documentContent: JSON.stringify(documentContent),
|
||||||
};
|
};
|
||||||
|
|
||||||
const endpoint = getFeatureEndpointOrDefault(MongoProxyApi.CreateDocument);
|
const endpoint = getEndpoint(configContext.MONGO_PROXY_ENDPOINT);
|
||||||
|
|
||||||
return window
|
return window
|
||||||
.fetch(`${endpoint}/createDocument`, {
|
.fetch(`${endpoint}/createDocument`, {
|
||||||
@@ -328,54 +203,12 @@ export function createDocument(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createDocument_ToBeDeprecated(
|
|
||||||
databaseId: string,
|
|
||||||
collection: Collection,
|
|
||||||
partitionKeyProperty: string,
|
|
||||||
documentContent: unknown,
|
|
||||||
): Promise<DataModels.DocumentId> {
|
|
||||||
const { databaseAccount } = userContext;
|
|
||||||
const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint;
|
|
||||||
const params = {
|
|
||||||
db: databaseId,
|
|
||||||
coll: collection.id(),
|
|
||||||
resourceUrl: `${resourceEndpoint}dbs/${databaseId}/colls/${collection.id()}/docs/`,
|
|
||||||
rid: collection.rid,
|
|
||||||
rtype: "docs",
|
|
||||||
sid: userContext.subscriptionId,
|
|
||||||
rg: userContext.resourceGroup,
|
|
||||||
dba: databaseAccount.name,
|
|
||||||
pk: collection && collection.partitionKey && !collection.partitionKey.systemKey ? partitionKeyProperty : "",
|
|
||||||
};
|
|
||||||
|
|
||||||
const endpoint = getFeatureEndpointOrDefault("createDocument");
|
|
||||||
|
|
||||||
return window
|
|
||||||
.fetch(`${endpoint}/resourcelist?${queryString.stringify(params)}`, {
|
|
||||||
method: "POST",
|
|
||||||
body: JSON.stringify(documentContent),
|
|
||||||
headers: {
|
|
||||||
...defaultHeaders,
|
|
||||||
...authHeaders(),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then(async (response) => {
|
|
||||||
if (response.ok) {
|
|
||||||
return response.json();
|
|
||||||
}
|
|
||||||
return await errorHandling(response, "creating document", params);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateDocument(
|
export function updateDocument(
|
||||||
databaseId: string,
|
databaseId: string,
|
||||||
collection: Collection,
|
collection: Collection,
|
||||||
documentId: DocumentId,
|
documentId: DocumentId,
|
||||||
documentContent: string,
|
documentContent: string,
|
||||||
): Promise<DataModels.DocumentId> {
|
): Promise<DataModels.DocumentId> {
|
||||||
if (!useMongoProxyEndpoint(MongoProxyApi.UpdateDocument)) {
|
|
||||||
return updateDocument_ToBeDeprecated(databaseId, collection, documentId, documentContent);
|
|
||||||
}
|
|
||||||
const { databaseAccount } = userContext;
|
const { databaseAccount } = userContext;
|
||||||
const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint;
|
const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint;
|
||||||
const idComponents = documentId.self.split("/");
|
const idComponents = documentId.self.split("/");
|
||||||
@@ -396,7 +229,7 @@ export function updateDocument(
|
|||||||
: "",
|
: "",
|
||||||
documentContent,
|
documentContent,
|
||||||
};
|
};
|
||||||
const endpoint = getFeatureEndpointOrDefault(MongoProxyApi.UpdateDocument);
|
const endpoint = getEndpoint(configContext.MONGO_PROXY_ENDPOINT);
|
||||||
|
|
||||||
return window
|
return window
|
||||||
.fetch(endpoint, {
|
.fetch(endpoint, {
|
||||||
@@ -417,139 +250,6 @@ export function updateDocument(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateDocument_ToBeDeprecated(
|
|
||||||
databaseId: string,
|
|
||||||
collection: Collection,
|
|
||||||
documentId: DocumentId,
|
|
||||||
documentContent: string,
|
|
||||||
): Promise<DataModels.DocumentId> {
|
|
||||||
const { databaseAccount } = userContext;
|
|
||||||
const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint;
|
|
||||||
const idComponents = documentId.self.split("/");
|
|
||||||
const path = idComponents.slice(0, 5).join("/");
|
|
||||||
const rid = encodeURIComponent(idComponents[5]);
|
|
||||||
const params = {
|
|
||||||
db: databaseId,
|
|
||||||
coll: collection.id(),
|
|
||||||
resourceUrl: `${resourceEndpoint}${path}/${rid}`,
|
|
||||||
rid,
|
|
||||||
rtype: "docs",
|
|
||||||
sid: userContext.subscriptionId,
|
|
||||||
rg: userContext.resourceGroup,
|
|
||||||
dba: databaseAccount.name,
|
|
||||||
pk:
|
|
||||||
documentId && documentId.partitionKey && !documentId.partitionKey.systemKey
|
|
||||||
? documentId.partitionKeyProperties?.[0]
|
|
||||||
: "",
|
|
||||||
};
|
|
||||||
const endpoint = getFeatureEndpointOrDefault("updateDocument");
|
|
||||||
|
|
||||||
return window
|
|
||||||
.fetch(`${endpoint}?${queryString.stringify(params)}`, {
|
|
||||||
method: "PUT",
|
|
||||||
body: documentContent,
|
|
||||||
headers: {
|
|
||||||
...defaultHeaders,
|
|
||||||
...authHeaders(),
|
|
||||||
[HttpHeaders.contentType]: ContentType.applicationJson,
|
|
||||||
[CosmosSDKConstants.HttpHeaders.PartitionKey]: JSON.stringify(documentId.partitionKeyHeader()),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then(async (response) => {
|
|
||||||
if (response.ok) {
|
|
||||||
return response.json();
|
|
||||||
}
|
|
||||||
return await errorHandling(response, "updating document", params);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function deleteDocument(databaseId: string, collection: Collection, documentId: DocumentId): Promise<void> {
|
|
||||||
if (!useMongoProxyEndpoint(MongoProxyApi.DeleteDocument)) {
|
|
||||||
return deleteDocument_ToBeDeprecated(databaseId, collection, documentId);
|
|
||||||
}
|
|
||||||
const { databaseAccount } = userContext;
|
|
||||||
const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint;
|
|
||||||
const idComponents = documentId.self.split("/");
|
|
||||||
const path = idComponents.slice(0, 5).join("/");
|
|
||||||
const rid = encodeURIComponent(idComponents[5]);
|
|
||||||
const params = {
|
|
||||||
databaseID: databaseId,
|
|
||||||
collectionID: collection.id(),
|
|
||||||
resourceUrl: `${resourceEndpoint}${path}/${rid}`,
|
|
||||||
resourceID: rid,
|
|
||||||
resourceType: "docs",
|
|
||||||
subscriptionID: userContext.subscriptionId,
|
|
||||||
resourceGroup: userContext.resourceGroup,
|
|
||||||
databaseAccountName: databaseAccount.name,
|
|
||||||
partitionKey:
|
|
||||||
documentId && documentId.partitionKey && !documentId.partitionKey.systemKey
|
|
||||||
? documentId.partitionKeyProperties?.[0]
|
|
||||||
: "",
|
|
||||||
};
|
|
||||||
const endpoint = getFeatureEndpointOrDefault(MongoProxyApi.DeleteDocument);
|
|
||||||
|
|
||||||
return window
|
|
||||||
.fetch(endpoint, {
|
|
||||||
method: "DELETE",
|
|
||||||
body: JSON.stringify(params),
|
|
||||||
headers: {
|
|
||||||
...defaultHeaders,
|
|
||||||
...authHeaders(),
|
|
||||||
[HttpHeaders.contentType]: ContentType.applicationJson,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then(async (response) => {
|
|
||||||
if (response.ok) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
return await errorHandling(response, "deleting document", params);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function deleteDocument_ToBeDeprecated(
|
|
||||||
databaseId: string,
|
|
||||||
collection: Collection,
|
|
||||||
documentId: DocumentId,
|
|
||||||
): Promise<void> {
|
|
||||||
const { databaseAccount } = userContext;
|
|
||||||
const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint;
|
|
||||||
const idComponents = documentId.self.split("/");
|
|
||||||
const path = idComponents.slice(0, 5).join("/");
|
|
||||||
const rid = encodeURIComponent(idComponents[5]);
|
|
||||||
const params = {
|
|
||||||
db: databaseId,
|
|
||||||
coll: collection.id(),
|
|
||||||
resourceUrl: `${resourceEndpoint}${path}/${rid}`,
|
|
||||||
rid,
|
|
||||||
rtype: "docs",
|
|
||||||
sid: userContext.subscriptionId,
|
|
||||||
rg: userContext.resourceGroup,
|
|
||||||
dba: databaseAccount.name,
|
|
||||||
pk:
|
|
||||||
documentId && documentId.partitionKey && !documentId.partitionKey.systemKey
|
|
||||||
? documentId.partitionKeyProperties?.[0]
|
|
||||||
: "",
|
|
||||||
};
|
|
||||||
const endpoint = getFeatureEndpointOrDefault("deleteDocument");
|
|
||||||
|
|
||||||
return window
|
|
||||||
.fetch(`${endpoint}?${queryString.stringify(params)}`, {
|
|
||||||
method: "DELETE",
|
|
||||||
headers: {
|
|
||||||
...defaultHeaders,
|
|
||||||
...authHeaders(),
|
|
||||||
[HttpHeaders.contentType]: ContentType.applicationJson,
|
|
||||||
[CosmosSDKConstants.HttpHeaders.PartitionKey]: JSON.stringify(documentId.partitionKeyHeader()),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then(async (response) => {
|
|
||||||
if (response.ok) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
return await errorHandling(response, "deleting document", params);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function deleteDocuments(
|
export function deleteDocuments(
|
||||||
databaseId: string,
|
databaseId: string,
|
||||||
collection: Collection,
|
collection: Collection,
|
||||||
@@ -575,7 +275,7 @@ export function deleteDocuments(
|
|||||||
resourceGroup: userContext.resourceGroup,
|
resourceGroup: userContext.resourceGroup,
|
||||||
databaseAccountName: databaseAccount.name,
|
databaseAccountName: databaseAccount.name,
|
||||||
};
|
};
|
||||||
const endpoint = getFeatureEndpointOrDefault(MongoProxyApi.BulkDelete);
|
const endpoint = getEndpoint(configContext.MONGO_PROXY_ENDPOINT);
|
||||||
|
|
||||||
return window
|
return window
|
||||||
.fetch(`${endpoint}/bulkdelete`, {
|
.fetch(`${endpoint}/bulkdelete`, {
|
||||||
@@ -599,9 +299,6 @@ export function deleteDocuments(
|
|||||||
export function createMongoCollectionWithProxy(
|
export function createMongoCollectionWithProxy(
|
||||||
params: DataModels.CreateCollectionParams,
|
params: DataModels.CreateCollectionParams,
|
||||||
): Promise<DataModels.Collection> {
|
): Promise<DataModels.Collection> {
|
||||||
if (!useMongoProxyEndpoint(MongoProxyApi.CreateCollectionWithProxy)) {
|
|
||||||
return createMongoCollectionWithProxy_ToBeDeprecated(params);
|
|
||||||
}
|
|
||||||
const { databaseAccount } = userContext;
|
const { databaseAccount } = userContext;
|
||||||
const shardKey: string = params.partitionKey?.paths[0];
|
const shardKey: string = params.partitionKey?.paths[0];
|
||||||
|
|
||||||
@@ -622,7 +319,7 @@ export function createMongoCollectionWithProxy(
|
|||||||
isSharded: !!shardKey,
|
isSharded: !!shardKey,
|
||||||
};
|
};
|
||||||
|
|
||||||
const endpoint = getFeatureEndpointOrDefault(MongoProxyApi.CreateCollectionWithProxy);
|
const endpoint = getEndpoint(configContext.MONGO_PROXY_ENDPOINT);
|
||||||
|
|
||||||
return window
|
return window
|
||||||
.fetch(`${endpoint}/createCollection`, {
|
.fetch(`${endpoint}/createCollection`, {
|
||||||
@@ -642,70 +339,6 @@ export function createMongoCollectionWithProxy(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createMongoCollectionWithProxy_ToBeDeprecated(
|
|
||||||
params: DataModels.CreateCollectionParams,
|
|
||||||
): Promise<DataModels.Collection> {
|
|
||||||
const { databaseAccount } = userContext;
|
|
||||||
const shardKey: string = params.partitionKey?.paths[0];
|
|
||||||
const mongoParams: DataModels.MongoParameters = {
|
|
||||||
resourceUrl: databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint,
|
|
||||||
db: params.databaseId,
|
|
||||||
coll: params.collectionId,
|
|
||||||
pk: shardKey,
|
|
||||||
offerThroughput: params.autoPilotMaxThroughput || params.offerThroughput,
|
|
||||||
cd: params.createNewDatabase,
|
|
||||||
st: params.databaseLevelThroughput,
|
|
||||||
is: !!shardKey,
|
|
||||||
rid: "",
|
|
||||||
rtype: "colls",
|
|
||||||
sid: userContext.subscriptionId,
|
|
||||||
rg: userContext.resourceGroup,
|
|
||||||
dba: databaseAccount.name,
|
|
||||||
isAutoPilot: !!params.autoPilotMaxThroughput,
|
|
||||||
};
|
|
||||||
|
|
||||||
const endpoint = getFeatureEndpointOrDefault("createCollectionWithProxy");
|
|
||||||
|
|
||||||
return window
|
|
||||||
.fetch(
|
|
||||||
`${endpoint}/createCollection?${queryString.stringify(
|
|
||||||
mongoParams as unknown as queryString.ParsedUrlQueryInput,
|
|
||||||
)}`,
|
|
||||||
{
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
...defaultHeaders,
|
|
||||||
...authHeaders(),
|
|
||||||
[HttpHeaders.contentType]: "application/json",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.then(async (response) => {
|
|
||||||
if (response.ok) {
|
|
||||||
return response.json();
|
|
||||||
}
|
|
||||||
return await errorHandling(response, "creating collection", mongoParams);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
export function getFeatureEndpointOrDefault(feature: string): string {
|
|
||||||
let endpoint;
|
|
||||||
if (useMongoProxyEndpoint(feature)) {
|
|
||||||
endpoint = configContext.MONGO_PROXY_ENDPOINT;
|
|
||||||
} else {
|
|
||||||
const allowedMongoProxyEndpoints = configContext.allowedMongoProxyEndpoints || [
|
|
||||||
...defaultAllowedMongoProxyEndpoints,
|
|
||||||
...allowedMongoProxyEndpoints_ToBeDeprecated,
|
|
||||||
];
|
|
||||||
endpoint =
|
|
||||||
hasFlag(userContext.features.mongoProxyAPIs, feature) &&
|
|
||||||
validateEndpoint(userContext.features.mongoProxyEndpoint, allowedMongoProxyEndpoints)
|
|
||||||
? userContext.features.mongoProxyEndpoint
|
|
||||||
: configContext.MONGO_BACKEND_ENDPOINT || configContext.BACKEND_ENDPOINT;
|
|
||||||
}
|
|
||||||
|
|
||||||
return getEndpoint(endpoint);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getEndpoint(endpoint: string): string {
|
export function getEndpoint(endpoint: string): string {
|
||||||
let url = endpoint + "/api/mongo/explorer";
|
let url = endpoint + "/api/mongo/explorer";
|
||||||
|
|
||||||
@@ -719,84 +352,6 @@ export function getEndpoint(endpoint: string): string {
|
|||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useMongoProxyEndpoint(mongoProxyApi: string): boolean {
|
|
||||||
const mongoProxyEnvironmentMap: { [key: string]: string[] } = {
|
|
||||||
[MongoProxyApi.ResourceList]: [
|
|
||||||
MongoProxyEndpoints.Development,
|
|
||||||
MongoProxyEndpoints.Mpac,
|
|
||||||
MongoProxyEndpoints.Prod,
|
|
||||||
MongoProxyEndpoints.Fairfax,
|
|
||||||
MongoProxyEndpoints.Mooncake,
|
|
||||||
],
|
|
||||||
[MongoProxyApi.QueryDocuments]: [
|
|
||||||
MongoProxyEndpoints.Development,
|
|
||||||
MongoProxyEndpoints.Mpac,
|
|
||||||
MongoProxyEndpoints.Prod,
|
|
||||||
MongoProxyEndpoints.Fairfax,
|
|
||||||
MongoProxyEndpoints.Mooncake,
|
|
||||||
],
|
|
||||||
[MongoProxyApi.CreateDocument]: [
|
|
||||||
MongoProxyEndpoints.Development,
|
|
||||||
MongoProxyEndpoints.Mpac,
|
|
||||||
MongoProxyEndpoints.Prod,
|
|
||||||
MongoProxyEndpoints.Fairfax,
|
|
||||||
MongoProxyEndpoints.Mooncake,
|
|
||||||
],
|
|
||||||
[MongoProxyApi.ReadDocument]: [
|
|
||||||
MongoProxyEndpoints.Development,
|
|
||||||
MongoProxyEndpoints.Mpac,
|
|
||||||
MongoProxyEndpoints.Prod,
|
|
||||||
MongoProxyEndpoints.Fairfax,
|
|
||||||
MongoProxyEndpoints.Mooncake,
|
|
||||||
],
|
|
||||||
[MongoProxyApi.UpdateDocument]: [
|
|
||||||
MongoProxyEndpoints.Development,
|
|
||||||
MongoProxyEndpoints.Mpac,
|
|
||||||
MongoProxyEndpoints.Prod,
|
|
||||||
MongoProxyEndpoints.Fairfax,
|
|
||||||
MongoProxyEndpoints.Mooncake,
|
|
||||||
],
|
|
||||||
[MongoProxyApi.DeleteDocument]: [
|
|
||||||
MongoProxyEndpoints.Development,
|
|
||||||
MongoProxyEndpoints.Mpac,
|
|
||||||
MongoProxyEndpoints.Prod,
|
|
||||||
MongoProxyEndpoints.Fairfax,
|
|
||||||
MongoProxyEndpoints.Mooncake,
|
|
||||||
],
|
|
||||||
[MongoProxyApi.CreateCollectionWithProxy]: [
|
|
||||||
MongoProxyEndpoints.Development,
|
|
||||||
MongoProxyEndpoints.Mpac,
|
|
||||||
MongoProxyEndpoints.Prod,
|
|
||||||
MongoProxyEndpoints.Fairfax,
|
|
||||||
MongoProxyEndpoints.Mooncake,
|
|
||||||
],
|
|
||||||
[MongoProxyApi.LegacyMongoShell]: [
|
|
||||||
MongoProxyEndpoints.Development,
|
|
||||||
MongoProxyEndpoints.Mpac,
|
|
||||||
MongoProxyEndpoints.Prod,
|
|
||||||
MongoProxyEndpoints.Fairfax,
|
|
||||||
MongoProxyEndpoints.Mooncake,
|
|
||||||
],
|
|
||||||
[MongoProxyApi.BulkDelete]: [
|
|
||||||
MongoProxyEndpoints.Development,
|
|
||||||
MongoProxyEndpoints.Mpac,
|
|
||||||
MongoProxyEndpoints.Prod,
|
|
||||||
MongoProxyEndpoints.Fairfax,
|
|
||||||
MongoProxyEndpoints.Mooncake,
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!mongoProxyEnvironmentMap[mongoProxyApi] || !configContext.MONGO_PROXY_ENDPOINT) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (configContext.globallyEnabledMongoAPIs.includes(mongoProxyApi)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return mongoProxyEnvironmentMap[mongoProxyApi].includes(configContext.MONGO_PROXY_ENDPOINT);
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ThrottlingError extends Error {
|
export class ThrottlingError extends Error {
|
||||||
constructor(message: string) {
|
constructor(message: string) {
|
||||||
super(message);
|
super(message);
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { Platform, configContext } from "../ConfigContext";
|
import { isFabric } from "Platform/Fabric/FabricUtil";
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
export const StyleConstants = require("less-vars-loader!../../less/Common/Constants.less");
|
export const StyleConstants = require("less-vars-loader!../../less/Common/Constants.less");
|
||||||
|
|
||||||
export function updateStyles(): void {
|
export function updateStyles(): void {
|
||||||
if (configContext.platform === Platform.Fabric) {
|
if (isFabric()) {
|
||||||
StyleConstants.AccentMediumHigh = StyleConstants.FabricAccentMediumHigh;
|
StyleConstants.AccentMediumHigh = StyleConstants.FabricAccentMediumHigh;
|
||||||
StyleConstants.AccentMedium = StyleConstants.FabricAccentMedium;
|
StyleConstants.AccentMedium = StyleConstants.FabricAccentMedium;
|
||||||
StyleConstants.AccentLight = StyleConstants.FabricAccentLight;
|
StyleConstants.AccentLight = StyleConstants.FabricAccentLight;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { ContainerRequest, ContainerResponse, DatabaseRequest, DatabaseResponse, RequestOptions } from "@azure/cosmos";
|
import { ContainerRequest, ContainerResponse, DatabaseRequest, DatabaseResponse, RequestOptions } from "@azure/cosmos";
|
||||||
|
import { isFabricNative } from "Platform/Fabric/FabricUtil";
|
||||||
import { AuthType } from "../../AuthType";
|
import { AuthType } from "../../AuthType";
|
||||||
import * as DataModels from "../../Contracts/DataModels";
|
import * as DataModels from "../../Contracts/DataModels";
|
||||||
import { useDatabases } from "../../Explorer/useDatabases";
|
import { useDatabases } from "../../Explorer/useDatabases";
|
||||||
@@ -24,7 +25,7 @@ export const createCollection = async (params: DataModels.CreateCollectionParams
|
|||||||
);
|
);
|
||||||
try {
|
try {
|
||||||
let collection: DataModels.Collection;
|
let collection: DataModels.Collection;
|
||||||
if (userContext.authType === AuthType.AAD && !userContext.features.enableSDKoperations) {
|
if (!isFabricNative() && userContext.authType === AuthType.AAD && !userContext.features.enableSDKoperations) {
|
||||||
if (params.createNewDatabase) {
|
if (params.createNewDatabase) {
|
||||||
const createDatabaseParams: DataModels.CreateDatabaseParams = {
|
const createDatabaseParams: DataModels.CreateDatabaseParams = {
|
||||||
autoPilotMaxThroughput: params.autoPilotMaxThroughput,
|
autoPilotMaxThroughput: params.autoPilotMaxThroughput,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { isFabric } from "Platform/Fabric/FabricUtil";
|
||||||
import { AuthType } from "../../AuthType";
|
import { AuthType } from "../../AuthType";
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
import { deleteCassandraTable } from "../../Utils/arm/generatedClients/cosmos/cassandraResources";
|
import { deleteCassandraTable } from "../../Utils/arm/generatedClients/cosmos/cassandraResources";
|
||||||
@@ -12,7 +13,7 @@ import { handleError } from "../ErrorHandlingUtils";
|
|||||||
export async function deleteCollection(databaseId: string, collectionId: string): Promise<void> {
|
export async function deleteCollection(databaseId: string, collectionId: string): Promise<void> {
|
||||||
const clearMessage = logConsoleProgress(`Deleting container ${collectionId}`);
|
const clearMessage = logConsoleProgress(`Deleting container ${collectionId}`);
|
||||||
try {
|
try {
|
||||||
if (userContext.authType === AuthType.AAD && !userContext.features.enableSDKoperations) {
|
if (userContext.authType === AuthType.AAD && !userContext.features.enableSDKoperations && !isFabric()) {
|
||||||
await deleteCollectionWithARM(databaseId, collectionId);
|
await deleteCollectionWithARM(databaseId, collectionId);
|
||||||
} else {
|
} else {
|
||||||
await client().database(databaseId).container(collectionId).delete();
|
await client().database(databaseId).container(collectionId).delete();
|
||||||
|
|||||||
@@ -105,6 +105,8 @@ const readCollectionOfferWithARM = async (databaseId: string, collectionId: stri
|
|||||||
? parseInt(resource.softAllowedMaximumThroughput)
|
? parseInt(resource.softAllowedMaximumThroughput)
|
||||||
: resource.softAllowedMaximumThroughput;
|
: resource.softAllowedMaximumThroughput;
|
||||||
|
|
||||||
|
const throughputBuckets = resource?.throughputBuckets;
|
||||||
|
|
||||||
if (autoscaleSettings) {
|
if (autoscaleSettings) {
|
||||||
return {
|
return {
|
||||||
id: offerId,
|
id: offerId,
|
||||||
@@ -114,6 +116,7 @@ const readCollectionOfferWithARM = async (databaseId: string, collectionId: stri
|
|||||||
offerReplacePending: resource.offerReplacePending === "true",
|
offerReplacePending: resource.offerReplacePending === "true",
|
||||||
instantMaximumThroughput,
|
instantMaximumThroughput,
|
||||||
softAllowedMaximumThroughput,
|
softAllowedMaximumThroughput,
|
||||||
|
throughputBuckets,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,6 +128,7 @@ const readCollectionOfferWithARM = async (databaseId: string, collectionId: stri
|
|||||||
offerReplacePending: resource.offerReplacePending === "true",
|
offerReplacePending: resource.offerReplacePending === "true",
|
||||||
instantMaximumThroughput,
|
instantMaximumThroughput,
|
||||||
softAllowedMaximumThroughput,
|
softAllowedMaximumThroughput,
|
||||||
|
throughputBuckets,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { ContainerResponse } from "@azure/cosmos";
|
import { ContainerResponse } from "@azure/cosmos";
|
||||||
import { Queries } from "Common/Constants";
|
import { Queries } from "Common/Constants";
|
||||||
import { Platform, configContext } from "ConfigContext";
|
import { CosmosDbArtifactType } from "Contracts/FabricMessagesContract";
|
||||||
|
import { isFabric, isFabricMirroredKey } from "Platform/Fabric/FabricUtil";
|
||||||
import { AuthType } from "../../AuthType";
|
import { AuthType } from "../../AuthType";
|
||||||
import * as DataModels from "../../Contracts/DataModels";
|
import * as DataModels from "../../Contracts/DataModels";
|
||||||
import { userContext } from "../../UserContext";
|
import { FabricArtifactInfo, userContext } from "../../UserContext";
|
||||||
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
import { listCassandraTables } from "../../Utils/arm/generatedClients/cosmos/cassandraResources";
|
import { listCassandraTables } from "../../Utils/arm/generatedClients/cosmos/cassandraResources";
|
||||||
import { listGremlinGraphs } from "../../Utils/arm/generatedClients/cosmos/gremlinResources";
|
import { listGremlinGraphs } from "../../Utils/arm/generatedClients/cosmos/gremlinResources";
|
||||||
@@ -16,15 +17,13 @@ import { handleError } from "../ErrorHandlingUtils";
|
|||||||
export async function readCollections(databaseId: string): Promise<DataModels.Collection[]> {
|
export async function readCollections(databaseId: string): Promise<DataModels.Collection[]> {
|
||||||
const clearMessage = logConsoleProgress(`Querying containers for database ${databaseId}`);
|
const clearMessage = logConsoleProgress(`Querying containers for database ${databaseId}`);
|
||||||
|
|
||||||
if (
|
if (isFabricMirroredKey() && userContext.fabricContext?.databaseName === databaseId) {
|
||||||
configContext.platform === Platform.Fabric &&
|
|
||||||
userContext.fabricContext &&
|
|
||||||
userContext.fabricContext.databaseConnectionInfo.databaseId === databaseId
|
|
||||||
) {
|
|
||||||
const collections: DataModels.Collection[] = [];
|
const collections: DataModels.Collection[] = [];
|
||||||
const promises: Promise<ContainerResponse>[] = [];
|
const promises: Promise<ContainerResponse>[] = [];
|
||||||
|
|
||||||
for (const collectionResourceId in userContext.fabricContext.databaseConnectionInfo.resourceTokens) {
|
for (const collectionResourceId in (
|
||||||
|
userContext.fabricContext.artifactInfo as FabricArtifactInfo[CosmosDbArtifactType.MIRRORED_KEY]
|
||||||
|
).resourceTokenInfo.resourceTokens) {
|
||||||
// Dictionary key looks like this: dbs/SampleDB/colls/Container
|
// Dictionary key looks like this: dbs/SampleDB/colls/Container
|
||||||
const resourceIdObj = collectionResourceId.split("/");
|
const resourceIdObj = collectionResourceId.split("/");
|
||||||
const tokenDatabaseId = resourceIdObj[1];
|
const tokenDatabaseId = resourceIdObj[1];
|
||||||
@@ -56,7 +55,8 @@ export async function readCollections(databaseId: string): Promise<DataModels.Co
|
|||||||
if (
|
if (
|
||||||
userContext.authType === AuthType.AAD &&
|
userContext.authType === AuthType.AAD &&
|
||||||
!userContext.features.enableSDKoperations &&
|
!userContext.features.enableSDKoperations &&
|
||||||
userContext.apiType !== "Tables"
|
userContext.apiType !== "Tables" &&
|
||||||
|
!isFabric()
|
||||||
) {
|
) {
|
||||||
return await readCollectionsWithARM(databaseId);
|
return await readCollectionsWithARM(databaseId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Platform, configContext } from "ConfigContext";
|
import { isFabric, isFabricMirroredKey, isFabricNative } from "Platform/Fabric/FabricUtil";
|
||||||
import { AuthType } from "../../AuthType";
|
import { AuthType } from "../../AuthType";
|
||||||
import { Offer, ReadDatabaseOfferParams } from "../../Contracts/DataModels";
|
import { Offer, ReadDatabaseOfferParams } from "../../Contracts/DataModels";
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
@@ -11,8 +11,9 @@ import { handleError } from "../ErrorHandlingUtils";
|
|||||||
import { readOfferWithSDK } from "./readOfferWithSDK";
|
import { readOfferWithSDK } from "./readOfferWithSDK";
|
||||||
|
|
||||||
export const readDatabaseOffer = async (params: ReadDatabaseOfferParams): Promise<Offer> => {
|
export const readDatabaseOffer = async (params: ReadDatabaseOfferParams): Promise<Offer> => {
|
||||||
if (configContext.platform === Platform.Fabric) {
|
if (isFabricMirroredKey() || isFabricNative()) {
|
||||||
// TODO This works, but is very slow, because it requests the token, so we skip for now
|
// For Fabric Mirroring, it is slow, because it requests the token and we don't need it.
|
||||||
|
// For Fabric Native, it is not supported.
|
||||||
console.error("Skiping readDatabaseOffer for Fabric");
|
console.error("Skiping readDatabaseOffer for Fabric");
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@@ -23,7 +24,8 @@ export const readDatabaseOffer = async (params: ReadDatabaseOfferParams): Promis
|
|||||||
if (
|
if (
|
||||||
userContext.authType === AuthType.AAD &&
|
userContext.authType === AuthType.AAD &&
|
||||||
!userContext.features.enableSDKoperations &&
|
!userContext.features.enableSDKoperations &&
|
||||||
userContext.apiType !== "Tables"
|
userContext.apiType !== "Tables" &&
|
||||||
|
!isFabric()
|
||||||
) {
|
) {
|
||||||
return await readDatabaseOfferWithARM(params.databaseId);
|
return await readDatabaseOfferWithARM(params.databaseId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { Platform, configContext } from "ConfigContext";
|
import { CosmosDbArtifactType } from "Contracts/FabricMessagesContract";
|
||||||
|
import { isFabric, isFabricMirroredKey, isFabricNative } from "Platform/Fabric/FabricUtil";
|
||||||
import { AuthType } from "../../AuthType";
|
import { AuthType } from "../../AuthType";
|
||||||
import * as DataModels from "../../Contracts/DataModels";
|
import * as DataModels from "../../Contracts/DataModels";
|
||||||
import { userContext } from "../../UserContext";
|
import { FabricArtifactInfo, userContext } from "../../UserContext";
|
||||||
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
import { listCassandraKeyspaces } from "../../Utils/arm/generatedClients/cosmos/cassandraResources";
|
import { listCassandraKeyspaces } from "../../Utils/arm/generatedClients/cosmos/cassandraResources";
|
||||||
import { listGremlinDatabases } from "../../Utils/arm/generatedClients/cosmos/gremlinResources";
|
import { listGremlinDatabases } from "../../Utils/arm/generatedClients/cosmos/gremlinResources";
|
||||||
@@ -14,8 +15,13 @@ export async function readDatabases(): Promise<DataModels.Database[]> {
|
|||||||
let databases: DataModels.Database[];
|
let databases: DataModels.Database[];
|
||||||
const clearMessage = logConsoleProgress(`Querying databases`);
|
const clearMessage = logConsoleProgress(`Querying databases`);
|
||||||
|
|
||||||
if (configContext.platform === Platform.Fabric && userContext.fabricContext?.databaseConnectionInfo.resourceTokens) {
|
if (
|
||||||
const tokensData = userContext.fabricContext.databaseConnectionInfo;
|
isFabricMirroredKey() &&
|
||||||
|
(userContext.fabricContext?.artifactInfo as FabricArtifactInfo[CosmosDbArtifactType.MIRRORED_KEY]).resourceTokenInfo
|
||||||
|
.resourceTokens
|
||||||
|
) {
|
||||||
|
const tokensData = (userContext.fabricContext.artifactInfo as FabricArtifactInfo[CosmosDbArtifactType.MIRRORED_KEY])
|
||||||
|
.resourceTokenInfo;
|
||||||
|
|
||||||
const databaseIdsSet = new Set<string>(); // databaseId
|
const databaseIdsSet = new Set<string>(); // databaseId
|
||||||
|
|
||||||
@@ -46,13 +52,28 @@ export async function readDatabases(): Promise<DataModels.Database[]> {
|
|||||||
}));
|
}));
|
||||||
clearMessage();
|
clearMessage();
|
||||||
return databases;
|
return databases;
|
||||||
|
} else if (isFabricNative() && userContext.fabricContext?.databaseName) {
|
||||||
|
const databaseId = userContext.fabricContext.databaseName;
|
||||||
|
databases = [
|
||||||
|
{
|
||||||
|
_rid: "",
|
||||||
|
_self: "",
|
||||||
|
_etag: "",
|
||||||
|
_ts: 0,
|
||||||
|
id: databaseId,
|
||||||
|
collections: [],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
clearMessage();
|
||||||
|
return databases;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (
|
if (
|
||||||
userContext.authType === AuthType.AAD &&
|
userContext.authType === AuthType.AAD &&
|
||||||
!userContext.features.enableSDKoperations &&
|
!userContext.features.enableSDKoperations &&
|
||||||
userContext.apiType !== "Tables"
|
userContext.apiType !== "Tables" &&
|
||||||
|
!isFabric()
|
||||||
) {
|
) {
|
||||||
databases = await readDatabasesWithARM();
|
databases = await readDatabasesWithARM();
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { ContainerDefinition, RequestOptions } from "@azure/cosmos";
|
import { ContainerDefinition, RequestOptions } from "@azure/cosmos";
|
||||||
|
import { isFabric } from "Platform/Fabric/FabricUtil";
|
||||||
import { AuthType } from "../../AuthType";
|
import { AuthType } from "../../AuthType";
|
||||||
import { Collection } from "../../Contracts/DataModels";
|
import { Collection } from "../../Contracts/DataModels";
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
@@ -36,7 +37,8 @@ export async function updateCollection(
|
|||||||
if (
|
if (
|
||||||
userContext.authType === AuthType.AAD &&
|
userContext.authType === AuthType.AAD &&
|
||||||
!userContext.features.enableSDKoperations &&
|
!userContext.features.enableSDKoperations &&
|
||||||
userContext.apiType !== "Tables"
|
userContext.apiType !== "Tables" &&
|
||||||
|
!isFabric()
|
||||||
) {
|
) {
|
||||||
collection = await updateCollectionWithARM(databaseId, collectionId, newCollection);
|
collection = await updateCollectionWithARM(databaseId, collectionId, newCollection);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { OfferDefinition, RequestOptions } from "@azure/cosmos";
|
import { OfferDefinition, RequestOptions } from "@azure/cosmos";
|
||||||
|
import { isFabric } from "Platform/Fabric/FabricUtil";
|
||||||
import { AuthType } from "../../AuthType";
|
import { AuthType } from "../../AuthType";
|
||||||
import { Offer, SDKOfferDefinition, UpdateOfferParams } from "../../Contracts/DataModels";
|
import { Offer, SDKOfferDefinition, ThroughputBucket, UpdateOfferParams } from "../../Contracts/DataModels";
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
import {
|
import {
|
||||||
migrateCassandraKeyspaceToAutoscale,
|
migrateCassandraKeyspaceToAutoscale,
|
||||||
@@ -56,7 +57,7 @@ export const updateOffer = async (params: UpdateOfferParams): Promise<Offer> =>
|
|||||||
const clearMessage = logConsoleProgress(`Updating offer for ${offerResourceText}`);
|
const clearMessage = logConsoleProgress(`Updating offer for ${offerResourceText}`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (userContext.authType === AuthType.AAD && !userContext.features.enableSDKoperations) {
|
if (userContext.authType === AuthType.AAD && !userContext.features.enableSDKoperations && !isFabric()) {
|
||||||
if (params.collectionId) {
|
if (params.collectionId) {
|
||||||
updatedOffer = await updateCollectionOfferWithARM(params);
|
updatedOffer = await updateCollectionOfferWithARM(params);
|
||||||
} else if (userContext.apiType === "Tables") {
|
} else if (userContext.apiType === "Tables") {
|
||||||
@@ -359,6 +360,13 @@ const createUpdateOfferBody = (params: UpdateOfferParams): ThroughputSettingsUpd
|
|||||||
body.properties.resource.throughput = params.manualThroughput;
|
body.properties.resource.throughput = params.manualThroughput;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (params.throughputBuckets) {
|
||||||
|
const throughputBuckets = params.throughputBuckets.filter(
|
||||||
|
(bucket: ThroughputBucket) => bucket.maxThroughputPercentage !== 100,
|
||||||
|
);
|
||||||
|
body.properties.resource.throughputBuckets = throughputBuckets;
|
||||||
|
}
|
||||||
|
|
||||||
return body;
|
return body;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import {
|
|||||||
allowedGraphEndpoints,
|
allowedGraphEndpoints,
|
||||||
allowedHostedExplorerEndpoints,
|
allowedHostedExplorerEndpoints,
|
||||||
allowedJunoOrigins,
|
allowedJunoOrigins,
|
||||||
allowedMongoBackendEndpoints,
|
|
||||||
allowedMsalRedirectEndpoints,
|
allowedMsalRedirectEndpoints,
|
||||||
defaultAllowedArmEndpoints,
|
defaultAllowedArmEndpoints,
|
||||||
defaultAllowedBackendEndpoints,
|
defaultAllowedBackendEndpoints,
|
||||||
@@ -50,10 +49,8 @@ export interface ConfigContext {
|
|||||||
CATALOG_API_KEY: string;
|
CATALOG_API_KEY: string;
|
||||||
ARCADIA_ENDPOINT: string;
|
ARCADIA_ENDPOINT: string;
|
||||||
ARCADIA_LIVY_ENDPOINT_DNS_ZONE: string;
|
ARCADIA_LIVY_ENDPOINT_DNS_ZONE: string;
|
||||||
BACKEND_ENDPOINT?: string;
|
|
||||||
PORTAL_BACKEND_ENDPOINT: string;
|
PORTAL_BACKEND_ENDPOINT: string;
|
||||||
NEW_BACKEND_APIS?: BackendApi[];
|
NEW_BACKEND_APIS?: BackendApi[];
|
||||||
MONGO_BACKEND_ENDPOINT?: string;
|
|
||||||
MONGO_PROXY_ENDPOINT: string;
|
MONGO_PROXY_ENDPOINT: string;
|
||||||
CASSANDRA_PROXY_ENDPOINT: string;
|
CASSANDRA_PROXY_ENDPOINT: string;
|
||||||
NEW_CASSANDRA_APIS?: string[];
|
NEW_CASSANDRA_APIS?: string[];
|
||||||
@@ -91,7 +88,7 @@ let configContext: Readonly<ConfigContext> = {
|
|||||||
`^https:\\/\\/.*\\.analysis-df\\.net$`,
|
`^https:\\/\\/.*\\.analysis-df\\.net$`,
|
||||||
`^https:\\/\\/.*\\.analysis-df\\.windows\\.net$`,
|
`^https:\\/\\/.*\\.analysis-df\\.windows\\.net$`,
|
||||||
`^https:\\/\\/.*\\.azure-test\\.net$`,
|
`^https:\\/\\/.*\\.azure-test\\.net$`,
|
||||||
`^https:\\/\\/cosmos-explorer-preview\\.azurewebsites\\.net$`,
|
`^https:\\/\\/dataexplorer-preview\\.azurewebsites\\.net$`,
|
||||||
], // Webpack injects this at build time
|
], // Webpack injects this at build time
|
||||||
gitSha: process.env.GIT_SHA,
|
gitSha: process.env.GIT_SHA,
|
||||||
hostedExplorerURL: "https://cosmos.azure.com/",
|
hostedExplorerURL: "https://cosmos.azure.com/",
|
||||||
@@ -109,7 +106,6 @@ let configContext: Readonly<ConfigContext> = {
|
|||||||
GITHUB_CLIENT_ID: "6cb2f63cf6f7b5cbdeca", // Registered OAuth app: https://github.com/organizations/AzureCosmosDBNotebooks/settings/applications/1189306
|
GITHUB_CLIENT_ID: "6cb2f63cf6f7b5cbdeca", // Registered OAuth app: https://github.com/organizations/AzureCosmosDBNotebooks/settings/applications/1189306
|
||||||
GITHUB_TEST_ENV_CLIENT_ID: "b63fc8cbf87fd3c6e2eb", // Registered OAuth app: https://github.com/organizations/AzureCosmosDBNotebooks/settings/applications/1777772
|
GITHUB_TEST_ENV_CLIENT_ID: "b63fc8cbf87fd3c6e2eb", // Registered OAuth app: https://github.com/organizations/AzureCosmosDBNotebooks/settings/applications/1777772
|
||||||
JUNO_ENDPOINT: JunoEndpoints.Prod,
|
JUNO_ENDPOINT: JunoEndpoints.Prod,
|
||||||
BACKEND_ENDPOINT: "https://main.documentdb.ext.azure.com",
|
|
||||||
PORTAL_BACKEND_ENDPOINT: PortalBackendEndpoints.Prod,
|
PORTAL_BACKEND_ENDPOINT: PortalBackendEndpoints.Prod,
|
||||||
MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Prod,
|
MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Prod,
|
||||||
CASSANDRA_PROXY_ENDPOINT: CassandraProxyEndpoints.Prod,
|
CASSANDRA_PROXY_ENDPOINT: CassandraProxyEndpoints.Prod,
|
||||||
@@ -152,15 +148,6 @@ export function updateConfigContext(newContext: Partial<ConfigContext>): void {
|
|||||||
delete newContext.ARCADIA_ENDPOINT;
|
delete newContext.ARCADIA_ENDPOINT;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
|
||||||
!validateEndpoint(
|
|
||||||
newContext.BACKEND_ENDPOINT,
|
|
||||||
configContext.allowedBackendEndpoints || defaultAllowedBackendEndpoints,
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
delete newContext.BACKEND_ENDPOINT;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!validateEndpoint(
|
!validateEndpoint(
|
||||||
newContext.MONGO_PROXY_ENDPOINT,
|
newContext.MONGO_PROXY_ENDPOINT,
|
||||||
@@ -170,10 +157,6 @@ export function updateConfigContext(newContext: Partial<ConfigContext>): void {
|
|||||||
delete newContext.MONGO_PROXY_ENDPOINT;
|
delete newContext.MONGO_PROXY_ENDPOINT;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!validateEndpoint(newContext.MONGO_BACKEND_ENDPOINT, allowedMongoBackendEndpoints)) {
|
|
||||||
delete newContext.MONGO_BACKEND_ENDPOINT;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!validateEndpoint(
|
!validateEndpoint(
|
||||||
newContext.CASSANDRA_PROXY_ENDPOINT,
|
newContext.CASSANDRA_PROXY_ENDPOINT,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ export interface ArmEntity {
|
|||||||
location: string;
|
location: string;
|
||||||
type: string;
|
type: string;
|
||||||
kind: string;
|
kind: string;
|
||||||
|
tags?: Tags;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DatabaseAccount extends ArmEntity {
|
export interface DatabaseAccount extends ArmEntity {
|
||||||
@@ -274,6 +275,12 @@ export interface Offer {
|
|||||||
offerReplacePending: boolean;
|
offerReplacePending: boolean;
|
||||||
instantMaximumThroughput?: number;
|
instantMaximumThroughput?: number;
|
||||||
softAllowedMaximumThroughput?: number;
|
softAllowedMaximumThroughput?: number;
|
||||||
|
throughputBuckets?: ThroughputBucket[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ThroughputBucket {
|
||||||
|
id: number;
|
||||||
|
maxThroughputPercentage: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SDKOfferDefinition extends Resource {
|
export interface SDKOfferDefinition extends Resource {
|
||||||
@@ -396,6 +403,7 @@ export interface UpdateOfferParams {
|
|||||||
collectionId?: string;
|
collectionId?: string;
|
||||||
migrateToAutoPilot?: boolean;
|
migrateToAutoPilot?: boolean;
|
||||||
migrateToManual?: boolean;
|
migrateToManual?: boolean;
|
||||||
|
throughputBuckets?: ThroughputBucket[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Notification {
|
export interface Notification {
|
||||||
@@ -663,3 +671,5 @@ export interface FeatureRegistration {
|
|||||||
state: string;
|
state: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type Tags = { [key: string]: string };
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
export enum FabricMessageTypes {
|
export enum FabricMessageTypes {
|
||||||
GetAuthorizationToken = "GetAuthorizationToken",
|
GetAuthorizationToken = "GetAuthorizationToken",
|
||||||
GetAllResourceTokens = "GetAllResourceTokens",
|
GetAllResourceTokens = "GetAllResourceTokens",
|
||||||
|
GetAccessToken = "GetAccessToken",
|
||||||
Ready = "Ready",
|
Ready = "Ready",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,47 +1,9 @@
|
|||||||
import { AuthorizationToken } from "Contracts/FabricMessageTypes";
|
import { AuthorizationToken } from "./FabricMessageTypes";
|
||||||
|
|
||||||
// This is the version of these messages
|
// This is the version of these messages
|
||||||
export const FABRIC_RPC_VERSION = "2";
|
export const FABRIC_RPC_VERSION = "FabricMessageV3";
|
||||||
|
|
||||||
// Fabric to Data Explorer
|
// Fabric to Data Explorer
|
||||||
|
|
||||||
// TODO Deprecated. Remove this section once DE is updated
|
|
||||||
export type FabricMessageV1 =
|
|
||||||
| {
|
|
||||||
type: "newContainer";
|
|
||||||
databaseName: string;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: "initialize";
|
|
||||||
message: {
|
|
||||||
endpoint: string | undefined;
|
|
||||||
databaseId: string | undefined;
|
|
||||||
resourceTokens: unknown | undefined;
|
|
||||||
resourceTokensTimestamp: number | undefined;
|
|
||||||
error: string | undefined;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: "authorizationToken";
|
|
||||||
message: {
|
|
||||||
id: string;
|
|
||||||
error: string | undefined;
|
|
||||||
data: AuthorizationToken | undefined;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: "allResourceTokens";
|
|
||||||
message: {
|
|
||||||
id: string;
|
|
||||||
error: string | undefined;
|
|
||||||
endpoint: string | undefined;
|
|
||||||
databaseId: string | undefined;
|
|
||||||
resourceTokens: unknown | undefined;
|
|
||||||
resourceTokensTimestamp: number | undefined;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
// -----------------------------
|
|
||||||
|
|
||||||
export type FabricMessageV2 =
|
export type FabricMessageV2 =
|
||||||
| {
|
| {
|
||||||
type: "newContainer";
|
type: "newContainer";
|
||||||
@@ -69,7 +31,7 @@ export type FabricMessageV2 =
|
|||||||
message: {
|
message: {
|
||||||
id: string;
|
id: string;
|
||||||
error: string | undefined;
|
error: string | undefined;
|
||||||
data: FabricDatabaseConnectionInfo | undefined;
|
data: ResourceTokenInfo | undefined;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
@@ -79,17 +41,81 @@ export type FabricMessageV2 =
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CosmosDBTokenResponse = {
|
export type FabricMessageV3 =
|
||||||
token: string;
|
| {
|
||||||
date: string;
|
type: "newContainer";
|
||||||
};
|
databaseName: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "initialize";
|
||||||
|
version: string;
|
||||||
|
id: string;
|
||||||
|
message: InitializeMessageV3<CosmosDbArtifactType>;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "authorizationToken";
|
||||||
|
message: {
|
||||||
|
id: string;
|
||||||
|
error: string | undefined;
|
||||||
|
data: AuthorizationToken | undefined;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "allResourceTokens_v2";
|
||||||
|
message: {
|
||||||
|
id: string;
|
||||||
|
error: string | undefined;
|
||||||
|
data: ResourceTokenInfo | undefined;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "explorerVisible";
|
||||||
|
message: {
|
||||||
|
visible: boolean;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "accessToken";
|
||||||
|
message: {
|
||||||
|
id: string;
|
||||||
|
error: string | undefined;
|
||||||
|
data: { accessToken: string };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export type CosmosDBConnectionInfoResponse = {
|
export enum CosmosDbArtifactType {
|
||||||
|
MIRRORED_KEY = "MIRRORED_KEY",
|
||||||
|
MIRRORED_AAD = "MIRRORED_AAD",
|
||||||
|
NATIVE = "NATIVE",
|
||||||
|
}
|
||||||
|
export interface ArtifactConnectionInfo {
|
||||||
|
[CosmosDbArtifactType.MIRRORED_KEY]: { connectionId: string };
|
||||||
|
[CosmosDbArtifactType.MIRRORED_AAD]: AccessTokenConnectionInfo;
|
||||||
|
[CosmosDbArtifactType.NATIVE]: AccessTokenConnectionInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AccessTokenConnectionInfo {
|
||||||
|
accessToken: string;
|
||||||
|
databaseName: string;
|
||||||
|
accountEndpoint: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface InitializeMessageV3<T extends CosmosDbArtifactType> {
|
||||||
|
connectionId: string;
|
||||||
|
isVisible: boolean;
|
||||||
|
isReadOnly: boolean;
|
||||||
|
artifactType: T;
|
||||||
|
artifactConnectionInfo: ArtifactConnectionInfo[T];
|
||||||
|
}
|
||||||
|
export interface CosmosDBConnectionInfoResponse {
|
||||||
endpoint: string;
|
endpoint: string;
|
||||||
databaseId: string;
|
databaseId: string;
|
||||||
resourceTokens: { [resourceId: string]: string };
|
resourceTokens: Record<string, string> | undefined;
|
||||||
};
|
accessToken: string | undefined;
|
||||||
|
isReadOnly: boolean;
|
||||||
|
credentialType: "Key" | "OAuth2" | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
export interface FabricDatabaseConnectionInfo extends CosmosDBConnectionInfoResponse {
|
export interface ResourceTokenInfo extends CosmosDBConnectionInfoResponse {
|
||||||
resourceTokensTimestamp: number;
|
resourceTokensTimestamp: number;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ export enum MessageTypes {
|
|||||||
OpenPostgreSQLPasswordReset,
|
OpenPostgreSQLPasswordReset,
|
||||||
OpenPostgresNetworkingBlade,
|
OpenPostgresNetworkingBlade,
|
||||||
OpenCosmosDBNetworkingBlade,
|
OpenCosmosDBNetworkingBlade,
|
||||||
DisplayNPSSurvey,
|
DisplayNPSSurvey, // unused
|
||||||
OpenVCoreMongoNetworkingBlade,
|
OpenVCoreMongoNetworkingBlade,
|
||||||
OpenVCoreMongoConnectionStringsBlade,
|
OpenVCoreMongoConnectionStringsBlade,
|
||||||
GetAuthorizationToken, // unused. Can be removed if the portal uses the same list of enums.
|
GetAuthorizationToken, // unused. Can be removed if the portal uses the same list of enums.
|
||||||
|
|||||||
@@ -406,7 +406,6 @@ export interface DataExplorerInputsFrame {
|
|||||||
csmEndpoint?: string;
|
csmEndpoint?: string;
|
||||||
dnsSuffix?: string;
|
dnsSuffix?: string;
|
||||||
serverId?: string;
|
serverId?: string;
|
||||||
extensionEndpoint?: string;
|
|
||||||
portalBackendEndpoint?: string;
|
portalBackendEndpoint?: string;
|
||||||
mongoProxyEndpoint?: string;
|
mongoProxyEndpoint?: string;
|
||||||
cassandraProxyEndpoint?: string;
|
cassandraProxyEndpoint?: string;
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
|
import { configContext, Platform } from "ConfigContext";
|
||||||
import { TreeNodeMenuItem } from "Explorer/Controls/TreeComponent/TreeNodeComponent";
|
import { TreeNodeMenuItem } from "Explorer/Controls/TreeComponent/TreeNodeComponent";
|
||||||
import { useDatabases } from "Explorer/useDatabases";
|
import { useDatabases } from "Explorer/useDatabases";
|
||||||
|
import { isFabric, isFabricNative } from "Platform/Fabric/FabricUtil";
|
||||||
import { Action } from "Shared/Telemetry/TelemetryConstants";
|
import { Action } from "Shared/Telemetry/TelemetryConstants";
|
||||||
import { traceOpen } from "Shared/Telemetry/TelemetryProcessor";
|
import { traceOpen } from "Shared/Telemetry/TelemetryProcessor";
|
||||||
import { ReactTabKind, useTabs } from "hooks/useTabs";
|
import { ReactTabKind, useTabs } from "hooks/useTabs";
|
||||||
@@ -19,7 +21,6 @@ import * as ViewModels from "../Contracts/ViewModels";
|
|||||||
import { userContext } from "../UserContext";
|
import { userContext } from "../UserContext";
|
||||||
import { getCollectionName, getDatabaseName } from "../Utils/APITypeUtils";
|
import { getCollectionName, getDatabaseName } from "../Utils/APITypeUtils";
|
||||||
import { useSidePanel } from "../hooks/useSidePanel";
|
import { useSidePanel } from "../hooks/useSidePanel";
|
||||||
import { Platform, configContext } from "./../ConfigContext";
|
|
||||||
import Explorer from "./Explorer";
|
import Explorer from "./Explorer";
|
||||||
import { useNotebook } from "./Notebook/useNotebook";
|
import { useNotebook } from "./Notebook/useNotebook";
|
||||||
import { DeleteCollectionConfirmationPane } from "./Panes/DeleteCollectionConfirmationPane/DeleteCollectionConfirmationPane";
|
import { DeleteCollectionConfirmationPane } from "./Panes/DeleteCollectionConfirmationPane/DeleteCollectionConfirmationPane";
|
||||||
@@ -41,7 +42,7 @@ export interface DatabaseContextMenuButtonParams {
|
|||||||
* New resource tree (in ReactJS)
|
* New resource tree (in ReactJS)
|
||||||
*/
|
*/
|
||||||
export const createDatabaseContextMenu = (container: Explorer, databaseId: string): TreeNodeMenuItem[] => {
|
export const createDatabaseContextMenu = (container: Explorer, databaseId: string): TreeNodeMenuItem[] => {
|
||||||
if (configContext.platform === Platform.Fabric && userContext.fabricContext?.isReadOnly) {
|
if (isFabric() && userContext.fabricContext?.isReadOnly) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,7 +54,7 @@ export const createDatabaseContextMenu = (container: Explorer, databaseId: strin
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
if (userContext.apiType !== "Tables" || userContext.features.enableSDKoperations) {
|
if (!isFabricNative() && (userContext.apiType !== "Tables" || userContext.features.enableSDKoperations)) {
|
||||||
items.push({
|
items.push({
|
||||||
iconSrc: DeleteDatabaseIcon,
|
iconSrc: DeleteDatabaseIcon,
|
||||||
onClick: (lastFocusedElement?: React.RefObject<HTMLElement>) => {
|
onClick: (lastFocusedElement?: React.RefObject<HTMLElement>) => {
|
||||||
@@ -145,7 +146,7 @@ export const createCollectionContextMenuButton = (
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (configContext.platform !== Platform.Fabric) {
|
if (!isFabric() || (isFabric() && !userContext.fabricContext?.isReadOnly)) {
|
||||||
items.push({
|
items.push({
|
||||||
iconSrc: DeleteCollectionIcon,
|
iconSrc: DeleteCollectionIcon,
|
||||||
onClick: (lastFocusedElement?: React.RefObject<HTMLElement>) => {
|
onClick: (lastFocusedElement?: React.RefObject<HTMLElement>) => {
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
|
import { AuthType } from "AuthType";
|
||||||
import { shallow } from "enzyme";
|
import { shallow } from "enzyme";
|
||||||
import ko from "knockout";
|
import ko from "knockout";
|
||||||
|
import { Features } from "Platform/Hosted/extractFeatures";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { updateCollection } from "../../../Common/dataAccess/updateCollection";
|
import { updateCollection } from "../../../Common/dataAccess/updateCollection";
|
||||||
import { updateOffer } from "../../../Common/dataAccess/updateOffer";
|
import { updateOffer } from "../../../Common/dataAccess/updateOffer";
|
||||||
@@ -247,4 +249,42 @@ describe("SettingsComponent", () => {
|
|||||||
expect(conflictResolutionPolicy.mode).toEqual(DataModels.ConflictResolutionMode.Custom);
|
expect(conflictResolutionPolicy.mode).toEqual(DataModels.ConflictResolutionMode.Custom);
|
||||||
expect(conflictResolutionPolicy.conflictResolutionProcedure).toEqual(expectSprocPath);
|
expect(conflictResolutionPolicy.conflictResolutionProcedure).toEqual(expectSprocPath);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should save throughput bucket changes when Save button is clicked", async () => {
|
||||||
|
updateUserContext({
|
||||||
|
apiType: "SQL",
|
||||||
|
features: { enableThroughputBuckets: true } as Features,
|
||||||
|
authType: AuthType.AAD,
|
||||||
|
});
|
||||||
|
|
||||||
|
const wrapper = shallow(<SettingsComponent {...baseProps} />);
|
||||||
|
|
||||||
|
const settingsComponentInstance = wrapper.instance() as SettingsComponent;
|
||||||
|
const isEnabled = settingsComponentInstance["throughputBucketsEnabled"];
|
||||||
|
expect(isEnabled).toBe(true);
|
||||||
|
|
||||||
|
wrapper.setState({
|
||||||
|
isThroughputBucketsSaveable: true,
|
||||||
|
throughputBuckets: [
|
||||||
|
{ id: 1, maxThroughputPercentage: 70 },
|
||||||
|
{ id: 2, maxThroughputPercentage: 60 },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
await settingsComponentInstance.onSaveClick();
|
||||||
|
|
||||||
|
expect(updateOffer).toHaveBeenCalledWith({
|
||||||
|
databaseId: collection.databaseId,
|
||||||
|
collectionId: collection.id(),
|
||||||
|
currentOffer: expect.any(Object),
|
||||||
|
autopilotThroughput: collection.offer().autoscaleMaxThroughput,
|
||||||
|
manualThroughput: collection.offer().manualThroughput,
|
||||||
|
throughputBuckets: [
|
||||||
|
{ id: 1, maxThroughputPercentage: 70 },
|
||||||
|
{ id: 2, maxThroughputPercentage: 60 },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(wrapper.state("isThroughputBucketsSaveable")).toBe(false);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ import {
|
|||||||
ContainerPolicyComponent,
|
ContainerPolicyComponent,
|
||||||
ContainerPolicyComponentProps,
|
ContainerPolicyComponentProps,
|
||||||
} from "Explorer/Controls/Settings/SettingsSubComponents/ContainerPolicyComponent";
|
} from "Explorer/Controls/Settings/SettingsSubComponents/ContainerPolicyComponent";
|
||||||
|
import {
|
||||||
|
ThroughputBucketsComponent,
|
||||||
|
ThroughputBucketsComponentProps,
|
||||||
|
} from "Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/ThroughputBucketsComponent";
|
||||||
import { useDatabases } from "Explorer/useDatabases";
|
import { useDatabases } from "Explorer/useDatabases";
|
||||||
import { isFullTextSearchEnabled, isVectorSearchEnabled } from "Utils/CapabilityUtils";
|
import { isFullTextSearchEnabled, isVectorSearchEnabled } from "Utils/CapabilityUtils";
|
||||||
import { isRunningOnPublicCloud } from "Utils/CloudUtils";
|
import { isRunningOnPublicCloud } from "Utils/CloudUtils";
|
||||||
@@ -86,6 +90,8 @@ export interface SettingsComponentState {
|
|||||||
wasAutopilotOriginallySet: boolean;
|
wasAutopilotOriginallySet: boolean;
|
||||||
isScaleSaveable: boolean;
|
isScaleSaveable: boolean;
|
||||||
isScaleDiscardable: boolean;
|
isScaleDiscardable: boolean;
|
||||||
|
throughputBuckets: DataModels.ThroughputBucket[];
|
||||||
|
throughputBucketsBaseline: DataModels.ThroughputBucket[];
|
||||||
throughputError: string;
|
throughputError: string;
|
||||||
|
|
||||||
timeToLive: TtlType;
|
timeToLive: TtlType;
|
||||||
@@ -104,6 +110,7 @@ export interface SettingsComponentState {
|
|||||||
changeFeedPolicyBaseline: ChangeFeedPolicyState;
|
changeFeedPolicyBaseline: ChangeFeedPolicyState;
|
||||||
isSubSettingsSaveable: boolean;
|
isSubSettingsSaveable: boolean;
|
||||||
isSubSettingsDiscardable: boolean;
|
isSubSettingsDiscardable: boolean;
|
||||||
|
isThroughputBucketsSaveable: boolean;
|
||||||
|
|
||||||
vectorEmbeddingPolicy: DataModels.VectorEmbeddingPolicy;
|
vectorEmbeddingPolicy: DataModels.VectorEmbeddingPolicy;
|
||||||
vectorEmbeddingPolicyBaseline: DataModels.VectorEmbeddingPolicy;
|
vectorEmbeddingPolicyBaseline: DataModels.VectorEmbeddingPolicy;
|
||||||
@@ -158,6 +165,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
private isVectorSearchEnabled: boolean;
|
private isVectorSearchEnabled: boolean;
|
||||||
private isFullTextSearchEnabled: boolean;
|
private isFullTextSearchEnabled: boolean;
|
||||||
private totalThroughputUsed: number;
|
private totalThroughputUsed: number;
|
||||||
|
private throughputBucketsEnabled: boolean;
|
||||||
public mongoDBCollectionResource: MongoDBCollectionResource;
|
public mongoDBCollectionResource: MongoDBCollectionResource;
|
||||||
|
|
||||||
constructor(props: SettingsComponentProps) {
|
constructor(props: SettingsComponentProps) {
|
||||||
@@ -175,6 +183,10 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
this.isFullTextSearchEnabled = isFullTextSearchEnabled() && !hasDatabaseSharedThroughput(this.collection);
|
this.isFullTextSearchEnabled = isFullTextSearchEnabled() && !hasDatabaseSharedThroughput(this.collection);
|
||||||
|
|
||||||
this.changeFeedPolicyVisible = userContext.features.enableChangeFeedPolicy;
|
this.changeFeedPolicyVisible = userContext.features.enableChangeFeedPolicy;
|
||||||
|
this.throughputBucketsEnabled =
|
||||||
|
userContext.apiType === "SQL" &&
|
||||||
|
userContext.features.enableThroughputBuckets &&
|
||||||
|
userContext.authType === AuthType.AAD;
|
||||||
|
|
||||||
// Mongo container with system partition key still treat as "Fixed"
|
// Mongo container with system partition key still treat as "Fixed"
|
||||||
this.isFixedContainer =
|
this.isFixedContainer =
|
||||||
@@ -193,6 +205,8 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
wasAutopilotOriginallySet: false,
|
wasAutopilotOriginallySet: false,
|
||||||
isScaleSaveable: false,
|
isScaleSaveable: false,
|
||||||
isScaleDiscardable: false,
|
isScaleDiscardable: false,
|
||||||
|
throughputBuckets: undefined,
|
||||||
|
throughputBucketsBaseline: undefined,
|
||||||
throughputError: undefined,
|
throughputError: undefined,
|
||||||
|
|
||||||
timeToLive: undefined,
|
timeToLive: undefined,
|
||||||
@@ -211,6 +225,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
changeFeedPolicyBaseline: undefined,
|
changeFeedPolicyBaseline: undefined,
|
||||||
isSubSettingsSaveable: false,
|
isSubSettingsSaveable: false,
|
||||||
isSubSettingsDiscardable: false,
|
isSubSettingsDiscardable: false,
|
||||||
|
isThroughputBucketsSaveable: false,
|
||||||
|
|
||||||
vectorEmbeddingPolicy: undefined,
|
vectorEmbeddingPolicy: undefined,
|
||||||
vectorEmbeddingPolicyBaseline: undefined,
|
vectorEmbeddingPolicyBaseline: undefined,
|
||||||
@@ -327,7 +342,8 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
this.state.isIndexingPolicyDirty ||
|
this.state.isIndexingPolicyDirty ||
|
||||||
this.state.isConflictResolutionDirty ||
|
this.state.isConflictResolutionDirty ||
|
||||||
this.state.isComputedPropertiesDirty ||
|
this.state.isComputedPropertiesDirty ||
|
||||||
(!!this.state.currentMongoIndexes && this.state.isMongoIndexingPolicySaveable)
|
(!!this.state.currentMongoIndexes && this.state.isMongoIndexingPolicySaveable) ||
|
||||||
|
this.state.isThroughputBucketsSaveable
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -339,7 +355,8 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
this.state.isIndexingPolicyDirty ||
|
this.state.isIndexingPolicyDirty ||
|
||||||
this.state.isConflictResolutionDirty ||
|
this.state.isConflictResolutionDirty ||
|
||||||
this.state.isComputedPropertiesDirty ||
|
this.state.isComputedPropertiesDirty ||
|
||||||
(!!this.state.currentMongoIndexes && this.state.isMongoIndexingPolicyDiscardable)
|
(!!this.state.currentMongoIndexes && this.state.isMongoIndexingPolicyDiscardable) ||
|
||||||
|
this.state.isThroughputBucketsSaveable
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -419,6 +436,8 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
throughput: this.state.throughputBaseline,
|
throughput: this.state.throughputBaseline,
|
||||||
|
throughputBuckets: this.state.throughputBucketsBaseline,
|
||||||
|
throughputBucketsBaseline: this.state.throughputBucketsBaseline,
|
||||||
timeToLive: this.state.timeToLiveBaseline,
|
timeToLive: this.state.timeToLiveBaseline,
|
||||||
timeToLiveSeconds: this.state.timeToLiveSecondsBaseline,
|
timeToLiveSeconds: this.state.timeToLiveSecondsBaseline,
|
||||||
displayedTtlSeconds: this.state.displayedTtlSecondsBaseline,
|
displayedTtlSeconds: this.state.displayedTtlSecondsBaseline,
|
||||||
@@ -441,6 +460,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
isScaleSaveable: false,
|
isScaleSaveable: false,
|
||||||
isScaleDiscardable: false,
|
isScaleDiscardable: false,
|
||||||
isSubSettingsSaveable: false,
|
isSubSettingsSaveable: false,
|
||||||
|
isThroughputBucketsSaveable: false,
|
||||||
isSubSettingsDiscardable: false,
|
isSubSettingsDiscardable: false,
|
||||||
isContainerPolicyDirty: false,
|
isContainerPolicyDirty: false,
|
||||||
isIndexingPolicyDirty: false,
|
isIndexingPolicyDirty: false,
|
||||||
@@ -479,6 +499,10 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
private onIndexingPolicyContentChange = (newIndexingPolicy: DataModels.IndexingPolicy): void =>
|
private onIndexingPolicyContentChange = (newIndexingPolicy: DataModels.IndexingPolicy): void =>
|
||||||
this.setState({ indexingPolicyContent: newIndexingPolicy });
|
this.setState({ indexingPolicyContent: newIndexingPolicy });
|
||||||
|
|
||||||
|
private onThroughputBucketsSaveableChange = (isSaveable: boolean): void => {
|
||||||
|
this.setState({ isThroughputBucketsSaveable: isSaveable });
|
||||||
|
};
|
||||||
|
|
||||||
private resetShouldDiscardContainerPolicies = (): void => this.setState({ shouldDiscardContainerPolicies: false });
|
private resetShouldDiscardContainerPolicies = (): void => this.setState({ shouldDiscardContainerPolicies: false });
|
||||||
|
|
||||||
private resetShouldDiscardIndexingPolicy = (): void => this.setState({ shouldDiscardIndexingPolicy: false });
|
private resetShouldDiscardIndexingPolicy = (): void => this.setState({ shouldDiscardIndexingPolicy: false });
|
||||||
@@ -749,9 +773,13 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
] as DataModels.ComputedProperties;
|
] as DataModels.ComputedProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const throughputBuckets = this.offer?.throughputBuckets;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
throughput: offerThroughput,
|
throughput: offerThroughput,
|
||||||
throughputBaseline: offerThroughput,
|
throughputBaseline: offerThroughput,
|
||||||
|
throughputBuckets,
|
||||||
|
throughputBucketsBaseline: throughputBuckets,
|
||||||
changeFeedPolicy: changeFeedPolicy,
|
changeFeedPolicy: changeFeedPolicy,
|
||||||
changeFeedPolicyBaseline: changeFeedPolicy,
|
changeFeedPolicyBaseline: changeFeedPolicy,
|
||||||
timeToLive: timeToLive,
|
timeToLive: timeToLive,
|
||||||
@@ -839,6 +867,10 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
this.setState({ throughput: newThroughput, throughputError });
|
this.setState({ throughput: newThroughput, throughputError });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private onThroughputBucketChange = (throughputBuckets: DataModels.ThroughputBucket[]): void => {
|
||||||
|
this.setState({ throughputBuckets });
|
||||||
|
};
|
||||||
|
|
||||||
private onAutoPilotSelected = (isAutoPilotSelected: boolean): void =>
|
private onAutoPilotSelected = (isAutoPilotSelected: boolean): void =>
|
||||||
this.setState({ isAutoPilotSelected: isAutoPilotSelected });
|
this.setState({ isAutoPilotSelected: isAutoPilotSelected });
|
||||||
|
|
||||||
@@ -1029,6 +1061,24 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.throughputBucketsEnabled && this.state.isThroughputBucketsSaveable) {
|
||||||
|
const updatedOffer: DataModels.Offer = await updateOffer({
|
||||||
|
databaseId: this.collection.databaseId,
|
||||||
|
collectionId: this.collection.id(),
|
||||||
|
currentOffer: this.collection.offer(),
|
||||||
|
autopilotThroughput: this.collection.offer().autoscaleMaxThroughput
|
||||||
|
? this.collection.offer().autoscaleMaxThroughput
|
||||||
|
: undefined,
|
||||||
|
manualThroughput: this.collection.offer().manualThroughput
|
||||||
|
? this.collection.offer().manualThroughput
|
||||||
|
: undefined,
|
||||||
|
throughputBuckets: this.state.throughputBuckets,
|
||||||
|
});
|
||||||
|
this.collection.offer(updatedOffer);
|
||||||
|
this.offer = updatedOffer;
|
||||||
|
this.setState({ isThroughputBucketsSaveable: false });
|
||||||
|
}
|
||||||
|
|
||||||
if (this.state.isScaleSaveable) {
|
if (this.state.isScaleSaveable) {
|
||||||
const updateOfferParams: DataModels.UpdateOfferParams = {
|
const updateOfferParams: DataModels.UpdateOfferParams = {
|
||||||
databaseId: this.collection.databaseId,
|
databaseId: this.collection.databaseId,
|
||||||
@@ -1209,6 +1259,13 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
onConflictResolutionDirtyChange: this.onConflictResolutionDirtyChange,
|
onConflictResolutionDirtyChange: this.onConflictResolutionDirtyChange,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const throughputBucketsComponentProps: ThroughputBucketsComponentProps = {
|
||||||
|
currentBuckets: this.state.throughputBuckets,
|
||||||
|
throughputBucketsBaseline: this.state.throughputBucketsBaseline,
|
||||||
|
onBucketsChange: this.onThroughputBucketChange,
|
||||||
|
onSaveableChange: this.onThroughputBucketsSaveableChange,
|
||||||
|
};
|
||||||
|
|
||||||
const partitionKeyComponentProps: PartitionKeyComponentProps = {
|
const partitionKeyComponentProps: PartitionKeyComponentProps = {
|
||||||
database: useDatabases.getState().findDatabaseWithId(this.collection.databaseId),
|
database: useDatabases.getState().findDatabaseWithId(this.collection.databaseId),
|
||||||
collection: this.collection,
|
collection: this.collection,
|
||||||
@@ -1271,6 +1328,13 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.throughputBucketsEnabled) {
|
||||||
|
tabs.push({
|
||||||
|
tab: SettingsV2TabTypes.ThroughputBucketsTab,
|
||||||
|
content: <ThroughputBucketsComponent {...throughputBucketsComponentProps} />,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const pivotProps: IPivotProps = {
|
const pivotProps: IPivotProps = {
|
||||||
onLinkClick: this.onPivotChange,
|
onLinkClick: this.onPivotChange,
|
||||||
selectedKey: SettingsV2TabTypes[this.state.selectedTab],
|
selectedKey: SettingsV2TabTypes[this.state.selectedTab],
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import * as ViewModels from "../../../../Contracts/ViewModels";
|
|||||||
|
|
||||||
import { handleError } from "Common/ErrorHandlingUtils";
|
import { handleError } from "Common/ErrorHandlingUtils";
|
||||||
import { cancelDataTransferJob, pollDataTransferJob } from "Common/dataAccess/dataTransfers";
|
import { cancelDataTransferJob, pollDataTransferJob } from "Common/dataAccess/dataTransfers";
|
||||||
|
import { Platform, configContext } from "ConfigContext";
|
||||||
import Explorer from "Explorer/Explorer";
|
import Explorer from "Explorer/Explorer";
|
||||||
import { ChangePartitionKeyPane } from "Explorer/Panes/ChangePartitionKeyPane/ChangePartitionKeyPane";
|
import { ChangePartitionKeyPane } from "Explorer/Panes/ChangePartitionKeyPane/ChangePartitionKeyPane";
|
||||||
import {
|
import {
|
||||||
@@ -177,12 +178,14 @@ export const PartitionKeyComponent: React.FC<PartitionKeyComponentProps> = ({ da
|
|||||||
To change the partition key, a new destination container must be created or an existing destination container
|
To change the partition key, a new destination container must be created or an existing destination container
|
||||||
selected. Data will then be copied to the destination container.
|
selected. Data will then be copied to the destination container.
|
||||||
</Text>
|
</Text>
|
||||||
<PrimaryButton
|
{configContext.platform !== Platform.Emulator && (
|
||||||
styles={{ root: { width: "fit-content" } }}
|
<PrimaryButton
|
||||||
text="Change"
|
styles={{ root: { width: "fit-content" } }}
|
||||||
onClick={startPartitionkeyChangeWorkflow}
|
text="Change"
|
||||||
disabled={isCurrentJobInProgress(portalDataTransferJob)}
|
onClick={startPartitionkeyChangeWorkflow}
|
||||||
/>
|
disabled={isCurrentJobInProgress(portalDataTransferJob)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{portalDataTransferJob && (
|
{portalDataTransferJob && (
|
||||||
<Stack>
|
<Stack>
|
||||||
<Text styles={textHeadingStyle}>{partitionKeyName} change job</Text>
|
<Text styles={textHeadingStyle}>{partitionKeyName} change job</Text>
|
||||||
|
|||||||
@@ -0,0 +1,177 @@
|
|||||||
|
import "@testing-library/jest-dom";
|
||||||
|
import { fireEvent, render, screen } from "@testing-library/react";
|
||||||
|
import React from "react";
|
||||||
|
import { ThroughputBucketsComponent } from "./ThroughputBucketsComponent";
|
||||||
|
|
||||||
|
describe("ThroughputBucketsComponent", () => {
|
||||||
|
const mockOnBucketsChange = jest.fn();
|
||||||
|
const mockOnSaveableChange = jest.fn();
|
||||||
|
|
||||||
|
const defaultProps = {
|
||||||
|
currentBuckets: [
|
||||||
|
{ id: 1, maxThroughputPercentage: 50 },
|
||||||
|
{ id: 2, maxThroughputPercentage: 60 },
|
||||||
|
],
|
||||||
|
throughputBucketsBaseline: [
|
||||||
|
{ id: 1, maxThroughputPercentage: 40 },
|
||||||
|
{ id: 2, maxThroughputPercentage: 50 },
|
||||||
|
],
|
||||||
|
onBucketsChange: mockOnBucketsChange,
|
||||||
|
onSaveableChange: mockOnSaveableChange,
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders the correct number of buckets", () => {
|
||||||
|
render(<ThroughputBucketsComponent {...defaultProps} />);
|
||||||
|
expect(screen.getAllByText(/Group \d+/)).toHaveLength(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders buckets in the correct order even if input is unordered", () => {
|
||||||
|
const unorderedBuckets = [
|
||||||
|
{ id: 2, maxThroughputPercentage: 60 },
|
||||||
|
{ id: 1, maxThroughputPercentage: 50 },
|
||||||
|
];
|
||||||
|
render(<ThroughputBucketsComponent {...defaultProps} currentBuckets={unorderedBuckets} />);
|
||||||
|
|
||||||
|
const bucketLabels = screen.getAllByText(/Group \d+/).map((el) => el.textContent);
|
||||||
|
expect(bucketLabels).toEqual(["Group 1 (Data Explorer Query Bucket)", "Group 2", "Group 3", "Group 4", "Group 5"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders all provided buckets even if they exceed the max default bucket count", () => {
|
||||||
|
const oversizedBuckets = [
|
||||||
|
{ id: 1, maxThroughputPercentage: 50 },
|
||||||
|
{ id: 2, maxThroughputPercentage: 60 },
|
||||||
|
{ id: 3, maxThroughputPercentage: 70 },
|
||||||
|
{ id: 4, maxThroughputPercentage: 80 },
|
||||||
|
{ id: 5, maxThroughputPercentage: 90 },
|
||||||
|
{ id: 6, maxThroughputPercentage: 100 },
|
||||||
|
{ id: 7, maxThroughputPercentage: 40 },
|
||||||
|
];
|
||||||
|
|
||||||
|
render(<ThroughputBucketsComponent {...defaultProps} currentBuckets={oversizedBuckets} />);
|
||||||
|
|
||||||
|
expect(screen.getAllByText(/Group \d+/)).toHaveLength(7);
|
||||||
|
|
||||||
|
expect(screen.getByDisplayValue("50")).toBeInTheDocument();
|
||||||
|
expect(screen.getByDisplayValue("60")).toBeInTheDocument();
|
||||||
|
expect(screen.getByDisplayValue("70")).toBeInTheDocument();
|
||||||
|
expect(screen.getByDisplayValue("80")).toBeInTheDocument();
|
||||||
|
expect(screen.getByDisplayValue("90")).toBeInTheDocument();
|
||||||
|
expect(screen.getByDisplayValue("100")).toBeInTheDocument();
|
||||||
|
expect(screen.getByDisplayValue("40")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("calls onBucketsChange when a bucket value changes", () => {
|
||||||
|
render(<ThroughputBucketsComponent {...defaultProps} />);
|
||||||
|
const input = screen.getByDisplayValue("50");
|
||||||
|
fireEvent.change(input, { target: { value: "70" } });
|
||||||
|
|
||||||
|
expect(mockOnBucketsChange).toHaveBeenCalledWith([
|
||||||
|
{ id: 1, maxThroughputPercentage: 70 },
|
||||||
|
{ id: 2, maxThroughputPercentage: 60 },
|
||||||
|
{ id: 3, maxThroughputPercentage: 100 },
|
||||||
|
{ id: 4, maxThroughputPercentage: 100 },
|
||||||
|
{ id: 5, maxThroughputPercentage: 100 },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("triggers onSaveableChange when values change", () => {
|
||||||
|
render(<ThroughputBucketsComponent {...defaultProps} />);
|
||||||
|
const input = screen.getByDisplayValue("50");
|
||||||
|
fireEvent.change(input, { target: { value: "80" } });
|
||||||
|
|
||||||
|
expect(mockOnSaveableChange).toHaveBeenCalledWith(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("updates state consistently after multiple changes to different buckets", () => {
|
||||||
|
render(<ThroughputBucketsComponent {...defaultProps} />);
|
||||||
|
|
||||||
|
const input1 = screen.getByDisplayValue("50");
|
||||||
|
fireEvent.change(input1, { target: { value: "70" } });
|
||||||
|
|
||||||
|
const input2 = screen.getByDisplayValue("60");
|
||||||
|
fireEvent.change(input2, { target: { value: "80" } });
|
||||||
|
|
||||||
|
expect(mockOnBucketsChange).toHaveBeenCalledWith([
|
||||||
|
{ id: 1, maxThroughputPercentage: 70 },
|
||||||
|
{ id: 2, maxThroughputPercentage: 80 },
|
||||||
|
{ id: 3, maxThroughputPercentage: 100 },
|
||||||
|
{ id: 4, maxThroughputPercentage: 100 },
|
||||||
|
{ id: 5, maxThroughputPercentage: 100 },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("resets to baseline when currentBuckets are reset", () => {
|
||||||
|
const { rerender } = render(<ThroughputBucketsComponent {...defaultProps} />);
|
||||||
|
const input1 = screen.getByDisplayValue("50");
|
||||||
|
fireEvent.change(input1, { target: { value: "70" } });
|
||||||
|
|
||||||
|
rerender(<ThroughputBucketsComponent {...defaultProps} currentBuckets={defaultProps.throughputBucketsBaseline} />);
|
||||||
|
|
||||||
|
expect(screen.getByDisplayValue("40")).toBeInTheDocument();
|
||||||
|
expect(screen.getByDisplayValue("50")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not call onBucketsChange when value remains unchanged", () => {
|
||||||
|
render(<ThroughputBucketsComponent {...defaultProps} />);
|
||||||
|
const input = screen.getByDisplayValue("50");
|
||||||
|
fireEvent.change(input, { target: { value: "50" } });
|
||||||
|
|
||||||
|
expect(mockOnBucketsChange).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("disables input and slider when maxThroughputPercentage is 100", () => {
|
||||||
|
render(
|
||||||
|
<ThroughputBucketsComponent
|
||||||
|
{...defaultProps}
|
||||||
|
currentBuckets={[
|
||||||
|
{ id: 1, maxThroughputPercentage: 100 },
|
||||||
|
{ id: 2, maxThroughputPercentage: 50 },
|
||||||
|
]}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const disabledInputs = screen.getAllByDisplayValue("100");
|
||||||
|
expect(disabledInputs.length).toBeGreaterThan(0);
|
||||||
|
expect(disabledInputs[0]).toBeDisabled();
|
||||||
|
|
||||||
|
const sliders = screen.getAllByRole("slider");
|
||||||
|
expect(sliders.length).toBeGreaterThan(0);
|
||||||
|
expect(sliders[0]).toHaveAttribute("aria-disabled", "true");
|
||||||
|
expect(sliders[1]).toHaveAttribute("aria-disabled", "false");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("toggles bucket value between 50 and 100 with switch", () => {
|
||||||
|
render(<ThroughputBucketsComponent {...defaultProps} />);
|
||||||
|
const toggles = screen.getAllByRole("switch");
|
||||||
|
|
||||||
|
fireEvent.click(toggles[0]);
|
||||||
|
|
||||||
|
expect(mockOnBucketsChange).toHaveBeenCalledWith([
|
||||||
|
{ id: 1, maxThroughputPercentage: 100 },
|
||||||
|
{ id: 2, maxThroughputPercentage: 60 },
|
||||||
|
{ id: 3, maxThroughputPercentage: 100 },
|
||||||
|
{ id: 4, maxThroughputPercentage: 100 },
|
||||||
|
{ id: 5, maxThroughputPercentage: 100 },
|
||||||
|
]);
|
||||||
|
|
||||||
|
fireEvent.click(toggles[0]);
|
||||||
|
|
||||||
|
expect(mockOnBucketsChange).toHaveBeenCalledWith([
|
||||||
|
{ id: 1, maxThroughputPercentage: 50 },
|
||||||
|
{ id: 2, maxThroughputPercentage: 60 },
|
||||||
|
{ id: 3, maxThroughputPercentage: 100 },
|
||||||
|
{ id: 4, maxThroughputPercentage: 100 },
|
||||||
|
{ id: 5, maxThroughputPercentage: 100 },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("ensures default buckets are used when no buckets are provided", () => {
|
||||||
|
render(<ThroughputBucketsComponent {...defaultProps} currentBuckets={[]} />);
|
||||||
|
expect(screen.getAllByText(/Group \d+/)).toHaveLength(5);
|
||||||
|
expect(screen.getAllByDisplayValue("100")).toHaveLength(5);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
import { Label, Slider, Stack, TextField, Toggle } from "@fluentui/react";
|
||||||
|
import { ThroughputBucket } from "Contracts/DataModels";
|
||||||
|
import React, { FC, useEffect, useState } from "react";
|
||||||
|
import { isDirty } from "../../SettingsUtils";
|
||||||
|
|
||||||
|
const MAX_BUCKET_SIZES = 5;
|
||||||
|
|
||||||
|
const DEFAULT_BUCKETS = Array.from({ length: MAX_BUCKET_SIZES }, (_, i) => ({
|
||||||
|
id: i + 1,
|
||||||
|
maxThroughputPercentage: 100,
|
||||||
|
}));
|
||||||
|
|
||||||
|
export interface ThroughputBucketsComponentProps {
|
||||||
|
currentBuckets: ThroughputBucket[];
|
||||||
|
throughputBucketsBaseline: ThroughputBucket[];
|
||||||
|
onBucketsChange: (updatedBuckets: ThroughputBucket[]) => void;
|
||||||
|
onSaveableChange: (isSaveable: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ThroughputBucketsComponent: FC<ThroughputBucketsComponentProps> = ({
|
||||||
|
currentBuckets,
|
||||||
|
throughputBucketsBaseline,
|
||||||
|
onBucketsChange,
|
||||||
|
onSaveableChange,
|
||||||
|
}) => {
|
||||||
|
const getThroughputBuckets = (buckets: ThroughputBucket[]): ThroughputBucket[] => {
|
||||||
|
if (!buckets || buckets.length === 0) {
|
||||||
|
return DEFAULT_BUCKETS;
|
||||||
|
}
|
||||||
|
const maxBuckets = Math.max(DEFAULT_BUCKETS.length, buckets.length);
|
||||||
|
const adjustedDefaultBuckets = Array.from({ length: maxBuckets }, (_, i) => ({
|
||||||
|
id: i + 1,
|
||||||
|
maxThroughputPercentage: 100,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return adjustedDefaultBuckets.map(
|
||||||
|
(defaultBucket) => buckets?.find((bucket) => bucket.id === defaultBucket.id) || defaultBucket,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const [throughputBuckets, setThroughputBuckets] = useState<ThroughputBucket[]>(getThroughputBuckets(currentBuckets));
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setThroughputBuckets(getThroughputBuckets(currentBuckets));
|
||||||
|
onSaveableChange(false);
|
||||||
|
}, [currentBuckets]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const isChanged = isDirty(throughputBuckets, getThroughputBuckets(throughputBucketsBaseline));
|
||||||
|
onSaveableChange(isChanged);
|
||||||
|
}, [throughputBuckets]);
|
||||||
|
|
||||||
|
const handleBucketChange = (id: number, newValue: number) => {
|
||||||
|
const updatedBuckets = throughputBuckets.map((bucket) =>
|
||||||
|
bucket.id === id ? { ...bucket, maxThroughputPercentage: newValue } : bucket,
|
||||||
|
);
|
||||||
|
setThroughputBuckets(updatedBuckets);
|
||||||
|
const settingsChanged = isDirty(updatedBuckets, throughputBuckets);
|
||||||
|
settingsChanged && onBucketsChange(updatedBuckets);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onToggle = (id: number, checked: boolean) => {
|
||||||
|
handleBucketChange(id, checked ? 50 : 100);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack tokens={{ childrenGap: "m" }} styles={{ root: { width: "70%", maxWidth: 700 } }}>
|
||||||
|
<Label>Throughput Buckets</Label>
|
||||||
|
<Stack>
|
||||||
|
{throughputBuckets?.map((bucket) => (
|
||||||
|
<Stack key={bucket.id} horizontal tokens={{ childrenGap: 8 }} verticalAlign="center">
|
||||||
|
<Slider
|
||||||
|
min={1}
|
||||||
|
max={100}
|
||||||
|
step={1}
|
||||||
|
value={bucket.maxThroughputPercentage}
|
||||||
|
onChange={(newValue) => handleBucketChange(bucket.id, newValue)}
|
||||||
|
showValue={false}
|
||||||
|
label={`Group ${bucket.id}${bucket.id === 1 ? " (Data Explorer Query Bucket)" : ""}`}
|
||||||
|
styles={{ root: { flex: 2, maxWidth: 400 } }}
|
||||||
|
disabled={bucket.maxThroughputPercentage === 100}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
value={bucket.maxThroughputPercentage.toString()}
|
||||||
|
onChange={(event, newValue) => handleBucketChange(bucket.id, parseInt(newValue || "0", 10))}
|
||||||
|
type="number"
|
||||||
|
suffix="%"
|
||||||
|
styles={{
|
||||||
|
fieldGroup: { width: 80 },
|
||||||
|
}}
|
||||||
|
disabled={bucket.maxThroughputPercentage === 100}
|
||||||
|
/>
|
||||||
|
<Toggle
|
||||||
|
onText="Active"
|
||||||
|
offText="Inactive"
|
||||||
|
checked={bucket.maxThroughputPercentage !== 100}
|
||||||
|
onChange={(event, checked) => onToggle(bucket.id, checked)}
|
||||||
|
styles={{ root: { marginBottom: 0 }, text: { fontSize: 12 } }}
|
||||||
|
></Toggle>
|
||||||
|
</Stack>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -17,14 +17,13 @@ import {
|
|||||||
} from "@fluentui/react";
|
} from "@fluentui/react";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import * as DataModels from "../../../../../Contracts/DataModels";
|
import * as DataModels from "../../../../../Contracts/DataModels";
|
||||||
import { SubscriptionType } from "../../../../../Contracts/SubscriptionType";
|
|
||||||
import * as SharedConstants from "../../../../../Shared/Constants";
|
import * as SharedConstants from "../../../../../Shared/Constants";
|
||||||
import { Action, ActionModifiers } from "../../../../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../../../../Shared/Telemetry/TelemetryConstants";
|
||||||
import * as TelemetryProcessor from "../../../../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { userContext } from "../../../../../UserContext";
|
import { userContext } from "../../../../../UserContext";
|
||||||
import * as AutoPilotUtils from "../../../../../Utils/AutoPilotUtils";
|
import * as AutoPilotUtils from "../../../../../Utils/AutoPilotUtils";
|
||||||
import { autoPilotThroughput1K } from "../../../../../Utils/AutoPilotUtils";
|
import { autoPilotThroughput1K } from "../../../../../Utils/AutoPilotUtils";
|
||||||
import { calculateEstimateNumber, usageInGB } from "../../../../../Utils/PricingUtils";
|
import { calculateEstimateNumber } from "../../../../../Utils/PricingUtils";
|
||||||
import { Int32 } from "../../../../Panes/Tables/Validators/EntityPropertyValidationCommon";
|
import { Int32 } from "../../../../Panes/Tables/Validators/EntityPropertyValidationCommon";
|
||||||
import {
|
import {
|
||||||
PriceBreakdown,
|
PriceBreakdown,
|
||||||
@@ -366,29 +365,6 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
private minRUperGBSurvey = (): JSX.Element => {
|
|
||||||
const href = `https://ncv.microsoft.com/vRBTO37jmO?ctx={"AzureSubscriptionId":"${userContext.subscriptionId}","CosmosDBAccountName":"${userContext.databaseAccount?.name}"}`;
|
|
||||||
const oneTBinKB = 1000000000;
|
|
||||||
const minRUperGB = 10;
|
|
||||||
const featureFlagEnabled = userContext.features.showMinRUSurvey;
|
|
||||||
const collectionIsEligible =
|
|
||||||
userContext.subscriptionType !== SubscriptionType.Internal &&
|
|
||||||
this.props.usageSizeInKB > oneTBinKB &&
|
|
||||||
this.props.minimum >= usageInGB(this.props.usageSizeInKB) * minRUperGB;
|
|
||||||
if (featureFlagEnabled || collectionIsEligible) {
|
|
||||||
return (
|
|
||||||
<Text>
|
|
||||||
Need to scale below {this.props.minimum} RU/s? Reach out by filling{" "}
|
|
||||||
<a target="_blank" rel="noreferrer" href={href}>
|
|
||||||
this questionnaire
|
|
||||||
</a>
|
|
||||||
.
|
|
||||||
</Text>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
private renderThroughputModeChoices = (): JSX.Element => {
|
private renderThroughputModeChoices = (): JSX.Element => {
|
||||||
const labelId = "settingsV2RadioButtonLabelId";
|
const labelId = "settingsV2RadioButtonLabelId";
|
||||||
return (
|
return (
|
||||||
@@ -661,7 +637,6 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
|
|||||||
</Link>
|
</Link>
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
{this.minRUperGBSurvey()}
|
|
||||||
{this.props.spendAckVisible && (
|
{this.props.spendAckVisible && (
|
||||||
<Checkbox
|
<Checkbox
|
||||||
id="spendAckCheckBox"
|
id="spendAckCheckBox"
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ export type isDirtyTypes =
|
|||||||
| DataModels.IndexingPolicy
|
| DataModels.IndexingPolicy
|
||||||
| DataModels.ComputedProperties
|
| DataModels.ComputedProperties
|
||||||
| DataModels.VectorEmbedding[]
|
| DataModels.VectorEmbedding[]
|
||||||
| DataModels.FullTextPolicy;
|
| DataModels.FullTextPolicy
|
||||||
|
| DataModels.ThroughputBucket[];
|
||||||
export const TtlOff = "off";
|
export const TtlOff = "off";
|
||||||
export const TtlOn = "on";
|
export const TtlOn = "on";
|
||||||
export const TtlOnNoDefault = "on-nodefault";
|
export const TtlOnNoDefault = "on-nodefault";
|
||||||
@@ -55,6 +56,7 @@ export enum SettingsV2TabTypes {
|
|||||||
PartitionKeyTab,
|
PartitionKeyTab,
|
||||||
ComputedPropertiesTab,
|
ComputedPropertiesTab,
|
||||||
ContainerVectorPolicyTab,
|
ContainerVectorPolicyTab,
|
||||||
|
ThroughputBucketsTab,
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ContainerPolicyTabTypes {
|
export enum ContainerPolicyTabTypes {
|
||||||
@@ -167,6 +169,8 @@ export const getTabTitle = (tab: SettingsV2TabTypes): string => {
|
|||||||
return "Computed Properties";
|
return "Computed Properties";
|
||||||
case SettingsV2TabTypes.ContainerVectorPolicyTab:
|
case SettingsV2TabTypes.ContainerVectorPolicyTab:
|
||||||
return "Container Policies";
|
return "Container Policies";
|
||||||
|
case SettingsV2TabTypes.ThroughputBucketsTab:
|
||||||
|
return "Throughput Buckets";
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unknown tab ${tab}`);
|
throw new Error(`Unknown tab ${tab}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Checkbox, DirectionalHint, Link, Stack, Text, TextField, TooltipHost } from "@fluentui/react";
|
import { Checkbox, DirectionalHint, Link, Stack, Text, TextField, TooltipHost } from "@fluentui/react";
|
||||||
|
import { getWorkloadType } from "Common/DatabaseAccountUtility";
|
||||||
import { useDatabases } from "Explorer/useDatabases";
|
import { useDatabases } from "Explorer/useDatabases";
|
||||||
import React, { FunctionComponent, useEffect, useState } from "react";
|
import React, { FunctionComponent, useEffect, useState } from "react";
|
||||||
import * as Constants from "../../../Common/Constants";
|
import * as Constants from "../../../Common/Constants";
|
||||||
@@ -34,10 +35,23 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
|||||||
setIsThroughputCapExceeded,
|
setIsThroughputCapExceeded,
|
||||||
onCostAcknowledgeChange,
|
onCostAcknowledgeChange,
|
||||||
}: ThroughputInputProps) => {
|
}: ThroughputInputProps) => {
|
||||||
|
let defaultThroughput: number;
|
||||||
|
const workloadType: Constants.WorkloadType = getWorkloadType();
|
||||||
|
|
||||||
|
if (
|
||||||
|
isFreeTier ||
|
||||||
|
isQuickstart ||
|
||||||
|
[Constants.WorkloadType.Learning, Constants.WorkloadType.DevelopmentTesting].includes(workloadType)
|
||||||
|
) {
|
||||||
|
defaultThroughput = AutoPilotUtils.autoPilotThroughput1K;
|
||||||
|
} else if (workloadType === Constants.WorkloadType.Production) {
|
||||||
|
defaultThroughput = AutoPilotUtils.autoPilotThroughput10K;
|
||||||
|
} else {
|
||||||
|
defaultThroughput = AutoPilotUtils.autoPilotThroughput4K;
|
||||||
|
}
|
||||||
|
|
||||||
const [isAutoscaleSelected, setIsAutoScaleSelected] = useState<boolean>(true);
|
const [isAutoscaleSelected, setIsAutoScaleSelected] = useState<boolean>(true);
|
||||||
const [throughput, setThroughput] = useState<number>(
|
const [throughput, setThroughput] = useState<number>(defaultThroughput);
|
||||||
isFreeTier || isQuickstart ? AutoPilotUtils.autoPilotThroughput1K : AutoPilotUtils.autoPilotThroughput4K,
|
|
||||||
);
|
|
||||||
const [isCostAcknowledged, setIsCostAcknowledged] = useState<boolean>(false);
|
const [isCostAcknowledged, setIsCostAcknowledged] = useState<boolean>(false);
|
||||||
const [throughputError, setThroughputError] = useState<string>("");
|
const [throughputError, setThroughputError] = useState<string>("");
|
||||||
const [totalThroughputUsed, setTotalThroughputUsed] = useState<number>(0);
|
const [totalThroughputUsed, setTotalThroughputUsed] = useState<number>(0);
|
||||||
@@ -47,7 +61,6 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
|||||||
|
|
||||||
const throughputCap = userContext.databaseAccount?.properties.capacity?.totalThroughputLimit;
|
const throughputCap = userContext.databaseAccount?.properties.capacity?.totalThroughputLimit;
|
||||||
const numberOfRegions = userContext.databaseAccount?.properties.locations?.length || 1;
|
const numberOfRegions = userContext.databaseAccount?.properties.locations?.length || 1;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// throughput cap check for the initial state
|
// throughput cap check for the initial state
|
||||||
let totalThroughput = 0;
|
let totalThroughput = 0;
|
||||||
@@ -157,9 +170,6 @@ export const ThroughputInput: FunctionComponent<ThroughputInputProps> = ({
|
|||||||
|
|
||||||
const handleOnChangeMode = (event: React.ChangeEvent<HTMLInputElement>, mode: string): void => {
|
const handleOnChangeMode = (event: React.ChangeEvent<HTMLInputElement>, mode: string): void => {
|
||||||
if (mode === "Autoscale") {
|
if (mode === "Autoscale") {
|
||||||
const defaultThroughput = isFreeTier
|
|
||||||
? AutoPilotUtils.autoPilotThroughput1K
|
|
||||||
: AutoPilotUtils.autoPilotThroughput4K;
|
|
||||||
setThroughput(defaultThroughput);
|
setThroughput(defaultThroughput);
|
||||||
setIsAutoScaleSelected(true);
|
setIsAutoScaleSelected(true);
|
||||||
setThroughputValue(defaultThroughput);
|
setThroughputValue(defaultThroughput);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { MessageTypes } from "Contracts/ExplorerContracts";
|
|||||||
import { useDataPlaneRbac } from "Explorer/Panes/SettingsPane/SettingsPane";
|
import { useDataPlaneRbac } from "Explorer/Panes/SettingsPane/SettingsPane";
|
||||||
import { getCopilotEnabled, isCopilotFeatureRegistered } from "Explorer/QueryCopilot/Shared/QueryCopilotClient";
|
import { getCopilotEnabled, isCopilotFeatureRegistered } from "Explorer/QueryCopilot/Shared/QueryCopilotClient";
|
||||||
import { IGalleryItem } from "Juno/JunoClient";
|
import { IGalleryItem } from "Juno/JunoClient";
|
||||||
import { scheduleRefreshDatabaseResourceToken } from "Platform/Fabric/FabricUtil";
|
import { isFabricMirrored, isFabricMirroredKey, scheduleRefreshFabricToken } from "Platform/Fabric/FabricUtil";
|
||||||
import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
|
import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
|
||||||
import { acquireMsalTokenForAccount } from "Utils/AuthorizationUtils";
|
import { acquireMsalTokenForAccount } from "Utils/AuthorizationUtils";
|
||||||
import { allowedNotebookServerUrls, validateEndpoint } from "Utils/EndpointUtils";
|
import { allowedNotebookServerUrls, validateEndpoint } from "Utils/EndpointUtils";
|
||||||
@@ -35,7 +35,7 @@ import { PhoenixClient } from "../Phoenix/PhoenixClient";
|
|||||||
import * as ExplorerSettings from "../Shared/ExplorerSettings";
|
import * as ExplorerSettings from "../Shared/ExplorerSettings";
|
||||||
import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants";
|
||||||
import * as TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { isAccountNewerThanThresholdInMs, updateUserContext, userContext } from "../UserContext";
|
import { updateUserContext, userContext } from "../UserContext";
|
||||||
import { getCollectionName, getUploadName } from "../Utils/APITypeUtils";
|
import { getCollectionName, getUploadName } from "../Utils/APITypeUtils";
|
||||||
import { stringToBlob } from "../Utils/BlobUtils";
|
import { stringToBlob } from "../Utils/BlobUtils";
|
||||||
import { isCapabilityEnabled } from "../Utils/CapabilityUtils";
|
import { isCapabilityEnabled } from "../Utils/CapabilityUtils";
|
||||||
@@ -43,7 +43,7 @@ import { fromContentUri, toRawContentUri } from "../Utils/GitHubUtils";
|
|||||||
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
|
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
|
||||||
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../Utils/NotificationConsoleUtils";
|
import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../Utils/NotificationConsoleUtils";
|
||||||
import { useSidePanel } from "../hooks/useSidePanel";
|
import { useSidePanel } from "../hooks/useSidePanel";
|
||||||
import { useTabs } from "../hooks/useTabs";
|
import { ReactTabKind, useTabs } from "../hooks/useTabs";
|
||||||
import "./ComponentRegisterer";
|
import "./ComponentRegisterer";
|
||||||
import { DialogProps, useDialog } from "./Controls/Dialog";
|
import { DialogProps, useDialog } from "./Controls/Dialog";
|
||||||
import { GalleryTab as GalleryTabKind } from "./Controls/NotebookGallery/GalleryViewerComponent";
|
import { GalleryTab as GalleryTabKind } from "./Controls/NotebookGallery/GalleryViewerComponent";
|
||||||
@@ -187,6 +187,10 @@ export default class Explorer {
|
|||||||
useNotebook.getState().setNotebookBasePath(userContext.features.notebookBasePath);
|
useNotebook.getState().setNotebookBasePath(userContext.features.notebookBasePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isFabricMirrored()) {
|
||||||
|
useTabs.getState().closeReactTab(ReactTabKind.Home);
|
||||||
|
}
|
||||||
|
|
||||||
this.refreshExplorer();
|
this.refreshExplorer();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -278,37 +282,6 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public openNPSSurveyDialog(): void {
|
|
||||||
if (!Platform.Portal || !["Postgres", "SQL", "Mongo"].includes(userContext.apiType)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ONE_DAY_IN_MS = 86400000;
|
|
||||||
const SEVEN_DAYS_IN_MS = 604800000;
|
|
||||||
|
|
||||||
// Try Cosmos DB subscription - survey shown to 100% of users at day 1 in Data Explorer.
|
|
||||||
if (userContext.isTryCosmosDBSubscription) {
|
|
||||||
if (isAccountNewerThanThresholdInMs(userContext.databaseAccount?.systemData?.createdAt || "", ONE_DAY_IN_MS)) {
|
|
||||||
Logger.logInfo(
|
|
||||||
`Sending message to Portal to check if NPS Survey can be displayed in Try Cosmos DB ${userContext.apiType}`,
|
|
||||||
"Explorer/openNPSSurveyDialog",
|
|
||||||
);
|
|
||||||
sendMessage({ type: MessageTypes.DisplayNPSSurvey });
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Show survey when an existing account is older than 7 days
|
|
||||||
if (
|
|
||||||
!isAccountNewerThanThresholdInMs(userContext.databaseAccount?.systemData?.createdAt || "", SEVEN_DAYS_IN_MS)
|
|
||||||
) {
|
|
||||||
Logger.logInfo(
|
|
||||||
`Sending message to Portal to check if NPS Survey can be displayed for existing ${userContext.apiType} account older than 7 days`,
|
|
||||||
"Explorer/openNPSSurveyDialog",
|
|
||||||
);
|
|
||||||
sendMessage({ type: MessageTypes.DisplayNPSSurvey });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async openCESCVAFeedbackBlade(): Promise<void> {
|
public async openCESCVAFeedbackBlade(): Promise<void> {
|
||||||
sendMessage({ type: MessageTypes.OpenCESCVAFeedbackBlade });
|
sendMessage({ type: MessageTypes.OpenCESCVAFeedbackBlade });
|
||||||
Logger.logInfo(
|
Logger.logInfo(
|
||||||
@@ -378,8 +351,8 @@ export default class Explorer {
|
|||||||
};
|
};
|
||||||
|
|
||||||
public onRefreshResourcesClick = async (): Promise<void> => {
|
public onRefreshResourcesClick = async (): Promise<void> => {
|
||||||
if (configContext.platform === Platform.Fabric) {
|
if (isFabricMirroredKey()) {
|
||||||
scheduleRefreshDatabaseResourceToken(true).then(() => this.refreshAllDatabases());
|
scheduleRefreshFabricToken(true).then(() => this.refreshAllDatabases());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1158,7 +1131,7 @@ export default class Explorer {
|
|||||||
await this.initNotebooks(userContext.databaseAccount);
|
await this.initNotebooks(userContext.databaseAccount);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.refreshSampleData();
|
this.refreshSampleData();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async configureCopilot(): Promise<void> {
|
public async configureCopilot(): Promise<void> {
|
||||||
@@ -1183,26 +1156,27 @@ export default class Explorer {
|
|||||||
.setCopilotSampleDBEnabled(copilotEnabled && copilotUserDBEnabled && copilotSampleDBEnabled);
|
.setCopilotSampleDBEnabled(copilotEnabled && copilotUserDBEnabled && copilotSampleDBEnabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async refreshSampleData(): Promise<void> {
|
public refreshSampleData(): void {
|
||||||
try {
|
if (!userContext.sampleDataConnectionInfo) {
|
||||||
if (!userContext.sampleDataConnectionInfo) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const collection: DataModels.Collection = await readSampleCollection();
|
|
||||||
if (!collection) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const databaseId = userContext.sampleDataConnectionInfo?.databaseId;
|
|
||||||
if (!databaseId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const sampleDataResourceTokenCollection = new ResourceTokenCollection(this, databaseId, collection, true);
|
|
||||||
useDatabases.setState({ sampleDataResourceTokenCollection });
|
|
||||||
} catch (error) {
|
|
||||||
Logger.logError(getErrorMessage(error), "Explorer");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const databaseId = userContext.sampleDataConnectionInfo?.databaseId;
|
||||||
|
if (!databaseId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
readSampleCollection()
|
||||||
|
.then((collection: DataModels.Collection) => {
|
||||||
|
if (!collection) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sampleDataResourceTokenCollection = new ResourceTokenCollection(this, databaseId, collection, true);
|
||||||
|
useDatabases.setState({ sampleDataResourceTokenCollection });
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
Logger.logError(getErrorMessage(error), "Explorer/refreshSampleData");
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,10 +14,6 @@
|
|||||||
.flex-direction(@direction: row);
|
.flex-direction(@direction: row);
|
||||||
padding: 4px 5px;
|
padding: 4px 5px;
|
||||||
|
|
||||||
label {
|
|
||||||
padding: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.valueCol {
|
.valueCol {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
padding-right: 5px;
|
padding-right: 5px;
|
||||||
@@ -63,6 +59,10 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.customTrashIcon {
|
||||||
|
padding-top: 33px;
|
||||||
|
}
|
||||||
|
|
||||||
.rightPaneTrashIconImg {
|
.rightPaneTrashIconImg {
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -142,10 +142,11 @@ export const NewVertexComponent: FunctionComponent<INewVertexComponentProps> = (
|
|||||||
<div className="labelCol">
|
<div className="labelCol">
|
||||||
<TextField
|
<TextField
|
||||||
className="edgeInput"
|
className="edgeInput"
|
||||||
|
label={index === 0 && "Key"}
|
||||||
type="text"
|
type="text"
|
||||||
id="propertyKeyNewVertexPane"
|
id="propertyKeyNewVertexPane"
|
||||||
componentRef={input}
|
componentRef={input}
|
||||||
aria-required="true"
|
required
|
||||||
placeholder="Key"
|
placeholder="Key"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
aria-label={`Enter value for propery ${index + 1}`}
|
aria-label={`Enter value for propery ${index + 1}`}
|
||||||
@@ -153,11 +154,11 @@ export const NewVertexComponent: FunctionComponent<INewVertexComponentProps> = (
|
|||||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) => onKeyChange(event, index)}
|
onChange={(event: React.ChangeEvent<HTMLInputElement>) => onKeyChange(event, index)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span className="mandatoryStar">* </span>
|
|
||||||
|
|
||||||
<div className="valueCol">
|
<div className="valueCol">
|
||||||
<TextField
|
<TextField
|
||||||
className="edgeInput"
|
className="edgeInput"
|
||||||
|
label={index === 0 && "Value"}
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Value"
|
placeholder="Value"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
@@ -169,6 +170,8 @@ export const NewVertexComponent: FunctionComponent<INewVertexComponentProps> = (
|
|||||||
<div>
|
<div>
|
||||||
<Dropdown
|
<Dropdown
|
||||||
role="combobox"
|
role="combobox"
|
||||||
|
label={index === 0 && "Type"}
|
||||||
|
ariaLabel="Type"
|
||||||
placeholder="Select an option"
|
placeholder="Select an option"
|
||||||
defaultSelectedKey={data.values[0].type}
|
defaultSelectedKey={data.values[0].type}
|
||||||
style={{ width: 100 }}
|
style={{ width: 100 }}
|
||||||
@@ -181,7 +184,7 @@ export const NewVertexComponent: FunctionComponent<INewVertexComponentProps> = (
|
|||||||
</div>
|
</div>
|
||||||
<div className="actionCol">
|
<div className="actionCol">
|
||||||
<div
|
<div
|
||||||
className="rightPaneTrashIcon rightPaneBtns"
|
className={`rightPaneTrashIcon rightPaneBtns ${index === 0 && "customTrashIcon"}`}
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
role="button"
|
role="button"
|
||||||
aria-label={`Delete ${data.key}`}
|
aria-label={`Delete ${data.key}`}
|
||||||
|
|||||||
@@ -6,12 +6,12 @@
|
|||||||
import { CommandBar as FluentCommandBar, ICommandBarItemProps } from "@fluentui/react";
|
import { CommandBar as FluentCommandBar, ICommandBarItemProps } from "@fluentui/react";
|
||||||
import { useNotebook } from "Explorer/Notebook/useNotebook";
|
import { useNotebook } from "Explorer/Notebook/useNotebook";
|
||||||
import { KeyboardActionGroup, useKeyboardActionGroup } from "KeyboardShortcuts";
|
import { KeyboardActionGroup, useKeyboardActionGroup } from "KeyboardShortcuts";
|
||||||
|
import { isFabric } from "Platform/Fabric/FabricUtil";
|
||||||
import { userContext } from "UserContext";
|
import { userContext } from "UserContext";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import create, { UseStore } from "zustand";
|
import create, { UseStore } from "zustand";
|
||||||
import { ConnectionStatusType, PoolIdType } from "../../../Common/Constants";
|
import { ConnectionStatusType, PoolIdType } from "../../../Common/Constants";
|
||||||
import { StyleConstants } from "../../../Common/StyleConstants";
|
import { StyleConstants } from "../../../Common/StyleConstants";
|
||||||
import { Platform, configContext } from "../../../ConfigContext";
|
|
||||||
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
import { useSelectedNode } from "../../useSelectedNode";
|
import { useSelectedNode } from "../../useSelectedNode";
|
||||||
@@ -93,19 +93,18 @@ export const CommandBar: React.FC<Props> = ({ container }: Props) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const rootStyle =
|
const rootStyle = isFabric()
|
||||||
configContext.platform === Platform.Fabric
|
? {
|
||||||
? {
|
root: {
|
||||||
root: {
|
backgroundColor: "transparent",
|
||||||
backgroundColor: "transparent",
|
padding: "2px 8px 0px 8px",
|
||||||
padding: "2px 8px 0px 8px",
|
},
|
||||||
},
|
}
|
||||||
}
|
: {
|
||||||
: {
|
root: {
|
||||||
root: {
|
backgroundColor: backgroundColor,
|
||||||
backgroundColor: backgroundColor,
|
},
|
||||||
},
|
};
|
||||||
};
|
|
||||||
|
|
||||||
const allButtons = staticButtons.concat(contextButtons).concat(controlButtons);
|
const allButtons = staticButtons.concat(contextButtons).concat(controlButtons);
|
||||||
const keyboardHandlers = CommandBarUtil.createKeyboardHandlers(allButtons);
|
const keyboardHandlers = CommandBarUtil.createKeyboardHandlers(allButtons);
|
||||||
|
|||||||
@@ -37,21 +37,25 @@ describe("CommandBarComponentButtonFactory tests", () => {
|
|||||||
expect(enableAzureSynapseLinkBtn).toBeDefined();
|
expect(enableAzureSynapseLinkBtn).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Button should not be visible for Tables API", () => {
|
// TODO: Now that Tables API supports dataplane RBAC, calling createStaticCommandBarButtons will enable the
|
||||||
updateUserContext({
|
// Entra ID Login button, which causes this test to fail due to "Invalid hook call.". This seems to be
|
||||||
databaseAccount: {
|
// unsupported in jest and needs to be tested with react-hooks-testing-library.
|
||||||
properties: {
|
//
|
||||||
capabilities: [{ name: "EnableTable" }],
|
// it("Button should not be visible for Tables API", () => {
|
||||||
},
|
// updateUserContext({
|
||||||
} as DatabaseAccount,
|
// databaseAccount: {
|
||||||
});
|
// properties: {
|
||||||
|
// capabilities: [{ name: "EnableTable" }],
|
||||||
const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
// },
|
||||||
const enableAzureSynapseLinkBtn = buttons.find(
|
// } as DatabaseAccount,
|
||||||
(button) => button.commandButtonLabel === enableAzureSynapseLinkBtnLabel,
|
// });
|
||||||
);
|
//
|
||||||
expect(enableAzureSynapseLinkBtn).toBeUndefined();
|
// const buttons = CommandBarComponentButtonFactory.createStaticCommandBarButtons(mockExplorer, selectedNodeState);
|
||||||
});
|
// const enableAzureSynapseLinkBtn = buttons.find(
|
||||||
|
// (button) => button.commandButtonLabel === enableAzureSynapseLinkBtnLabel,
|
||||||
|
// );
|
||||||
|
// expect(enableAzureSynapseLinkBtn).toBeUndefined();
|
||||||
|
//});
|
||||||
|
|
||||||
it("Button should not be visible for Cassandra API", () => {
|
it("Button should not be visible for Cassandra API", () => {
|
||||||
updateUserContext({
|
updateUserContext({
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { KeyboardAction } from "KeyboardShortcuts";
|
import { KeyboardAction } from "KeyboardShortcuts";
|
||||||
|
import { isDataplaneRbacSupported } from "Utils/APITypeUtils";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import AddSqlQueryIcon from "../../../../images/AddSqlQuery_16x16.svg";
|
import AddSqlQueryIcon from "../../../../images/AddSqlQuery_16x16.svg";
|
||||||
@@ -61,7 +62,7 @@ export function createStaticCommandBarButtons(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userContext.apiType === "SQL") {
|
if (isDataplaneRbacSupported(userContext.apiType)) {
|
||||||
const [loginButtonProps, setLoginButtonProps] = useState<CommandButtonComponentProps | undefined>(undefined);
|
const [loginButtonProps, setLoginButtonProps] = useState<CommandButtonComponentProps | undefined>(undefined);
|
||||||
const dataPlaneRbacEnabled = useDataPlaneRbac((state) => state.dataPlaneRbacEnabled);
|
const dataPlaneRbacEnabled = useDataPlaneRbac((state) => state.dataPlaneRbacEnabled);
|
||||||
const aadTokenUpdated = useDataPlaneRbac((state) => state.aadTokenUpdated);
|
const aadTokenUpdated = useDataPlaneRbac((state) => state.aadTokenUpdated);
|
||||||
|
|||||||
@@ -37,6 +37,10 @@
|
|||||||
background-color:@NotificationHigh;
|
background-color:@NotificationHigh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
.focusedBorder();
|
||||||
|
}
|
||||||
|
|
||||||
.statusBar {
|
.statusBar {
|
||||||
.dataTypeIcons {
|
.dataTypeIcons {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|||||||
@@ -81,10 +81,6 @@ export class NotificationConsoleComponent extends React.Component<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public setElememntRef = (element: HTMLElement): void => {
|
|
||||||
this.consoleHeaderElement = element;
|
|
||||||
};
|
|
||||||
|
|
||||||
public render(): JSX.Element {
|
public render(): JSX.Element {
|
||||||
const numInProgress = this.state.allConsoleData.filter(
|
const numInProgress = this.state.allConsoleData.filter(
|
||||||
(data: ConsoleData) => data.type === ConsoleDataType.InProgress,
|
(data: ConsoleData) => data.type === ConsoleDataType.InProgress,
|
||||||
@@ -101,7 +97,9 @@ export class NotificationConsoleComponent extends React.Component<
|
|||||||
<div
|
<div
|
||||||
className="notificationConsoleHeader"
|
className="notificationConsoleHeader"
|
||||||
id="notificationConsoleHeader"
|
id="notificationConsoleHeader"
|
||||||
ref={this.setElememntRef}
|
role="button"
|
||||||
|
aria-label="Console"
|
||||||
|
aria-expanded={this.props.isConsoleExpanded}
|
||||||
onClick={() => this.expandCollapseConsole()}
|
onClick={() => this.expandCollapseConsole()}
|
||||||
onKeyDown={(event: React.KeyboardEvent<HTMLDivElement>) => this.onExpandCollapseKeyPress(event)}
|
onKeyDown={(event: React.KeyboardEvent<HTMLDivElement>) => this.onExpandCollapseKeyPress(event)}
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
@@ -109,15 +107,15 @@ export class NotificationConsoleComponent extends React.Component<
|
|||||||
<div className="statusBar">
|
<div className="statusBar">
|
||||||
<span className="dataTypeIcons">
|
<span className="dataTypeIcons">
|
||||||
<span className="notificationConsoleHeaderIconWithData">
|
<span className="notificationConsoleHeaderIconWithData">
|
||||||
<img src={LoadingIcon} alt="in progress items" />
|
<img src={LoadingIcon} alt="In progress items" />
|
||||||
<span className="numInProgress">{numInProgress}</span>
|
<span className="numInProgress">{numInProgress}</span>
|
||||||
</span>
|
</span>
|
||||||
<span className="notificationConsoleHeaderIconWithData">
|
<span className="notificationConsoleHeaderIconWithData">
|
||||||
<img src={ErrorBlackIcon} alt="error items" />
|
<img src={ErrorBlackIcon} alt="Error items" />
|
||||||
<span className="numErroredItems">{numErroredItems}</span>
|
<span className="numErroredItems">{numErroredItems}</span>
|
||||||
</span>
|
</span>
|
||||||
<span className="notificationConsoleHeaderIconWithData">
|
<span className="notificationConsoleHeaderIconWithData">
|
||||||
<img src={infoBubbleIcon} alt="info items" />
|
<img src={infoBubbleIcon} alt="Info items" />
|
||||||
<span className="numInfoItems">{numInfoItems}</span>
|
<span className="numInfoItems">{numInfoItems}</span>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
@@ -129,17 +127,10 @@ export class NotificationConsoleComponent extends React.Component<
|
|||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div className="expandCollapseButton" data-test="NotificationConsole/ExpandCollapseButton">
|
||||||
className="expandCollapseButton"
|
|
||||||
data-test="NotificationConsole/ExpandCollapseButton"
|
|
||||||
role="button"
|
|
||||||
tabIndex={0}
|
|
||||||
aria-label={"console button" + (this.props.isConsoleExpanded ? " expanded" : " collapsed")}
|
|
||||||
aria-expanded={!this.props.isConsoleExpanded}
|
|
||||||
>
|
|
||||||
<img
|
<img
|
||||||
src={this.props.isConsoleExpanded ? ChevronDownIcon : ChevronUpIcon}
|
src={this.props.isConsoleExpanded ? ChevronDownIcon : ChevronUpIcon}
|
||||||
alt={this.props.isConsoleExpanded ? "ChevronDownIcon" : "ChevronUpIcon"}
|
alt={this.props.isConsoleExpanded ? "Collapse icon" : "Expand icon"}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -259,9 +250,6 @@ export class NotificationConsoleComponent extends React.Component<
|
|||||||
}
|
}
|
||||||
|
|
||||||
private onConsoleWasExpanded = (): void => {
|
private onConsoleWasExpanded = (): void => {
|
||||||
if (this.props.isConsoleExpanded && this.consoleHeaderElement) {
|
|
||||||
this.consoleHeaderElement.focus();
|
|
||||||
}
|
|
||||||
useNotificationConsole.getState().setConsoleAnimationFinished(true);
|
useNotificationConsole.getState().setConsoleAnimationFinished(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -5,10 +5,13 @@ exports[`NotificationConsoleComponent renders the console 1`] = `
|
|||||||
className="notificationConsoleContainer"
|
className="notificationConsoleContainer"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
aria-expanded={false}
|
||||||
|
aria-label="Console"
|
||||||
className="notificationConsoleHeader"
|
className="notificationConsoleHeader"
|
||||||
id="notificationConsoleHeader"
|
id="notificationConsoleHeader"
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
onKeyDown={[Function]}
|
onKeyDown={[Function]}
|
||||||
|
role="button"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@@ -21,7 +24,7 @@ exports[`NotificationConsoleComponent renders the console 1`] = `
|
|||||||
className="notificationConsoleHeaderIconWithData"
|
className="notificationConsoleHeaderIconWithData"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt="in progress items"
|
alt="In progress items"
|
||||||
src={{}}
|
src={{}}
|
||||||
/>
|
/>
|
||||||
<span
|
<span
|
||||||
@@ -34,7 +37,7 @@ exports[`NotificationConsoleComponent renders the console 1`] = `
|
|||||||
className="notificationConsoleHeaderIconWithData"
|
className="notificationConsoleHeaderIconWithData"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt="error items"
|
alt="Error items"
|
||||||
src={{}}
|
src={{}}
|
||||||
/>
|
/>
|
||||||
<span
|
<span
|
||||||
@@ -47,7 +50,7 @@ exports[`NotificationConsoleComponent renders the console 1`] = `
|
|||||||
className="notificationConsoleHeaderIconWithData"
|
className="notificationConsoleHeaderIconWithData"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt="info items"
|
alt="Info items"
|
||||||
src={{}}
|
src={{}}
|
||||||
/>
|
/>
|
||||||
<span
|
<span
|
||||||
@@ -71,15 +74,11 @@ exports[`NotificationConsoleComponent renders the console 1`] = `
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
aria-expanded={true}
|
|
||||||
aria-label="console button collapsed"
|
|
||||||
className="expandCollapseButton"
|
className="expandCollapseButton"
|
||||||
data-test="NotificationConsole/ExpandCollapseButton"
|
data-test="NotificationConsole/ExpandCollapseButton"
|
||||||
role="button"
|
|
||||||
tabIndex={0}
|
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt="ChevronUpIcon"
|
alt="Expand icon"
|
||||||
src=""
|
src=""
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -176,10 +175,13 @@ exports[`NotificationConsoleComponent renders the console 2`] = `
|
|||||||
className="notificationConsoleContainer"
|
className="notificationConsoleContainer"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
aria-expanded={false}
|
||||||
|
aria-label="Console"
|
||||||
className="notificationConsoleHeader"
|
className="notificationConsoleHeader"
|
||||||
id="notificationConsoleHeader"
|
id="notificationConsoleHeader"
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
onKeyDown={[Function]}
|
onKeyDown={[Function]}
|
||||||
|
role="button"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@@ -192,7 +194,7 @@ exports[`NotificationConsoleComponent renders the console 2`] = `
|
|||||||
className="notificationConsoleHeaderIconWithData"
|
className="notificationConsoleHeaderIconWithData"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt="in progress items"
|
alt="In progress items"
|
||||||
src={{}}
|
src={{}}
|
||||||
/>
|
/>
|
||||||
<span
|
<span
|
||||||
@@ -205,7 +207,7 @@ exports[`NotificationConsoleComponent renders the console 2`] = `
|
|||||||
className="notificationConsoleHeaderIconWithData"
|
className="notificationConsoleHeaderIconWithData"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt="error items"
|
alt="Error items"
|
||||||
src={{}}
|
src={{}}
|
||||||
/>
|
/>
|
||||||
<span
|
<span
|
||||||
@@ -218,7 +220,7 @@ exports[`NotificationConsoleComponent renders the console 2`] = `
|
|||||||
className="notificationConsoleHeaderIconWithData"
|
className="notificationConsoleHeaderIconWithData"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt="info items"
|
alt="Info items"
|
||||||
src={{}}
|
src={{}}
|
||||||
/>
|
/>
|
||||||
<span
|
<span
|
||||||
@@ -244,15 +246,11 @@ exports[`NotificationConsoleComponent renders the console 2`] = `
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
aria-expanded={true}
|
|
||||||
aria-label="console button collapsed"
|
|
||||||
className="expandCollapseButton"
|
className="expandCollapseButton"
|
||||||
data-test="NotificationConsole/ExpandCollapseButton"
|
data-test="NotificationConsole/ExpandCollapseButton"
|
||||||
role="button"
|
|
||||||
tabIndex={0}
|
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt="ChevronUpIcon"
|
alt="Expand icon"
|
||||||
src=""
|
src=""
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* Notebook container related stuff
|
* Notebook container related stuff
|
||||||
*/
|
*/
|
||||||
import { useDialog } from "Explorer/Controls/Dialog";
|
import { useDialog } from "Explorer/Controls/Dialog";
|
||||||
import promiseRetry, { AbortError } from "p-retry";
|
import promiseRetry, { AbortError, Options } from "p-retry";
|
||||||
import { PhoenixClient } from "Phoenix/PhoenixClient";
|
import { PhoenixClient } from "Phoenix/PhoenixClient";
|
||||||
import * as Constants from "../../Common/Constants";
|
import * as Constants from "../../Common/Constants";
|
||||||
import { ConnectionStatusType, HttpHeaders, HttpStatusCodes, Notebook, PoolIdType } from "../../Common/Constants";
|
import { ConnectionStatusType, HttpHeaders, HttpStatusCodes, Notebook, PoolIdType } from "../../Common/Constants";
|
||||||
@@ -19,7 +19,7 @@ export class NotebookContainerClient {
|
|||||||
private clearReconnectionAttemptMessage? = () => {};
|
private clearReconnectionAttemptMessage? = () => {};
|
||||||
private isResettingWorkspace: boolean;
|
private isResettingWorkspace: boolean;
|
||||||
private phoenixClient: PhoenixClient;
|
private phoenixClient: PhoenixClient;
|
||||||
private retryOptions: promiseRetry.Options;
|
private retryOptions: Options;
|
||||||
private scheduleTimerId: NodeJS.Timeout;
|
private scheduleTimerId: NodeJS.Timeout;
|
||||||
|
|
||||||
constructor(private onConnectionLost: () => void) {
|
constructor(private onConnectionLost: () => void) {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { isPublicInternetAccessAllowed } from "Common/DatabaseAccountUtility";
|
import { isPublicInternetAccessAllowed } from "Common/DatabaseAccountUtility";
|
||||||
import { PhoenixClient } from "Phoenix/PhoenixClient";
|
import { PhoenixClient } from "Phoenix/PhoenixClient";
|
||||||
import { useNewPortalBackendEndpoint } from "Utils/EndpointUtils";
|
|
||||||
import { cloneDeep } from "lodash";
|
import { cloneDeep } from "lodash";
|
||||||
import create, { UseStore } from "zustand";
|
import create, { UseStore } from "zustand";
|
||||||
import { AuthType } from "../../AuthType";
|
import { AuthType } from "../../AuthType";
|
||||||
@@ -128,9 +127,7 @@ export const useNotebook: UseStore<NotebookState> = create((set, get) => ({
|
|||||||
userContext.apiType === "Postgres" || userContext.apiType === "VCoreMongo"
|
userContext.apiType === "Postgres" || userContext.apiType === "VCoreMongo"
|
||||||
? databaseAccount?.location
|
? databaseAccount?.location
|
||||||
: databaseAccount?.properties?.writeLocations?.[0]?.locationName.toLowerCase();
|
: databaseAccount?.properties?.writeLocations?.[0]?.locationName.toLowerCase();
|
||||||
const disallowedLocationsUri: string = useNewPortalBackendEndpoint(Constants.BackendApi.DisallowedLocations)
|
const disallowedLocationsUri: string = `${configContext.PORTAL_BACKEND_ENDPOINT}/api/disallowedlocations`;
|
||||||
? `${configContext.PORTAL_BACKEND_ENDPOINT}/api/disallowedlocations`
|
|
||||||
: `${configContext.BACKEND_ENDPOINT}/api/disallowedLocations`;
|
|
||||||
const authorizationHeader = getAuthorizationHeader();
|
const authorizationHeader = getAuthorizationHeader();
|
||||||
try {
|
try {
|
||||||
const response = await fetch(disallowedLocationsUri, {
|
const response = await fetch(disallowedLocationsUri, {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// TODO convert this file to an action registry in order to have actions and their handlers be more tightly coupled.
|
// TODO convert this file to an action registry in order to have actions and their handlers be more tightly coupled.
|
||||||
import { configContext, Platform } from "ConfigContext";
|
|
||||||
import { useDatabases } from "Explorer/useDatabases";
|
import { useDatabases } from "Explorer/useDatabases";
|
||||||
|
import { isFabricMirrored } from "Platform/Fabric/FabricUtil";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { ActionContracts } from "../../Contracts/ExplorerContracts";
|
import { ActionContracts } from "../../Contracts/ExplorerContracts";
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
@@ -58,9 +58,9 @@ function openCollectionTab(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
configContext.platform === Platform.Fabric &&
|
isFabricMirrored() &&
|
||||||
!(
|
!(
|
||||||
// whitelist the tab kinds that are allowed to be opened in Fabric
|
// whitelist the tab kinds that are allowed to be opened in Fabric mirrored
|
||||||
(
|
(
|
||||||
action.tabKind === ActionContracts.TabKind.SQLDocuments ||
|
action.tabKind === ActionContracts.TabKind.SQLDocuments ||
|
||||||
action.tabKind === ActionContracts.TabKind.SQLQuery
|
action.tabKind === ActionContracts.TabKind.SQLQuery
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import {
|
|||||||
import { VectorEmbeddingPoliciesComponent } from "Explorer/Controls/VectorSearch/VectorEmbeddingPoliciesComponent";
|
import { VectorEmbeddingPoliciesComponent } from "Explorer/Controls/VectorSearch/VectorEmbeddingPoliciesComponent";
|
||||||
import { useSidePanel } from "hooks/useSidePanel";
|
import { useSidePanel } from "hooks/useSidePanel";
|
||||||
import { useTeachingBubble } from "hooks/useTeachingBubble";
|
import { useTeachingBubble } from "hooks/useTeachingBubble";
|
||||||
|
import { isFabricNative } from "Platform/Fabric/FabricUtil";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { CollectionCreation } from "Shared/Constants";
|
import { CollectionCreation } from "Shared/Constants";
|
||||||
import { Action } from "Shared/Telemetry/TelemetryConstants";
|
import { Action } from "Shared/Telemetry/TelemetryConstants";
|
||||||
@@ -284,150 +285,152 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="panelMainContent">
|
<div className="panelMainContent">
|
||||||
<Stack hidden={userContext.apiType === "Tables"}>
|
{!(isFabricNative() && this.props.databaseId !== undefined) && (
|
||||||
<Stack horizontal>
|
<Stack hidden={userContext.apiType === "Tables"}>
|
||||||
<span className="mandatoryStar">* </span>
|
<Stack horizontal>
|
||||||
<Text className="panelTextBold" variant="small">
|
<span className="mandatoryStar">* </span>
|
||||||
Database {userContext.apiType === "Mongo" ? "name" : "id"}
|
<Text className="panelTextBold" variant="small">
|
||||||
</Text>
|
Database {userContext.apiType === "Mongo" ? "name" : "id"}
|
||||||
<TooltipHost
|
</Text>
|
||||||
directionalHint={DirectionalHint.bottomLeftEdge}
|
<TooltipHost
|
||||||
content={`A database is analogous to a namespace. It is the unit of management for a set of ${getCollectionName(
|
directionalHint={DirectionalHint.bottomLeftEdge}
|
||||||
true,
|
content={`A database is analogous to a namespace. It is the unit of management for a set of ${getCollectionName(
|
||||||
).toLocaleLowerCase()}.`}
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
iconName="Info"
|
|
||||||
className="panelInfoIcon"
|
|
||||||
tabIndex={0}
|
|
||||||
ariaLabel={`A database is analogous to a namespace. It is the unit of management for a set of ${getCollectionName(
|
|
||||||
true,
|
true,
|
||||||
).toLocaleLowerCase()}.`}
|
).toLocaleLowerCase()}.`}
|
||||||
/>
|
>
|
||||||
</TooltipHost>
|
<Icon
|
||||||
</Stack>
|
iconName="Info"
|
||||||
|
className="panelInfoIcon"
|
||||||
{configContext.platform !== Platform.Fabric && (
|
|
||||||
<Stack horizontal verticalAlign="center">
|
|
||||||
<div role="radiogroup">
|
|
||||||
<input
|
|
||||||
className="panelRadioBtn"
|
|
||||||
checked={this.state.createNewDatabase}
|
|
||||||
aria-label="Create new database"
|
|
||||||
aria-checked={this.state.createNewDatabase}
|
|
||||||
name="databaseType"
|
|
||||||
type="radio"
|
|
||||||
role="radio"
|
|
||||||
id="databaseCreateNew"
|
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onChange={this.onCreateNewDatabaseRadioBtnChange.bind(this)}
|
ariaLabel={`A database is analogous to a namespace. It is the unit of management for a set of ${getCollectionName(
|
||||||
|
true,
|
||||||
|
).toLocaleLowerCase()}.`}
|
||||||
/>
|
/>
|
||||||
<span className="panelRadioBtnLabel">Create new</span>
|
</TooltipHost>
|
||||||
|
|
||||||
<input
|
|
||||||
className="panelRadioBtn"
|
|
||||||
checked={!this.state.createNewDatabase}
|
|
||||||
aria-label="Use existing database"
|
|
||||||
aria-checked={!this.state.createNewDatabase}
|
|
||||||
name="databaseType"
|
|
||||||
type="radio"
|
|
||||||
role="radio"
|
|
||||||
tabIndex={0}
|
|
||||||
onChange={this.onUseExistingDatabaseRadioBtnChange.bind(this)}
|
|
||||||
/>
|
|
||||||
<span className="panelRadioBtnLabel">Use existing</span>
|
|
||||||
</div>
|
|
||||||
</Stack>
|
</Stack>
|
||||||
)}
|
|
||||||
|
|
||||||
{this.state.createNewDatabase && (
|
{configContext.platform !== Platform.Fabric && (
|
||||||
<Stack className="panelGroupSpacing">
|
<Stack horizontal verticalAlign="center">
|
||||||
<input
|
<div role="radiogroup">
|
||||||
name="newDatabaseId"
|
<input
|
||||||
id="newDatabaseId"
|
className="panelRadioBtn"
|
||||||
aria-required
|
checked={this.state.createNewDatabase}
|
||||||
required
|
aria-label="Create new database"
|
||||||
type="text"
|
aria-checked={this.state.createNewDatabase}
|
||||||
autoComplete="off"
|
name="databaseType"
|
||||||
pattern="[^/?#\\]*[^/?# \\]"
|
type="radio"
|
||||||
title="May not end with space nor contain characters '\' '/' '#' '?'"
|
role="radio"
|
||||||
placeholder="Type a new database id"
|
id="databaseCreateNew"
|
||||||
size={40}
|
tabIndex={0}
|
||||||
className="panelTextField"
|
onChange={this.onCreateNewDatabaseRadioBtnChange.bind(this)}
|
||||||
aria-label="New database id, Type a new database id"
|
|
||||||
autoFocus
|
|
||||||
tabIndex={0}
|
|
||||||
value={this.state.newDatabaseId}
|
|
||||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
|
||||||
this.setState({ newDatabaseId: event.target.value })
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{!isServerlessAccount() && (
|
|
||||||
<Stack horizontal>
|
|
||||||
<Checkbox
|
|
||||||
label={`Share throughput across ${getCollectionName(true).toLocaleLowerCase()}`}
|
|
||||||
checked={this.state.isSharedThroughputChecked}
|
|
||||||
styles={{
|
|
||||||
text: { fontSize: 12 },
|
|
||||||
checkbox: { width: 12, height: 12 },
|
|
||||||
label: { padding: 0, alignItems: "center" },
|
|
||||||
}}
|
|
||||||
onChange={(ev: React.FormEvent<HTMLElement>, isChecked: boolean) =>
|
|
||||||
this.setState({ isSharedThroughputChecked: isChecked })
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
<TooltipHost
|
<span className="panelRadioBtnLabel">Create new</span>
|
||||||
directionalHint={DirectionalHint.bottomLeftEdge}
|
|
||||||
content={`Throughput configured at the database level will be shared across all ${getCollectionName(
|
<input
|
||||||
true,
|
className="panelRadioBtn"
|
||||||
).toLocaleLowerCase()} within the database.`}
|
checked={!this.state.createNewDatabase}
|
||||||
>
|
aria-label="Use existing database"
|
||||||
<Icon
|
aria-checked={!this.state.createNewDatabase}
|
||||||
iconName="Info"
|
name="databaseType"
|
||||||
className="panelInfoIcon"
|
type="radio"
|
||||||
tabIndex={0}
|
role="radio"
|
||||||
ariaLabel={`Throughput configured at the database level will be shared across all ${getCollectionName(
|
tabIndex={0}
|
||||||
|
onChange={this.onUseExistingDatabaseRadioBtnChange.bind(this)}
|
||||||
|
/>
|
||||||
|
<span className="panelRadioBtnLabel">Use existing</span>
|
||||||
|
</div>
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{this.state.createNewDatabase && (
|
||||||
|
<Stack className="panelGroupSpacing">
|
||||||
|
<input
|
||||||
|
name="newDatabaseId"
|
||||||
|
id="newDatabaseId"
|
||||||
|
aria-required
|
||||||
|
required
|
||||||
|
type="text"
|
||||||
|
autoComplete="off"
|
||||||
|
pattern="[^/?#\\]*[^/?# \\]"
|
||||||
|
title="May not end with space nor contain characters '\' '/' '#' '?'"
|
||||||
|
placeholder="Type a new database id"
|
||||||
|
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>) =>
|
||||||
|
this.setState({ newDatabaseId: event.target.value })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{!isServerlessAccount() && (
|
||||||
|
<Stack horizontal>
|
||||||
|
<Checkbox
|
||||||
|
label={`Share throughput across ${getCollectionName(true).toLocaleLowerCase()}`}
|
||||||
|
checked={this.state.isSharedThroughputChecked}
|
||||||
|
styles={{
|
||||||
|
text: { fontSize: 12 },
|
||||||
|
checkbox: { width: 12, height: 12 },
|
||||||
|
label: { padding: 0, alignItems: "center" },
|
||||||
|
}}
|
||||||
|
onChange={(ev: React.FormEvent<HTMLElement>, isChecked: boolean) =>
|
||||||
|
this.setState({ isSharedThroughputChecked: isChecked })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<TooltipHost
|
||||||
|
directionalHint={DirectionalHint.bottomLeftEdge}
|
||||||
|
content={`Throughput configured at the database level will be shared across all ${getCollectionName(
|
||||||
true,
|
true,
|
||||||
).toLocaleLowerCase()} within the database.`}
|
).toLocaleLowerCase()} within the database.`}
|
||||||
/>
|
>
|
||||||
</TooltipHost>
|
<Icon
|
||||||
</Stack>
|
iconName="Info"
|
||||||
)}
|
className="panelInfoIcon"
|
||||||
|
tabIndex={0}
|
||||||
|
ariaLabel={`Throughput configured at the database level will be shared across all ${getCollectionName(
|
||||||
|
true,
|
||||||
|
).toLocaleLowerCase()} within the database.`}
|
||||||
|
/>
|
||||||
|
</TooltipHost>
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
|
|
||||||
{!isServerlessAccount() && this.state.isSharedThroughputChecked && (
|
{!isServerlessAccount() && this.state.isSharedThroughputChecked && (
|
||||||
<ThroughputInput
|
<ThroughputInput
|
||||||
showFreeTierExceedThroughputTooltip={this.isFreeTierAccount() && !isFirstResourceCreated}
|
showFreeTierExceedThroughputTooltip={this.isFreeTierAccount() && !isFirstResourceCreated}
|
||||||
isDatabase={true}
|
isDatabase={true}
|
||||||
isSharded={this.state.isSharded}
|
isSharded={this.state.isSharded}
|
||||||
isFreeTier={this.isFreeTierAccount()}
|
isFreeTier={this.isFreeTierAccount()}
|
||||||
isQuickstart={this.props.isQuickstart}
|
isQuickstart={this.props.isQuickstart}
|
||||||
setThroughputValue={(throughput: number) => (this.newDatabaseThroughput = throughput)}
|
setThroughputValue={(throughput: number) => (this.newDatabaseThroughput = throughput)}
|
||||||
setIsAutoscale={(isAutoscale: boolean) => (this.isNewDatabaseAutoscale = isAutoscale)}
|
setIsAutoscale={(isAutoscale: boolean) => (this.isNewDatabaseAutoscale = isAutoscale)}
|
||||||
setIsThroughputCapExceeded={(isThroughputCapExceeded: boolean) =>
|
setIsThroughputCapExceeded={(isThroughputCapExceeded: boolean) =>
|
||||||
this.setState({ isThroughputCapExceeded })
|
this.setState({ isThroughputCapExceeded })
|
||||||
}
|
}
|
||||||
onCostAcknowledgeChange={(isAcknowledge: boolean) => (this.isCostAcknowledged = isAcknowledge)}
|
onCostAcknowledgeChange={(isAcknowledge: boolean) => (this.isCostAcknowledged = isAcknowledge)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
{!this.state.createNewDatabase && (
|
{!this.state.createNewDatabase && (
|
||||||
<Dropdown
|
<Dropdown
|
||||||
ariaLabel="Choose an existing database"
|
ariaLabel="Choose an existing database"
|
||||||
styles={{ title: { height: 27, lineHeight: 27 }, dropdownItem: { fontSize: 12 } }}
|
styles={{ title: { height: 27, lineHeight: 27 }, dropdownItem: { fontSize: 12 } }}
|
||||||
style={{ width: 300, fontSize: 12 }}
|
style={{ width: 300, fontSize: 12 }}
|
||||||
placeholder="Choose an existing database"
|
placeholder="Choose an existing database"
|
||||||
options={this.getDatabaseOptions()}
|
options={this.getDatabaseOptions()}
|
||||||
onChange={(event: React.FormEvent<HTMLDivElement>, database: IDropdownOption) =>
|
onChange={(event: React.FormEvent<HTMLDivElement>, database: IDropdownOption) =>
|
||||||
this.setState({ selectedDatabaseId: database.key as string })
|
this.setState({ selectedDatabaseId: database.key as string })
|
||||||
}
|
}
|
||||||
defaultSelectedKey={this.props.databaseId}
|
defaultSelectedKey={this.props.databaseId}
|
||||||
responsiveMode={999}
|
responsiveMode={999}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Separator className="panelSeparator" />
|
<Separator className="panelSeparator" />
|
||||||
</Stack>
|
</Stack>
|
||||||
|
)}
|
||||||
|
|
||||||
<Stack>
|
<Stack>
|
||||||
<Stack horizontal>
|
<Stack horizontal>
|
||||||
@@ -666,7 +669,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{userContext.apiType === "SQL" && (
|
{!isFabricNative() && userContext.apiType === "SQL" && (
|
||||||
<Stack className="panelGroupSpacing">
|
<Stack className="panelGroupSpacing">
|
||||||
<DefaultButton
|
<DefaultButton
|
||||||
styles={{ root: { padding: 0, width: 200, height: 30 }, label: { fontSize: 12 } }}
|
styles={{ root: { padding: 0, width: 200, height: 30 }, label: { fontSize: 12 } }}
|
||||||
@@ -747,7 +750,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{userContext.apiType === "SQL" && (
|
{!isFabricNative() && userContext.apiType === "SQL" && (
|
||||||
<Stack>
|
<Stack>
|
||||||
<Stack horizontal>
|
<Stack horizontal>
|
||||||
<Text className="panelTextBold" variant="small">
|
<Text className="panelTextBold" variant="small">
|
||||||
@@ -865,6 +868,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
<Link
|
<Link
|
||||||
href="https://aka.ms/cosmosdb-synapselink"
|
href="https://aka.ms/cosmosdb-synapselink"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
|
aria-label={Constants.ariaLabelForLearnMoreLink.AzureSynapseLink}
|
||||||
className="capacitycalculator-link"
|
className="capacitycalculator-link"
|
||||||
>
|
>
|
||||||
Learn more
|
Learn more
|
||||||
@@ -936,7 +940,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
</CollapsibleSectionComponent>
|
</CollapsibleSectionComponent>
|
||||||
</Stack>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
{userContext.apiType !== "Tables" && (
|
{!isFabricNative() && userContext.apiType !== "Tables" && (
|
||||||
<CollapsibleSectionComponent
|
<CollapsibleSectionComponent
|
||||||
title="Advanced"
|
title="Advanced"
|
||||||
isExpandedByDefault={false}
|
isExpandedByDefault={false}
|
||||||
@@ -1222,7 +1226,11 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
<Text variant="small">
|
<Text variant="small">
|
||||||
Enable analytical store capability to perform near real-time analytics on your operational data, without
|
Enable analytical store capability to perform near real-time analytics on your operational data, without
|
||||||
impacting the performance of transactional workloads.{" "}
|
impacting the performance of transactional workloads.{" "}
|
||||||
<Link target="_blank" href="https://aka.ms/analytical-store-overview">
|
<Link
|
||||||
|
aria-label={Constants.ariaLabelForLearnMoreLink.AnalyticalStore}
|
||||||
|
target="_blank"
|
||||||
|
href="https://aka.ms/analytical-store-overview"
|
||||||
|
>
|
||||||
Learn more
|
Learn more
|
||||||
</Link>
|
</Link>
|
||||||
</Text>
|
</Text>
|
||||||
@@ -1255,7 +1263,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
private shouldShowCollectionThroughputInput(): boolean {
|
private shouldShowCollectionThroughputInput(): boolean {
|
||||||
if (isServerlessAccount()) {
|
if (isFabricNative() || isServerlessAccount()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1281,7 +1289,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
}
|
}
|
||||||
|
|
||||||
private shouldShowAnalyticalStoreOptions(): boolean {
|
private shouldShowAnalyticalStoreOptions(): boolean {
|
||||||
if (configContext.platform === Platform.Emulator) {
|
if (isFabricNative() || configContext.platform === Platform.Emulator) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -94,6 +94,7 @@
|
|||||||
padding-left: @MediumSpace;
|
padding-left: @MediumSpace;
|
||||||
|
|
||||||
.paneErrorLink {
|
.paneErrorLink {
|
||||||
|
color: @LinkColor;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: @mediumFontSize;
|
font-size: @mediumFontSize;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import {
|
|||||||
} from "Shared/StorageUtility";
|
} from "Shared/StorageUtility";
|
||||||
import * as StringUtility from "Shared/StringUtility";
|
import * as StringUtility from "Shared/StringUtility";
|
||||||
import { updateUserContext, userContext } from "UserContext";
|
import { updateUserContext, userContext } from "UserContext";
|
||||||
|
import { isDataplaneRbacSupported } from "Utils/APITypeUtils";
|
||||||
import { acquireMsalTokenForAccount } from "Utils/AuthorizationUtils";
|
import { acquireMsalTokenForAccount } from "Utils/AuthorizationUtils";
|
||||||
import { logConsoleError, logConsoleInfo } from "Utils/NotificationConsoleUtils";
|
import { logConsoleError, logConsoleInfo } from "Utils/NotificationConsoleUtils";
|
||||||
import * as PriorityBasedExecutionUtils from "Utils/PriorityBasedExecutionUtils";
|
import * as PriorityBasedExecutionUtils from "Utils/PriorityBasedExecutionUtils";
|
||||||
@@ -174,15 +175,26 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
|||||||
const styles = useStyles();
|
const styles = useStyles();
|
||||||
|
|
||||||
const explorerVersion = configContext.gitSha;
|
const explorerVersion = configContext.gitSha;
|
||||||
|
const isEmulator = configContext.platform === Platform.Emulator;
|
||||||
const shouldShowQueryPageOptions = userContext.apiType === "SQL";
|
const shouldShowQueryPageOptions = userContext.apiType === "SQL";
|
||||||
const shouldShowGraphAutoVizOption = userContext.apiType === "Gremlin";
|
const showRetrySettings =
|
||||||
const shouldShowCrossPartitionOption = userContext.apiType !== "Gremlin";
|
(userContext.apiType === "SQL" || userContext.apiType === "Tables" || userContext.apiType === "Gremlin") &&
|
||||||
const shouldShowParallelismOption = userContext.apiType !== "Gremlin";
|
!isEmulator;
|
||||||
const shouldShowPriorityLevelOption = PriorityBasedExecutionUtils.isFeatureEnabled();
|
const shouldShowGraphAutoVizOption = userContext.apiType === "Gremlin" && !isEmulator;
|
||||||
|
const shouldShowCrossPartitionOption = userContext.apiType !== "Gremlin" && !isEmulator;
|
||||||
|
const shouldShowParallelismOption = userContext.apiType !== "Gremlin" && !isEmulator;
|
||||||
|
const showEnableEntraIdRbac =
|
||||||
|
isDataplaneRbacSupported(userContext.apiType) &&
|
||||||
|
userContext.authType === AuthType.AAD &&
|
||||||
|
configContext.platform !== Platform.Fabric &&
|
||||||
|
!isEmulator;
|
||||||
|
const shouldShowPriorityLevelOption = PriorityBasedExecutionUtils.isFeatureEnabled() && !isEmulator;
|
||||||
const shouldShowCopilotSampleDBOption =
|
const shouldShowCopilotSampleDBOption =
|
||||||
userContext.apiType === "SQL" &&
|
userContext.apiType === "SQL" &&
|
||||||
useQueryCopilot.getState().copilotEnabled &&
|
useQueryCopilot.getState().copilotEnabled &&
|
||||||
useDatabases.getState().sampleDataResourceTokenCollection;
|
useDatabases.getState().sampleDataResourceTokenCollection &&
|
||||||
|
!isEmulator;
|
||||||
|
|
||||||
const handlerOnSubmit = async () => {
|
const handlerOnSubmit = async () => {
|
||||||
setIsExecuting(true);
|
setIsExecuting(true);
|
||||||
|
|
||||||
@@ -491,7 +503,7 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
|||||||
return (
|
return (
|
||||||
<RightPaneForm {...genericPaneProps}>
|
<RightPaneForm {...genericPaneProps}>
|
||||||
<div className={`paneMainContent ${styles.container}`}>
|
<div className={`paneMainContent ${styles.container}`}>
|
||||||
<Accordion className={styles.firstItem}>
|
<Accordion className={`customAccordion ${styles.firstItem}`}>
|
||||||
{shouldShowQueryPageOptions && (
|
{shouldShowQueryPageOptions && (
|
||||||
<AccordionItem value="1">
|
<AccordionItem value="1">
|
||||||
<AccordionHeader>
|
<AccordionHeader>
|
||||||
@@ -541,39 +553,37 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
|||||||
</AccordionPanel>
|
</AccordionPanel>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
)}
|
)}
|
||||||
{userContext.apiType === "SQL" &&
|
{showEnableEntraIdRbac && (
|
||||||
userContext.authType === AuthType.AAD &&
|
<AccordionItem value="2">
|
||||||
configContext.platform !== Platform.Fabric && (
|
<AccordionHeader>
|
||||||
<AccordionItem value="2">
|
<div className={styles.header}>Enable Entra ID RBAC</div>
|
||||||
<AccordionHeader>
|
</AccordionHeader>
|
||||||
<div className={styles.header}>Enable Entra ID RBAC</div>
|
<AccordionPanel>
|
||||||
</AccordionHeader>
|
<div className={styles.settingsSectionContainer}>
|
||||||
<AccordionPanel>
|
<div className={styles.settingsSectionDescription}>
|
||||||
<div className={styles.settingsSectionContainer}>
|
Choose Automatic to enable Entra ID RBAC automatically. True/False to force enable/disable Entra ID
|
||||||
<div className={styles.settingsSectionDescription}>
|
RBAC.
|
||||||
Choose Automatic to enable Entra ID RBAC automatically. True/False to force enable/disable Entra
|
<a
|
||||||
ID RBAC.
|
href="https://learn.microsoft.com/en-us/azure/cosmos-db/how-to-setup-rbac#use-data-explorer"
|
||||||
<a
|
target="_blank"
|
||||||
href="https://learn.microsoft.com/en-us/azure/cosmos-db/how-to-setup-rbac#use-data-explorer"
|
rel="noopener noreferrer"
|
||||||
target="_blank"
|
>
|
||||||
rel="noopener noreferrer"
|
{" "}
|
||||||
>
|
Learn more{" "}
|
||||||
{" "}
|
</a>
|
||||||
Learn more{" "}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<ChoiceGroup
|
|
||||||
ariaLabelledBy="enableDataPlaneRBACOptions"
|
|
||||||
options={dataPlaneRBACOptionsList}
|
|
||||||
styles={choiceButtonStyles}
|
|
||||||
selectedKey={enableDataPlaneRBACOption}
|
|
||||||
onChange={handleOnDataPlaneRBACOptionChange}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</AccordionPanel>
|
<ChoiceGroup
|
||||||
</AccordionItem>
|
ariaLabelledBy="enableDataPlaneRBACOptions"
|
||||||
)}
|
options={dataPlaneRBACOptionsList}
|
||||||
{userContext.apiType === "SQL" && (
|
styles={choiceButtonStyles}
|
||||||
|
selectedKey={enableDataPlaneRBACOption}
|
||||||
|
onChange={handleOnDataPlaneRBACOptionChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</AccordionPanel>
|
||||||
|
</AccordionItem>
|
||||||
|
)}
|
||||||
|
{userContext.apiType === "SQL" && !isEmulator && (
|
||||||
<>
|
<>
|
||||||
<AccordionItem value="3">
|
<AccordionItem value="3">
|
||||||
<AccordionHeader>
|
<AccordionHeader>
|
||||||
@@ -671,7 +681,7 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
|||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{(userContext.apiType === "SQL" || userContext.apiType === "Tables" || userContext.apiType === "Gremlin") && (
|
{showRetrySettings && (
|
||||||
<AccordionItem value="6">
|
<AccordionItem value="6">
|
||||||
<AccordionHeader>
|
<AccordionHeader>
|
||||||
<div className={styles.header}>Retry Settings</div>
|
<div className={styles.header}>Retry Settings</div>
|
||||||
@@ -744,29 +754,30 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
|
|||||||
</AccordionPanel>
|
</AccordionPanel>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
)}
|
)}
|
||||||
|
{!isEmulator && (
|
||||||
<AccordionItem value="7">
|
<AccordionItem value="7">
|
||||||
<AccordionHeader>
|
<AccordionHeader>
|
||||||
<div className={styles.header}>Enable container pagination</div>
|
<div className={styles.header}>Enable container pagination</div>
|
||||||
</AccordionHeader>
|
</AccordionHeader>
|
||||||
<AccordionPanel>
|
<AccordionPanel>
|
||||||
<div className={styles.settingsSectionContainer}>
|
<div className={styles.settingsSectionContainer}>
|
||||||
<div className={styles.settingsSectionDescription}>
|
<div className={styles.settingsSectionDescription}>
|
||||||
Load 50 containers at a time. Currently, containers are not pulled in alphanumeric order.
|
Load 50 containers at a time. Currently, containers are not pulled in alphanumeric order.
|
||||||
|
</div>
|
||||||
|
<Checkbox
|
||||||
|
styles={{
|
||||||
|
label: { padding: 0 },
|
||||||
|
}}
|
||||||
|
className="padding"
|
||||||
|
ariaLabel="Enable container pagination"
|
||||||
|
checked={containerPaginationEnabled}
|
||||||
|
onChange={() => setContainerPaginationEnabled(!containerPaginationEnabled)}
|
||||||
|
label="Enable container pagination"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Checkbox
|
</AccordionPanel>
|
||||||
styles={{
|
</AccordionItem>
|
||||||
label: { padding: 0 },
|
)}
|
||||||
}}
|
|
||||||
className="padding"
|
|
||||||
ariaLabel="Enable container pagination"
|
|
||||||
checked={containerPaginationEnabled}
|
|
||||||
onChange={() => setContainerPaginationEnabled(!containerPaginationEnabled)}
|
|
||||||
label="Enable container pagination"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</AccordionPanel>
|
|
||||||
</AccordionItem>
|
|
||||||
{shouldShowCrossPartitionOption && (
|
{shouldShowCrossPartitionOption && (
|
||||||
<AccordionItem value="8">
|
<AccordionItem value="8">
|
||||||
<AccordionHeader>
|
<AccordionHeader>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ exports[`Settings Pane should render Default properly 1`] = `
|
|||||||
className="paneMainContent ___133e6fg_0000000 f22iagw f1vx9l62 f1l02sjl"
|
className="paneMainContent ___133e6fg_0000000 f22iagw f1vx9l62 f1l02sjl"
|
||||||
>
|
>
|
||||||
<Accordion
|
<Accordion
|
||||||
className="___1uf6361_0000000 fz7g6wx"
|
className="customAccordion ___1uf6361_0000000 fz7g6wx"
|
||||||
>
|
>
|
||||||
<AccordionItem
|
<AccordionItem
|
||||||
value="1"
|
value="1"
|
||||||
@@ -572,7 +572,7 @@ exports[`Settings Pane should render Gremlin properly 1`] = `
|
|||||||
className="paneMainContent ___133e6fg_0000000 f22iagw f1vx9l62 f1l02sjl"
|
className="paneMainContent ___133e6fg_0000000 f22iagw f1vx9l62 f1l02sjl"
|
||||||
>
|
>
|
||||||
<Accordion
|
<Accordion
|
||||||
className="___1uf6361_0000000 fz7g6wx"
|
className="customAccordion ___1uf6361_0000000 fz7g6wx"
|
||||||
>
|
>
|
||||||
<AccordionItem
|
<AccordionItem
|
||||||
value="6"
|
value="6"
|
||||||
|
|||||||
@@ -319,6 +319,7 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
|
|||||||
Enable analytical store capability to perform near real-time analytics on your operational data, without impacting the performance of transactional workloads.
|
Enable analytical store capability to perform near real-time analytics on your operational data, without impacting the performance of transactional workloads.
|
||||||
|
|
||||||
<StyledLinkBase
|
<StyledLinkBase
|
||||||
|
aria-label="Learn more about analytical store."
|
||||||
href="https://aka.ms/analytical-store-overview"
|
href="https://aka.ms/analytical-store-overview"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
@@ -383,6 +384,7 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
|
|||||||
. Enable Synapse Link for this Cosmos DB account.
|
. Enable Synapse Link for this Cosmos DB account.
|
||||||
|
|
||||||
<StyledLinkBase
|
<StyledLinkBase
|
||||||
|
aria-label="Learn more about Azure Synapse Link."
|
||||||
className="capacitycalculator-link"
|
className="capacitycalculator-link"
|
||||||
href="https://aka.ms/cosmosdb-synapselink"
|
href="https://aka.ms/cosmosdb-synapselink"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import {
|
|||||||
Text,
|
Text,
|
||||||
TextField,
|
TextField,
|
||||||
} from "@fluentui/react";
|
} from "@fluentui/react";
|
||||||
import { HttpStatusCodes, NormalizedEventKey } from "Common/Constants";
|
import { FeedbackLabels, HttpStatusCodes, NormalizedEventKey } from "Common/Constants";
|
||||||
import { handleError } from "Common/ErrorHandlingUtils";
|
import { handleError } from "Common/ErrorHandlingUtils";
|
||||||
import QueryError, { QueryErrorSeverity } from "Common/QueryError";
|
import QueryError, { QueryErrorSeverity } from "Common/QueryError";
|
||||||
import { createUri } from "Common/UrlUtility";
|
import { createUri } from "Common/UrlUtility";
|
||||||
@@ -75,6 +75,7 @@ export const QueryCopilotPromptbar: React.FC<QueryCopilotPromptProps> = ({
|
|||||||
const inputEdited = useRef(false);
|
const inputEdited = useRef(false);
|
||||||
const itemRefs = useRef([]);
|
const itemRefs = useRef([]);
|
||||||
const searchInputRef = useRef(null);
|
const searchInputRef = useRef(null);
|
||||||
|
const copyQueryRef = useRef(null);
|
||||||
const {
|
const {
|
||||||
openFeedbackModal,
|
openFeedbackModal,
|
||||||
hideFeedbackModalForLikedQueries,
|
hideFeedbackModalForLikedQueries,
|
||||||
@@ -132,6 +133,7 @@ export const QueryCopilotPromptbar: React.FC<QueryCopilotPromptProps> = ({
|
|||||||
document.body.removeChild(queryElement);
|
document.body.removeChild(queryElement);
|
||||||
|
|
||||||
setshowCopyPopup(true);
|
setshowCopyPopup(true);
|
||||||
|
copyQueryRef.current.focus();
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setshowCopyPopup(false);
|
setshowCopyPopup(false);
|
||||||
}, 6000);
|
}, 6000);
|
||||||
@@ -305,7 +307,7 @@ export const QueryCopilotPromptbar: React.FC<QueryCopilotPromptProps> = ({
|
|||||||
if (isGeneratingQuery === null) {
|
if (isGeneratingQuery === null) {
|
||||||
return " ";
|
return " ";
|
||||||
} else if (isGeneratingQuery) {
|
} else if (isGeneratingQuery) {
|
||||||
return "Content is loading";
|
return "Thinking";
|
||||||
} else {
|
} else {
|
||||||
return "Content is updated";
|
return "Content is updated";
|
||||||
}
|
}
|
||||||
@@ -391,8 +393,7 @@ export const QueryCopilotPromptbar: React.FC<QueryCopilotPromptProps> = ({
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
disabled={isGeneratingQuery}
|
disabled={isGeneratingQuery}
|
||||||
autoComplete="list"
|
autoComplete="off"
|
||||||
aria-expanded={showSamplePrompts}
|
|
||||||
placeholder="Ask a question in natural language and we’ll generate the query for you."
|
placeholder="Ask a question in natural language and we’ll generate the query for you."
|
||||||
aria-labelledby="copilot-textfield-label"
|
aria-labelledby="copilot-textfield-label"
|
||||||
onRenderSuffix={() => {
|
onRenderSuffix={() => {
|
||||||
@@ -400,6 +401,7 @@ export const QueryCopilotPromptbar: React.FC<QueryCopilotPromptProps> = ({
|
|||||||
<IconButton
|
<IconButton
|
||||||
iconProps={{ iconName: "Send" }}
|
iconProps={{ iconName: "Send" }}
|
||||||
disabled={isGeneratingQuery || !userPrompt.trim()}
|
disabled={isGeneratingQuery || !userPrompt.trim()}
|
||||||
|
allowDisabledFocus={true}
|
||||||
style={{ background: "none" }}
|
style={{ background: "none" }}
|
||||||
onClick={() => startGenerateQueryProcess()}
|
onClick={() => startGenerateQueryProcess()}
|
||||||
aria-label="Send"
|
aria-label="Send"
|
||||||
@@ -577,7 +579,7 @@ export const QueryCopilotPromptbar: React.FC<QueryCopilotPromptProps> = ({
|
|||||||
<Stack horizontal verticalAlign="center" style={{ maxHeight: 20 }}>
|
<Stack horizontal verticalAlign="center" style={{ maxHeight: 20 }}>
|
||||||
{userContext.feedbackPolicies?.policyAllowFeedback && (
|
{userContext.feedbackPolicies?.policyAllowFeedback && (
|
||||||
<Stack horizontal verticalAlign="center">
|
<Stack horizontal verticalAlign="center">
|
||||||
<Text style={{ fontSize: 12 }}>Provide feedback</Text>
|
<Text style={{ fontSize: 12 }}>{FeedbackLabels.provideFeedback}</Text>
|
||||||
{showCallout && !hideFeedbackModalForLikedQueries && (
|
{showCallout && !hideFeedbackModalForLikedQueries && (
|
||||||
<Callout
|
<Callout
|
||||||
role="status"
|
role="status"
|
||||||
@@ -627,8 +629,9 @@ export const QueryCopilotPromptbar: React.FC<QueryCopilotPromptProps> = ({
|
|||||||
<IconButton
|
<IconButton
|
||||||
id="likeBtn"
|
id="likeBtn"
|
||||||
style={{ marginLeft: 10 }}
|
style={{ marginLeft: 10 }}
|
||||||
aria-label="Like"
|
aria-label={FeedbackLabels.provideFeedback}
|
||||||
role="toggle"
|
role="button"
|
||||||
|
title="Like"
|
||||||
iconProps={{ iconName: likeQuery === true ? "LikeSolid" : "Like" }}
|
iconProps={{ iconName: likeQuery === true ? "LikeSolid" : "Like" }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setShowCallout(!likeQuery);
|
setShowCallout(!likeQuery);
|
||||||
@@ -646,8 +649,9 @@ export const QueryCopilotPromptbar: React.FC<QueryCopilotPromptProps> = ({
|
|||||||
/>
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
style={{ margin: "0 4px" }}
|
style={{ margin: "0 4px" }}
|
||||||
role="toggle"
|
role="button"
|
||||||
aria-label="Dislike"
|
aria-label={FeedbackLabels.provideFeedback}
|
||||||
|
title="Dislike"
|
||||||
iconProps={{ iconName: dislikeQuery === true ? "DislikeSolid" : "Dislike" }}
|
iconProps={{ iconName: dislikeQuery === true ? "DislikeSolid" : "Dislike" }}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
let toggleStatusValue = "Unpressed";
|
let toggleStatusValue = "Unpressed";
|
||||||
@@ -676,6 +680,7 @@ export const QueryCopilotPromptbar: React.FC<QueryCopilotPromptProps> = ({
|
|||||||
)}
|
)}
|
||||||
<CommandBarButton
|
<CommandBarButton
|
||||||
className="copyQuery"
|
className="copyQuery"
|
||||||
|
elementRef={copyQueryRef}
|
||||||
onClick={copyGeneratedCode}
|
onClick={copyGeneratedCode}
|
||||||
iconProps={{ iconName: "Copy" }}
|
iconProps={{ iconName: "Copy" }}
|
||||||
style={{ fontSize: 12, transition: "background-color 0.3s ease", height: "100%" }}
|
style={{ fontSize: 12, transition: "background-color 0.3s ease", height: "100%" }}
|
||||||
@@ -706,6 +711,9 @@ export const QueryCopilotPromptbar: React.FC<QueryCopilotPromptProps> = ({
|
|||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
|
{(showFeedbackBar || isGeneratingQuery) && (
|
||||||
|
<span role="alert" className="screenReaderOnly" aria-label={getAriaLabel()} />
|
||||||
|
)}
|
||||||
{isGeneratingQuery && (
|
{isGeneratingQuery && (
|
||||||
<ProgressIndicator
|
<ProgressIndicator
|
||||||
label="Thinking..."
|
label="Thinking..."
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { FeedOptions } from "@azure/cosmos";
|
import { FeedOptions } from "@azure/cosmos";
|
||||||
import {
|
import {
|
||||||
Areas,
|
Areas,
|
||||||
BackendApi,
|
|
||||||
ConnectionStatusType,
|
ConnectionStatusType,
|
||||||
ContainerStatusType,
|
ContainerStatusType,
|
||||||
HttpStatusCodes,
|
HttpStatusCodes,
|
||||||
@@ -32,7 +31,6 @@ import { Action } from "Shared/Telemetry/TelemetryConstants";
|
|||||||
import { traceFailure, traceStart, traceSuccess } from "Shared/Telemetry/TelemetryProcessor";
|
import { traceFailure, traceStart, traceSuccess } from "Shared/Telemetry/TelemetryProcessor";
|
||||||
import { userContext } from "UserContext";
|
import { userContext } from "UserContext";
|
||||||
import { getAuthorizationHeader } from "Utils/AuthorizationUtils";
|
import { getAuthorizationHeader } from "Utils/AuthorizationUtils";
|
||||||
import { useNewPortalBackendEndpoint } from "Utils/EndpointUtils";
|
|
||||||
import { queryPagesUntilContentPresent } from "Utils/QueryUtils";
|
import { queryPagesUntilContentPresent } from "Utils/QueryUtils";
|
||||||
import { QueryCopilotState, useQueryCopilot } from "hooks/useQueryCopilot";
|
import { QueryCopilotState, useQueryCopilot } from "hooks/useQueryCopilot";
|
||||||
import { useTabs } from "hooks/useTabs";
|
import { useTabs } from "hooks/useTabs";
|
||||||
@@ -82,9 +80,7 @@ export const isCopilotFeatureRegistered = async (subscriptionId: string): Promis
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const getCopilotEnabled = async (): Promise<boolean> => {
|
export const getCopilotEnabled = async (): Promise<boolean> => {
|
||||||
const backendEndpoint: string = useNewPortalBackendEndpoint(BackendApi.PortalSettings)
|
const backendEndpoint: string = configContext.PORTAL_BACKEND_ENDPOINT;
|
||||||
? configContext.PORTAL_BACKEND_ENDPOINT
|
|
||||||
: configContext.BACKEND_ENDPOINT;
|
|
||||||
|
|
||||||
const url = `${backendEndpoint}/api/portalsettings/querycopilot`;
|
const url = `${backendEndpoint}/api/portalsettings/querycopilot`;
|
||||||
const authorizationHeader: AuthorizationTokenHeaderMetadata = getAuthorizationHeader();
|
const authorizationHeader: AuthorizationTokenHeaderMetadata = getAuthorizationHeader();
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
|
makeStyles,
|
||||||
Menu,
|
Menu,
|
||||||
MenuButton,
|
MenuButton,
|
||||||
MenuButtonProps,
|
MenuButtonProps,
|
||||||
@@ -7,13 +8,12 @@ import {
|
|||||||
MenuList,
|
MenuList,
|
||||||
MenuPopover,
|
MenuPopover,
|
||||||
MenuTrigger,
|
MenuTrigger,
|
||||||
SplitButton,
|
|
||||||
makeStyles,
|
|
||||||
mergeClasses,
|
mergeClasses,
|
||||||
shorthands,
|
shorthands,
|
||||||
|
SplitButton,
|
||||||
} from "@fluentui/react-components";
|
} from "@fluentui/react-components";
|
||||||
import { Add16Regular, ArrowSync12Regular, ChevronLeft12Regular, ChevronRight12Regular } from "@fluentui/react-icons";
|
import { Add16Regular, ArrowSync12Regular, ChevronLeft12Regular, ChevronRight12Regular } from "@fluentui/react-icons";
|
||||||
import { Platform, configContext } from "ConfigContext";
|
import { configContext, Platform } from "ConfigContext";
|
||||||
import Explorer from "Explorer/Explorer";
|
import Explorer from "Explorer/Explorer";
|
||||||
import { AddDatabasePanel } from "Explorer/Panes/AddDatabasePanel/AddDatabasePanel";
|
import { AddDatabasePanel } from "Explorer/Panes/AddDatabasePanel/AddDatabasePanel";
|
||||||
import { Tabs } from "Explorer/Tabs/Tabs";
|
import { Tabs } from "Explorer/Tabs/Tabs";
|
||||||
@@ -21,12 +21,13 @@ import { CosmosFluentProvider, cosmosShorthands, tokens } from "Explorer/Theme/T
|
|||||||
import { ResourceTree } from "Explorer/Tree/ResourceTree";
|
import { ResourceTree } from "Explorer/Tree/ResourceTree";
|
||||||
import { useDatabases } from "Explorer/useDatabases";
|
import { useDatabases } from "Explorer/useDatabases";
|
||||||
import { KeyboardAction, KeyboardActionGroup, KeyboardActionHandler, useKeyboardActionGroup } from "KeyboardShortcuts";
|
import { KeyboardAction, KeyboardActionGroup, KeyboardActionHandler, useKeyboardActionGroup } from "KeyboardShortcuts";
|
||||||
|
import { isFabric, isFabricMirrored, isFabricNative } from "Platform/Fabric/FabricUtil";
|
||||||
import { userContext } from "UserContext";
|
import { userContext } from "UserContext";
|
||||||
import { getCollectionName, getDatabaseName } from "Utils/APITypeUtils";
|
import { getCollectionName, getDatabaseName } from "Utils/APITypeUtils";
|
||||||
import { Allotment, AllotmentHandle } from "allotment";
|
import { Allotment, AllotmentHandle } from "allotment";
|
||||||
import { useSidePanel } from "hooks/useSidePanel";
|
import { useSidePanel } from "hooks/useSidePanel";
|
||||||
import { debounce } from "lodash";
|
import { debounce } from "lodash";
|
||||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
||||||
|
|
||||||
const useSidebarStyles = makeStyles({
|
const useSidebarStyles = makeStyles({
|
||||||
sidebar: {
|
sidebar: {
|
||||||
@@ -109,6 +110,7 @@ interface GlobalCommand {
|
|||||||
icon: JSX.Element;
|
icon: JSX.Element;
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
keyboardAction?: KeyboardAction;
|
keyboardAction?: KeyboardAction;
|
||||||
|
ref?: React.RefObject<HTMLButtonElement>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const GlobalCommands: React.FC<GlobalCommandsProps> = ({ explorer }) => {
|
const GlobalCommands: React.FC<GlobalCommandsProps> = ({ explorer }) => {
|
||||||
@@ -118,10 +120,11 @@ const GlobalCommands: React.FC<GlobalCommandsProps> = ({ explorer }) => {
|
|||||||
// However, that messes with the Menu positioning, so we need to get a reference to the 'div' to pass to the Menu.
|
// However, that messes with the Menu positioning, so we need to get a reference to the 'div' to pass to the Menu.
|
||||||
// We can't use a ref though, because it would be set after the Menu is rendered, so we use a state value to force a re-render.
|
// We can't use a ref though, because it would be set after the Menu is rendered, so we use a state value to force a re-render.
|
||||||
const [globalCommandButton, setGlobalCommandButton] = useState<HTMLElement | null>(null);
|
const [globalCommandButton, setGlobalCommandButton] = useState<HTMLElement | null>(null);
|
||||||
|
const primaryFocusableRef = useRef<HTMLButtonElement>(null);
|
||||||
|
|
||||||
const actions = useMemo<GlobalCommand[]>(() => {
|
const actions = useMemo<GlobalCommand[]>(() => {
|
||||||
if (
|
if (
|
||||||
configContext.platform === Platform.Fabric ||
|
(isFabric() && userContext.fabricContext?.isReadOnly) ||
|
||||||
userContext.apiType === "Postgres" ||
|
userContext.apiType === "Postgres" ||
|
||||||
userContext.apiType === "VCoreMongo"
|
userContext.apiType === "VCoreMongo"
|
||||||
) {
|
) {
|
||||||
@@ -135,12 +138,15 @@ const GlobalCommands: React.FC<GlobalCommandsProps> = ({ explorer }) => {
|
|||||||
id: "new_collection",
|
id: "new_collection",
|
||||||
label: `New ${getCollectionName()}`,
|
label: `New ${getCollectionName()}`,
|
||||||
icon: <Add16Regular />,
|
icon: <Add16Regular />,
|
||||||
onClick: () => explorer.onNewCollectionClicked(),
|
onClick: () => {
|
||||||
|
const databaseId = isFabricNative() ? userContext.fabricContext?.databaseName : undefined;
|
||||||
|
explorer.onNewCollectionClicked({ databaseId });
|
||||||
|
},
|
||||||
keyboardAction: KeyboardAction.NEW_COLLECTION,
|
keyboardAction: KeyboardAction.NEW_COLLECTION,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
if (userContext.apiType !== "Tables") {
|
if (configContext.platform !== Platform.Fabric && userContext.apiType !== "Tables") {
|
||||||
actions.push({
|
actions.push({
|
||||||
id: "new_database",
|
id: "new_database",
|
||||||
label: `New ${getDatabaseName()}`,
|
label: `New ${getDatabaseName()}`,
|
||||||
@@ -177,6 +183,16 @@ const GlobalCommands: React.FC<GlobalCommandsProps> = ({ explorer }) => {
|
|||||||
);
|
);
|
||||||
}, [actions, setKeyboardActions]);
|
}, [actions, setKeyboardActions]);
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
if (primaryFocusableRef.current) {
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
primaryFocusableRef.current.focus();
|
||||||
|
}, 0);
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}, []);
|
||||||
|
|
||||||
if (!primaryAction) {
|
if (!primaryAction) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -184,7 +200,7 @@ const GlobalCommands: React.FC<GlobalCommandsProps> = ({ explorer }) => {
|
|||||||
return (
|
return (
|
||||||
<div className={styles.globalCommandsContainer} data-test="GlobalCommands">
|
<div className={styles.globalCommandsContainer} data-test="GlobalCommands">
|
||||||
{actions.length === 1 ? (
|
{actions.length === 1 ? (
|
||||||
<Button icon={primaryAction.icon} onClick={onPrimaryActionClick}>
|
<Button icon={primaryAction.icon} onClick={onPrimaryActionClick} ref={primaryFocusableRef}>
|
||||||
{primaryAction.label}
|
{primaryAction.label}
|
||||||
</Button>
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
@@ -194,7 +210,7 @@ const GlobalCommands: React.FC<GlobalCommandsProps> = ({ explorer }) => {
|
|||||||
<div ref={setGlobalCommandButton}>
|
<div ref={setGlobalCommandButton}>
|
||||||
<SplitButton
|
<SplitButton
|
||||||
menuButton={{ ...triggerProps, "aria-label": "More commands" }}
|
menuButton={{ ...triggerProps, "aria-label": "More commands" }}
|
||||||
primaryActionButton={{ onClick: onPrimaryActionClick }}
|
primaryActionButton={{ onClick: onPrimaryActionClick, ref: primaryFocusableRef }}
|
||||||
className={styles.globalCommandsSplitButton}
|
className={styles.globalCommandsSplitButton}
|
||||||
icon={primaryAction.icon}
|
icon={primaryAction.icon}
|
||||||
>
|
>
|
||||||
@@ -276,7 +292,7 @@ export const SidebarContainer: React.FC<SidebarProps> = ({ explorer }) => {
|
|||||||
}, [setLoading]);
|
}, [setLoading]);
|
||||||
|
|
||||||
const hasGlobalCommands = !(
|
const hasGlobalCommands = !(
|
||||||
configContext.platform === Platform.Fabric ||
|
isFabricMirrored() ||
|
||||||
userContext.apiType === "Postgres" ||
|
userContext.apiType === "Postgres" ||
|
||||||
userContext.apiType === "VCoreMongo"
|
userContext.apiType === "VCoreMongo"
|
||||||
);
|
);
|
||||||
|
|||||||
173
src/Explorer/SplashScreen/FabricHome.tsx
Normal file
173
src/Explorer/SplashScreen/FabricHome.tsx
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
/**
|
||||||
|
* Accordion top class
|
||||||
|
*/
|
||||||
|
import { Link, makeStyles, tokens } from "@fluentui/react-components";
|
||||||
|
import { DocumentAddRegular, LinkMultipleRegular } from "@fluentui/react-icons";
|
||||||
|
import { isFabricNative } from "Platform/Fabric/FabricUtil";
|
||||||
|
import * as React from "react";
|
||||||
|
import { userContext } from "UserContext";
|
||||||
|
import CosmosDbBlackIcon from "../../../images/CosmosDB_black.svg";
|
||||||
|
import LinkIcon from "../../../images/Link_blue.svg";
|
||||||
|
import Explorer from "../Explorer";
|
||||||
|
|
||||||
|
export interface SplashScreenProps {
|
||||||
|
explorer: Explorer;
|
||||||
|
}
|
||||||
|
|
||||||
|
const useStyles = makeStyles({
|
||||||
|
homeContainer: {
|
||||||
|
width: "100%",
|
||||||
|
alignContent: "center",
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
textAlign: "center",
|
||||||
|
fontSize: "20px",
|
||||||
|
fontWeight: "bold",
|
||||||
|
},
|
||||||
|
buttonsContainer: {
|
||||||
|
width: "584px",
|
||||||
|
margin: "auto",
|
||||||
|
display: "grid",
|
||||||
|
padding: "16px",
|
||||||
|
gridTemplateColumns: "repeat(3, 1fr)",
|
||||||
|
gap: "10px",
|
||||||
|
gridAutoRows: "minmax(184px, auto)",
|
||||||
|
},
|
||||||
|
one: {
|
||||||
|
gridColumn: "1 / 3",
|
||||||
|
gridRow: "1 / 3",
|
||||||
|
"& svg": {
|
||||||
|
width: "48px",
|
||||||
|
height: "48px",
|
||||||
|
margin: "auto",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
two: {
|
||||||
|
gridColumn: "3",
|
||||||
|
gridRow: "1",
|
||||||
|
"& img": {
|
||||||
|
width: "32px",
|
||||||
|
height: "32px",
|
||||||
|
margin: "auto",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
three: {
|
||||||
|
gridColumn: "3",
|
||||||
|
gridRow: "2",
|
||||||
|
"& svg": {
|
||||||
|
width: "32px",
|
||||||
|
height: "32px",
|
||||||
|
margin: "auto",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
buttonContainer: {
|
||||||
|
height: "100%",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
border: "1px solid #e0e0e0",
|
||||||
|
cursor: "pointer",
|
||||||
|
"&:hover": {
|
||||||
|
backgroundColor: tokens.colorNeutralBackground1Hover,
|
||||||
|
"border-color": tokens.colorNeutralStroke1Hover,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
buttonUpperPart: {
|
||||||
|
textAlign: "center",
|
||||||
|
flexGrow: 1,
|
||||||
|
display: "flex",
|
||||||
|
backgroundColor: "#e3f7ef",
|
||||||
|
},
|
||||||
|
buttonLowerPart: {
|
||||||
|
borderTop: "1px solid #e0e0e0",
|
||||||
|
height: "76px",
|
||||||
|
padding: "8px",
|
||||||
|
"> div:nth-child(1)": {
|
||||||
|
fontWeight: "bold",
|
||||||
|
},
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
justifyContent: "center",
|
||||||
|
},
|
||||||
|
footer: {
|
||||||
|
textAlign: "center",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
interface FabricHomeScreenButtonProps {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
icon: JSX.Element;
|
||||||
|
onClick?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FabricHomeScreenButton: React.FC<FabricHomeScreenButtonProps & { className: string }> = ({
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
icon,
|
||||||
|
className,
|
||||||
|
onClick,
|
||||||
|
}) => {
|
||||||
|
const styles = useStyles();
|
||||||
|
|
||||||
|
// TODO Make this a11y copmliant: aria-label for icon
|
||||||
|
return (
|
||||||
|
<div role="button" className={`${styles.buttonContainer} ${className}`} onClick={onClick}>
|
||||||
|
<div className={styles.buttonUpperPart}>{icon}</div>
|
||||||
|
<div className={styles.buttonLowerPart}>
|
||||||
|
<div>{title}</div>
|
||||||
|
<div>{description}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FabricHomeScreen: React.FC<SplashScreenProps> = (props: SplashScreenProps) => {
|
||||||
|
const styles = useStyles();
|
||||||
|
const getSplashScreenButtons = (): JSX.Element => {
|
||||||
|
const buttons: FabricHomeScreenButtonProps[] = [
|
||||||
|
{
|
||||||
|
title: "New container",
|
||||||
|
description: "Create a destination container to store your data",
|
||||||
|
icon: <DocumentAddRegular />,
|
||||||
|
onClick: () => {
|
||||||
|
const databaseId = isFabricNative() ? userContext.fabricContext?.databaseName : undefined;
|
||||||
|
props.explorer.onNewCollectionClicked({ databaseId });
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Sample data",
|
||||||
|
description: "Automatically load sample data in your database",
|
||||||
|
icon: <img src={CosmosDbBlackIcon} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "App development",
|
||||||
|
description: "Start here to use an SDK to build your apps",
|
||||||
|
icon: <LinkMultipleRegular />,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.buttonsContainer}>
|
||||||
|
<FabricHomeScreenButton className={styles.one} {...buttons[0]} />
|
||||||
|
<FabricHomeScreenButton className={styles.two} {...buttons[1]} />
|
||||||
|
<FabricHomeScreenButton className={styles.three} {...buttons[2]} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const title = "Build your database";
|
||||||
|
return (
|
||||||
|
<div className={styles.homeContainer}>
|
||||||
|
<div className={styles.title} role="heading" aria-label={title}>
|
||||||
|
{title}
|
||||||
|
</div>
|
||||||
|
{getSplashScreenButtons()}
|
||||||
|
<div className={styles.footer}>
|
||||||
|
Need help?{" "}
|
||||||
|
<Link href="https://cosmos.azure.com/docs" target="_blank">
|
||||||
|
Learn more <img src={LinkIcon} alt="Learn more" />
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -39,7 +39,7 @@ export const SplashScreenButton: React.FC<SplashScreenButtonProps> = ({
|
|||||||
role="button"
|
role="button"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<img src={imgSrc} />
|
<img src={imgSrc} alt={title} aria-hidden="true" />
|
||||||
</div>
|
</div>
|
||||||
<Stack style={{ marginLeft: 16 }}>
|
<Stack style={{ marginLeft: 16 }}>
|
||||||
<Text style={{ fontSize: 18, fontWeight: 600 }}>{title}</Text>
|
<Text style={{ fontSize: 18, fontWeight: 600 }}>{title}</Text>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import * as ko from "knockout";
|
|||||||
import Q from "q";
|
import Q from "q";
|
||||||
import { AuthType } from "../../AuthType";
|
import { AuthType } from "../../AuthType";
|
||||||
import * as Constants from "../../Common/Constants";
|
import * as Constants from "../../Common/Constants";
|
||||||
import { CassandraProxyAPIs, CassandraProxyEndpoints } from "../../Common/Constants";
|
import { CassandraProxyAPIs } from "../../Common/Constants";
|
||||||
import { handleError } from "../../Common/ErrorHandlingUtils";
|
import { handleError } from "../../Common/ErrorHandlingUtils";
|
||||||
import * as HeadersUtility from "../../Common/HeadersUtility";
|
import * as HeadersUtility from "../../Common/HeadersUtility";
|
||||||
import { createDocument } from "../../Common/dataAccess/createDocument";
|
import { createDocument } from "../../Common/dataAccess/createDocument";
|
||||||
@@ -264,9 +264,6 @@ export class CassandraAPIDataClient extends TableDataClient {
|
|||||||
shouldNotify?: boolean,
|
shouldNotify?: boolean,
|
||||||
paginationToken?: string,
|
paginationToken?: string,
|
||||||
): Promise<Entities.IListTableEntitiesResult> {
|
): Promise<Entities.IListTableEntitiesResult> {
|
||||||
if (!this.useCassandraProxyEndpoint("postQuery")) {
|
|
||||||
return this.queryDocuments_ToBeDeprecated(collection, query, shouldNotify, paginationToken);
|
|
||||||
}
|
|
||||||
const clearMessage =
|
const clearMessage =
|
||||||
shouldNotify && NotificationConsoleUtils.logConsoleProgress(`Querying rows for table ${collection.id()}`);
|
shouldNotify && NotificationConsoleUtils.logConsoleProgress(`Querying rows for table ${collection.id()}`);
|
||||||
try {
|
try {
|
||||||
@@ -309,55 +306,6 @@ export class CassandraAPIDataClient extends TableDataClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async queryDocuments_ToBeDeprecated(
|
|
||||||
collection: ViewModels.Collection,
|
|
||||||
query: string,
|
|
||||||
shouldNotify?: boolean,
|
|
||||||
paginationToken?: string,
|
|
||||||
): Promise<Entities.IListTableEntitiesResult> {
|
|
||||||
const clearMessage =
|
|
||||||
shouldNotify && NotificationConsoleUtils.logConsoleProgress(`Querying rows for table ${collection.id()}`);
|
|
||||||
try {
|
|
||||||
const { authType, databaseAccount } = userContext;
|
|
||||||
const apiEndpoint: string =
|
|
||||||
authType === AuthType.EncryptedToken
|
|
||||||
? Constants.CassandraBackend.guestQueryApi
|
|
||||||
: Constants.CassandraBackend.queryApi;
|
|
||||||
const data: any = await $.ajax(`${configContext.BACKEND_ENDPOINT}/${apiEndpoint}`, {
|
|
||||||
type: "POST",
|
|
||||||
data: {
|
|
||||||
accountName: databaseAccount?.name,
|
|
||||||
cassandraEndpoint: this.trimCassandraEndpoint(databaseAccount?.properties.cassandraEndpoint),
|
|
||||||
resourceId: databaseAccount?.id,
|
|
||||||
keyspaceId: collection.databaseId,
|
|
||||||
tableId: collection.id(),
|
|
||||||
query,
|
|
||||||
paginationToken,
|
|
||||||
},
|
|
||||||
beforeSend: this.setAuthorizationHeader as any,
|
|
||||||
cache: false,
|
|
||||||
});
|
|
||||||
shouldNotify &&
|
|
||||||
NotificationConsoleUtils.logConsoleInfo(
|
|
||||||
`Successfully fetched ${data.result.length} rows for table ${collection.id()}`,
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
Results: data.result,
|
|
||||||
ContinuationToken: data.paginationToken,
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
shouldNotify &&
|
|
||||||
handleError(
|
|
||||||
error,
|
|
||||||
"QueryDocuments_ToBeDeprecated_Cassandra",
|
|
||||||
`Failed to query rows for table ${collection.id()}`,
|
|
||||||
);
|
|
||||||
throw error;
|
|
||||||
} finally {
|
|
||||||
clearMessage?.();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async deleteDocuments(
|
public async deleteDocuments(
|
||||||
collection: ViewModels.Collection,
|
collection: ViewModels.Collection,
|
||||||
entitiesToDelete: Entities.ITableEntity[],
|
entitiesToDelete: Entities.ITableEntity[],
|
||||||
@@ -471,10 +419,6 @@ export class CassandraAPIDataClient extends TableDataClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public getTableKeys(collection: ViewModels.Collection): Q.Promise<CassandraTableKeys> {
|
public getTableKeys(collection: ViewModels.Collection): Q.Promise<CassandraTableKeys> {
|
||||||
if (!this.useCassandraProxyEndpoint("getKeys")) {
|
|
||||||
return this.getTableKeys_ToBeDeprecated(collection);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!!collection.cassandraKeys) {
|
if (!!collection.cassandraKeys) {
|
||||||
return Q.resolve(collection.cassandraKeys);
|
return Q.resolve(collection.cassandraKeys);
|
||||||
}
|
}
|
||||||
@@ -515,52 +459,7 @@ export class CassandraAPIDataClient extends TableDataClient {
|
|||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getTableKeys_ToBeDeprecated(collection: ViewModels.Collection): Q.Promise<CassandraTableKeys> {
|
|
||||||
if (!!collection.cassandraKeys) {
|
|
||||||
return Q.resolve(collection.cassandraKeys);
|
|
||||||
}
|
|
||||||
const clearInProgressMessage = logConsoleProgress(`Fetching keys for table ${collection.id()}`);
|
|
||||||
const { authType, databaseAccount } = userContext;
|
|
||||||
const apiEndpoint: string =
|
|
||||||
authType === AuthType.EncryptedToken
|
|
||||||
? Constants.CassandraBackend.guestKeysApi
|
|
||||||
: Constants.CassandraBackend.keysApi;
|
|
||||||
let endpoint = `${configContext.BACKEND_ENDPOINT}/${apiEndpoint}`;
|
|
||||||
const deferred = Q.defer<CassandraTableKeys>();
|
|
||||||
|
|
||||||
$.ajax(endpoint, {
|
|
||||||
type: "POST",
|
|
||||||
data: {
|
|
||||||
accountName: databaseAccount?.name,
|
|
||||||
cassandraEndpoint: this.trimCassandraEndpoint(databaseAccount?.properties.cassandraEndpoint),
|
|
||||||
resourceId: databaseAccount?.id,
|
|
||||||
keyspaceId: collection.databaseId,
|
|
||||||
tableId: collection.id(),
|
|
||||||
},
|
|
||||||
beforeSend: this.setAuthorizationHeader as any,
|
|
||||||
cache: false,
|
|
||||||
})
|
|
||||||
.then(
|
|
||||||
(data: CassandraTableKeys) => {
|
|
||||||
collection.cassandraKeys = data;
|
|
||||||
logConsoleInfo(`Successfully fetched keys for table ${collection.id()}`);
|
|
||||||
deferred.resolve(data);
|
|
||||||
},
|
|
||||||
(error: any) => {
|
|
||||||
const errorText = error.responseJSON?.message ?? JSON.stringify(error);
|
|
||||||
handleError(errorText, "FetchKeysCassandra", `Error fetching keys for table ${collection.id()}`);
|
|
||||||
deferred.reject(errorText);
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.done(clearInProgressMessage);
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getTableSchema(collection: ViewModels.Collection): Q.Promise<CassandraTableKey[]> {
|
public getTableSchema(collection: ViewModels.Collection): Q.Promise<CassandraTableKey[]> {
|
||||||
if (!this.useCassandraProxyEndpoint("getSchema")) {
|
|
||||||
return this.getTableSchema_ToBeDeprecated(collection);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!!collection.cassandraSchema) {
|
if (!!collection.cassandraSchema) {
|
||||||
return Q.resolve(collection.cassandraSchema);
|
return Q.resolve(collection.cassandraSchema);
|
||||||
}
|
}
|
||||||
@@ -602,52 +501,7 @@ export class CassandraAPIDataClient extends TableDataClient {
|
|||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getTableSchema_ToBeDeprecated(collection: ViewModels.Collection): Q.Promise<CassandraTableKey[]> {
|
|
||||||
if (!!collection.cassandraSchema) {
|
|
||||||
return Q.resolve(collection.cassandraSchema);
|
|
||||||
}
|
|
||||||
const clearInProgressMessage = logConsoleProgress(`Fetching schema for table ${collection.id()}`);
|
|
||||||
const { databaseAccount, authType } = userContext;
|
|
||||||
const apiEndpoint: string =
|
|
||||||
authType === AuthType.EncryptedToken
|
|
||||||
? Constants.CassandraBackend.guestSchemaApi
|
|
||||||
: Constants.CassandraBackend.schemaApi;
|
|
||||||
let endpoint = `${configContext.BACKEND_ENDPOINT}/${apiEndpoint}`;
|
|
||||||
const deferred = Q.defer<CassandraTableKey[]>();
|
|
||||||
|
|
||||||
$.ajax(endpoint, {
|
|
||||||
type: "POST",
|
|
||||||
data: {
|
|
||||||
accountName: databaseAccount?.name,
|
|
||||||
cassandraEndpoint: this.trimCassandraEndpoint(databaseAccount?.properties.cassandraEndpoint),
|
|
||||||
resourceId: databaseAccount?.id,
|
|
||||||
keyspaceId: collection.databaseId,
|
|
||||||
tableId: collection.id(),
|
|
||||||
},
|
|
||||||
beforeSend: this.setAuthorizationHeader as any,
|
|
||||||
cache: false,
|
|
||||||
})
|
|
||||||
.then(
|
|
||||||
(data: any) => {
|
|
||||||
collection.cassandraSchema = data.columns;
|
|
||||||
logConsoleInfo(`Successfully fetched schema for table ${collection.id()}`);
|
|
||||||
deferred.resolve(data.columns);
|
|
||||||
},
|
|
||||||
(error: any) => {
|
|
||||||
const errorText = error.responseJSON?.message ?? JSON.stringify(error);
|
|
||||||
handleError(errorText, "FetchSchemaCassandra", `Error fetching schema for table ${collection.id()}`);
|
|
||||||
deferred.reject(errorText);
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.done(clearInProgressMessage);
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
private createOrDeleteQuery(cassandraEndpoint: string, resourceId: string, query: string): Q.Promise<any> {
|
private createOrDeleteQuery(cassandraEndpoint: string, resourceId: string, query: string): Q.Promise<any> {
|
||||||
if (!this.useCassandraProxyEndpoint("createOrDelete")) {
|
|
||||||
return this.createOrDeleteQuery_ToBeDeprecated(cassandraEndpoint, resourceId, query);
|
|
||||||
}
|
|
||||||
|
|
||||||
const deferred = Q.defer();
|
const deferred = Q.defer();
|
||||||
const { authType, databaseAccount } = userContext;
|
const { authType, databaseAccount } = userContext;
|
||||||
const apiEndpoint: string =
|
const apiEndpoint: string =
|
||||||
@@ -677,38 +531,6 @@ export class CassandraAPIDataClient extends TableDataClient {
|
|||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
private createOrDeleteQuery_ToBeDeprecated(
|
|
||||||
cassandraEndpoint: string,
|
|
||||||
resourceId: string,
|
|
||||||
query: string,
|
|
||||||
): Q.Promise<any> {
|
|
||||||
const deferred = Q.defer();
|
|
||||||
const { authType, databaseAccount } = userContext;
|
|
||||||
const apiEndpoint: string =
|
|
||||||
authType === AuthType.EncryptedToken
|
|
||||||
? Constants.CassandraBackend.guestCreateOrDeleteApi
|
|
||||||
: Constants.CassandraBackend.createOrDeleteApi;
|
|
||||||
$.ajax(`${configContext.BACKEND_ENDPOINT}/${apiEndpoint}`, {
|
|
||||||
type: "POST",
|
|
||||||
data: {
|
|
||||||
accountName: databaseAccount?.name,
|
|
||||||
cassandraEndpoint: this.trimCassandraEndpoint(cassandraEndpoint),
|
|
||||||
resourceId: resourceId,
|
|
||||||
query: query,
|
|
||||||
},
|
|
||||||
beforeSend: this.setAuthorizationHeader as any,
|
|
||||||
cache: false,
|
|
||||||
}).then(
|
|
||||||
(data: any) => {
|
|
||||||
deferred.resolve();
|
|
||||||
},
|
|
||||||
(reason) => {
|
|
||||||
deferred.reject(reason);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
private trimCassandraEndpoint(cassandraEndpoint: string): string {
|
private trimCassandraEndpoint(cassandraEndpoint: string): string {
|
||||||
if (!cassandraEndpoint) {
|
if (!cassandraEndpoint) {
|
||||||
return cassandraEndpoint;
|
return cassandraEndpoint;
|
||||||
@@ -747,23 +569,4 @@ export class CassandraAPIDataClient extends TableDataClient {
|
|||||||
private getCassandraPartitionKeyProperty(collection: ViewModels.Collection): string {
|
private getCassandraPartitionKeyProperty(collection: ViewModels.Collection): string {
|
||||||
return collection.cassandraKeys.partitionKeys[0].property;
|
return collection.cassandraKeys.partitionKeys[0].property;
|
||||||
}
|
}
|
||||||
|
|
||||||
private useCassandraProxyEndpoint(api: string): boolean {
|
|
||||||
const activeCassandraProxyEndpoints: string[] = [
|
|
||||||
CassandraProxyEndpoints.Development,
|
|
||||||
CassandraProxyEndpoints.Mpac,
|
|
||||||
CassandraProxyEndpoints.Prod,
|
|
||||||
CassandraProxyEndpoints.Fairfax,
|
|
||||||
CassandraProxyEndpoints.Mooncake,
|
|
||||||
];
|
|
||||||
|
|
||||||
if (configContext.globallyEnabledCassandraAPIs.includes(api)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
configContext.NEW_CASSANDRA_APIS?.includes(api) &&
|
|
||||||
activeCassandraProxyEndpoints.includes(configContext.CASSANDRA_PROXY_ENDPOINT)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { FeedResponse, ItemDefinition, Resource } from "@azure/cosmos";
|
|||||||
import { waitFor } from "@testing-library/react";
|
import { waitFor } from "@testing-library/react";
|
||||||
import { deleteDocuments } from "Common/dataAccess/deleteDocument";
|
import { deleteDocuments } from "Common/dataAccess/deleteDocument";
|
||||||
import { Platform, updateConfigContext } from "ConfigContext";
|
import { Platform, updateConfigContext } from "ConfigContext";
|
||||||
|
import { CosmosDbArtifactType } from "Contracts/FabricMessagesContract";
|
||||||
import { useDialog } from "Explorer/Controls/Dialog";
|
import { useDialog } from "Explorer/Controls/Dialog";
|
||||||
import { EditorReactProps } from "Explorer/Controls/Editor/EditorReact";
|
import { EditorReactProps } from "Explorer/Controls/Editor/EditorReact";
|
||||||
import { ProgressModalDialog } from "Explorer/Controls/ProgressModalDialog";
|
import { ProgressModalDialog } from "Explorer/Controls/ProgressModalDialog";
|
||||||
@@ -341,10 +342,15 @@ describe("Documents tab (noSql API)", () => {
|
|||||||
updateConfigContext({ platform: Platform.Fabric });
|
updateConfigContext({ platform: Platform.Fabric });
|
||||||
updateUserContext({
|
updateUserContext({
|
||||||
fabricContext: {
|
fabricContext: {
|
||||||
connectionId: "test",
|
databaseName: "database",
|
||||||
databaseConnectionInfo: undefined,
|
artifactInfo: {
|
||||||
|
connectionId: "test",
|
||||||
|
resourceTokenInfo: undefined,
|
||||||
|
},
|
||||||
|
artifactType: CosmosDbArtifactType.MIRRORED_KEY,
|
||||||
isReadOnly: true,
|
isReadOnly: true,
|
||||||
isVisible: true,
|
isVisible: true,
|
||||||
|
fabricClientRpcVersion: "rpcVersion",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import {
|
|||||||
import { queryDocuments } from "Common/dataAccess/queryDocuments";
|
import { queryDocuments } from "Common/dataAccess/queryDocuments";
|
||||||
import { readDocument } from "Common/dataAccess/readDocument";
|
import { readDocument } from "Common/dataAccess/readDocument";
|
||||||
import { updateDocument } from "Common/dataAccess/updateDocument";
|
import { updateDocument } from "Common/dataAccess/updateDocument";
|
||||||
import { Platform, configContext } from "ConfigContext";
|
|
||||||
import { ActionType, OpenCollectionTab, TabKind } from "Contracts/ActionContracts";
|
import { ActionType, OpenCollectionTab, TabKind } from "Contracts/ActionContracts";
|
||||||
import { CommandButtonComponentProps } from "Explorer/Controls/CommandButton/CommandButtonComponent";
|
import { CommandButtonComponentProps } from "Explorer/Controls/CommandButton/CommandButtonComponent";
|
||||||
import { useDialog } from "Explorer/Controls/Dialog";
|
import { useDialog } from "Explorer/Controls/Dialog";
|
||||||
@@ -43,6 +42,7 @@ import { usePrevious } from "Explorer/Tabs/DocumentsTabV2/SelectionHelper";
|
|||||||
import { CosmosFluentProvider, LayoutConstants, cosmosShorthands, tokens } from "Explorer/Theme/ThemeUtil";
|
import { CosmosFluentProvider, LayoutConstants, cosmosShorthands, tokens } from "Explorer/Theme/ThemeUtil";
|
||||||
import { useSelectedNode } from "Explorer/useSelectedNode";
|
import { useSelectedNode } from "Explorer/useSelectedNode";
|
||||||
import { KeyboardAction, KeyboardActionGroup, useKeyboardActionGroup } from "KeyboardShortcuts";
|
import { KeyboardAction, KeyboardActionGroup, useKeyboardActionGroup } from "KeyboardShortcuts";
|
||||||
|
import { isFabric } from "Platform/Fabric/FabricUtil";
|
||||||
import { QueryConstants } from "Shared/Constants";
|
import { QueryConstants } from "Shared/Constants";
|
||||||
import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
|
import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
|
||||||
import { Action } from "Shared/Telemetry/TelemetryConstants";
|
import { Action } from "Shared/Telemetry/TelemetryConstants";
|
||||||
@@ -344,7 +344,7 @@ export const getTabsButtons = ({
|
|||||||
onRevertExistingDocumentClick,
|
onRevertExistingDocumentClick,
|
||||||
onDeleteExistingDocumentsClick,
|
onDeleteExistingDocumentsClick,
|
||||||
}: ButtonsDependencies): CommandButtonComponentProps[] => {
|
}: ButtonsDependencies): CommandButtonComponentProps[] => {
|
||||||
if (configContext.platform === Platform.Fabric && userContext.fabricContext?.isReadOnly) {
|
if (isFabric() && userContext.fabricContext?.isReadOnly) {
|
||||||
// All the following buttons require write access
|
// All the following buttons require write access
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@@ -1028,6 +1028,7 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
|
|||||||
);
|
);
|
||||||
|
|
||||||
const selectedDocumentId = documentIds[clickedRowIndex as number];
|
const selectedDocumentId = documentIds[clickedRowIndex as number];
|
||||||
|
const originalPartitionKeyValue = selectedDocumentId.partitionKeyValue;
|
||||||
selectedDocumentId.partitionKeyValue = partitionKeyValueArray;
|
selectedDocumentId.partitionKeyValue = partitionKeyValueArray;
|
||||||
|
|
||||||
onExecutionErrorChange(false);
|
onExecutionErrorChange(false);
|
||||||
@@ -1063,9 +1064,14 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
|
|||||||
setColumnDefinitionsFromDocument(documentContent);
|
setColumnDefinitionsFromDocument(documentContent);
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
|
// in case of any kind of failures of accidently changing partition key, restore the original
|
||||||
|
// so that when user navigates away from current document and comes back,
|
||||||
|
// it doesnt fail to load due to using the invalid partition keys
|
||||||
|
selectedDocumentId.partitionKeyValue = originalPartitionKeyValue;
|
||||||
onExecutionErrorChange(true);
|
onExecutionErrorChange(true);
|
||||||
const errorMessage = getErrorMessage(error);
|
const errorMessage = getErrorMessage(error);
|
||||||
useDialog.getState().showOkModalDialog("Update document failed", errorMessage);
|
useDialog.getState().showOkModalDialog("Update document failed", errorMessage);
|
||||||
|
|
||||||
TelemetryProcessor.traceFailure(
|
TelemetryProcessor.traceFailure(
|
||||||
Action.UpdateDocument,
|
Action.UpdateDocument,
|
||||||
{
|
{
|
||||||
@@ -1150,27 +1156,16 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
|
|||||||
deletePromise = _bulkDeleteNoSqlDocuments(_collection, toDeleteDocumentIds);
|
deletePromise = _bulkDeleteNoSqlDocuments(_collection, toDeleteDocumentIds);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (isMongoBulkDeleteDisabled) {
|
deletePromise = MongoProxyClient.deleteDocuments(
|
||||||
// TODO: Once new mongo proxy is available for all users, remove the call for MongoProxyClient.deleteDocument().
|
_collection.databaseId,
|
||||||
// MongoProxyClient.deleteDocuments() should be called for all users.
|
_collection as ViewModels.Collection,
|
||||||
deletePromise = MongoProxyClient.deleteDocument(
|
toDeleteDocumentIds,
|
||||||
_collection.databaseId,
|
).then(({ deletedCount, isAcknowledged }) => {
|
||||||
_collection as ViewModels.Collection,
|
if (deletedCount === toDeleteDocumentIds.length && isAcknowledged) {
|
||||||
toDeleteDocumentIds[0],
|
return toDeleteDocumentIds;
|
||||||
).then(() => [toDeleteDocumentIds[0]]);
|
}
|
||||||
// ----------------------------------------------------------------------------------------------------
|
throw new Error(`Delete failed with deletedCount: ${deletedCount} and isAcknowledged: ${isAcknowledged}`);
|
||||||
} else {
|
});
|
||||||
deletePromise = MongoProxyClient.deleteDocuments(
|
|
||||||
_collection.databaseId,
|
|
||||||
_collection as ViewModels.Collection,
|
|
||||||
toDeleteDocumentIds,
|
|
||||||
).then(({ deletedCount, isAcknowledged }) => {
|
|
||||||
if (deletedCount === toDeleteDocumentIds.length && isAcknowledged) {
|
|
||||||
return toDeleteDocumentIds;
|
|
||||||
}
|
|
||||||
throw new Error(`Delete failed with deletedCount: ${deletedCount} and isAcknowledged: ${isAcknowledged}`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return deletePromise
|
return deletePromise
|
||||||
@@ -2054,11 +2049,8 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
|
|||||||
}
|
}
|
||||||
}, [prevSelectedColumnIds, refreshDocumentsGrid, selectedColumnIds]);
|
}, [prevSelectedColumnIds, refreshDocumentsGrid, selectedColumnIds]);
|
||||||
|
|
||||||
// TODO: remove isMongoBulkDeleteDisabled when new mongo proxy is enabled for all users
|
|
||||||
// TODO: remove partitionKey.systemKey when JS SDK bug is fixed
|
// TODO: remove partitionKey.systemKey when JS SDK bug is fixed
|
||||||
const isMongoBulkDeleteDisabled = !MongoProxyClient.useMongoProxyEndpoint(Constants.MongoProxyApi.BulkDelete);
|
const isBulkDeleteDisabled = partitionKey.systemKey && !isPreferredApiMongoDB;
|
||||||
const isBulkDeleteDisabled =
|
|
||||||
(partitionKey.systemKey && !isPreferredApiMongoDB) || (isPreferredApiMongoDB && isMongoBulkDeleteDisabled);
|
|
||||||
// -------------------------------------------------------
|
// -------------------------------------------------------
|
||||||
|
|
||||||
const getFilterChoices = (): InputDatalistDropdownOptionSection[] => {
|
const getFilterChoices = (): InputDatalistDropdownOptionSection[] => {
|
||||||
@@ -2150,8 +2142,7 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
|
|||||||
selectedColumnIds={selectedColumnIds}
|
selectedColumnIds={selectedColumnIds}
|
||||||
columnDefinitions={columnDefinitions}
|
columnDefinitions={columnDefinitions}
|
||||||
isRowSelectionDisabled={
|
isRowSelectionDisabled={
|
||||||
isBulkDeleteDisabled ||
|
isBulkDeleteDisabled || (isFabric() && userContext.fabricContext?.isReadOnly)
|
||||||
(configContext.platform === Platform.Fabric && userContext.fabricContext?.isReadOnly)
|
|
||||||
}
|
}
|
||||||
onColumnSelectionChange={onColumnSelectionChange}
|
onColumnSelectionChange={onColumnSelectionChange}
|
||||||
defaultColumnSelection={getInitialColumnSelection()}
|
defaultColumnSelection={getInitialColumnSelection()}
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
import { useMongoProxyEndpoint } from "Common/MongoProxyClient";
|
|
||||||
import React, { Component } from "react";
|
import React, { Component } from "react";
|
||||||
import * as Constants from "../../../Common/Constants";
|
|
||||||
import { configContext } from "../../../ConfigContext";
|
import { configContext } from "../../../ConfigContext";
|
||||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||||
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||||
@@ -50,15 +48,13 @@ export default class MongoShellTabComponent extends Component<
|
|||||||
IMongoShellTabComponentStates
|
IMongoShellTabComponentStates
|
||||||
> {
|
> {
|
||||||
private _logTraces: Map<string, number>;
|
private _logTraces: Map<string, number>;
|
||||||
private _useMongoProxyEndpoint: boolean;
|
|
||||||
|
|
||||||
constructor(props: IMongoShellTabComponentProps) {
|
constructor(props: IMongoShellTabComponentProps) {
|
||||||
super(props);
|
super(props);
|
||||||
this._logTraces = new Map();
|
this._logTraces = new Map();
|
||||||
this._useMongoProxyEndpoint = useMongoProxyEndpoint(Constants.MongoProxyApi.LegacyMongoShell);
|
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
url: getMongoShellUrl(this._useMongoProxyEndpoint),
|
url: getMongoShellUrl(),
|
||||||
};
|
};
|
||||||
|
|
||||||
props.onMongoShellTabAccessor({
|
props.onMongoShellTabAccessor({
|
||||||
@@ -113,17 +109,9 @@ export default class MongoShellTabComponent extends Component<
|
|||||||
const resourceId = databaseAccount?.id;
|
const resourceId = databaseAccount?.id;
|
||||||
const accountName = databaseAccount?.name;
|
const accountName = databaseAccount?.name;
|
||||||
const documentEndpoint = databaseAccount?.properties.mongoEndpoint || databaseAccount?.properties.documentEndpoint;
|
const documentEndpoint = databaseAccount?.properties.mongoEndpoint || databaseAccount?.properties.documentEndpoint;
|
||||||
const mongoEndpoint =
|
|
||||||
documentEndpoint.substr(
|
|
||||||
Constants.MongoDBAccounts.protocol.length + 3,
|
|
||||||
documentEndpoint.length -
|
|
||||||
(Constants.MongoDBAccounts.protocol.length + 2 + Constants.MongoDBAccounts.defaultPort.length),
|
|
||||||
) + Constants.MongoDBAccounts.defaultPort.toString();
|
|
||||||
const databaseId = this.props.collection.databaseId;
|
const databaseId = this.props.collection.databaseId;
|
||||||
const collectionId = this.props.collection.id();
|
const collectionId = this.props.collection.id();
|
||||||
const apiEndpoint = this._useMongoProxyEndpoint
|
const apiEndpoint = configContext.MONGO_PROXY_ENDPOINT;
|
||||||
? configContext.MONGO_PROXY_ENDPOINT
|
|
||||||
: configContext.BACKEND_ENDPOINT;
|
|
||||||
const encryptedAuthToken: string = userContext.accessToken;
|
const encryptedAuthToken: string = userContext.accessToken;
|
||||||
|
|
||||||
shellIframe.contentWindow.postMessage(
|
shellIframe.contentWindow.postMessage(
|
||||||
@@ -132,7 +120,7 @@ export default class MongoShellTabComponent extends Component<
|
|||||||
data: {
|
data: {
|
||||||
resourceId: resourceId,
|
resourceId: resourceId,
|
||||||
accountName: accountName,
|
accountName: accountName,
|
||||||
mongoEndpoint: this._useMongoProxyEndpoint ? documentEndpoint : mongoEndpoint,
|
mongoEndpoint: documentEndpoint,
|
||||||
authorization: authorization,
|
authorization: authorization,
|
||||||
databaseId: databaseId,
|
databaseId: databaseId,
|
||||||
collectionId: collectionId,
|
collectionId: collectionId,
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ import { Platform, resetConfigContext, updateConfigContext } from "../../../Conf
|
|||||||
import { updateUserContext, userContext } from "../../../UserContext";
|
import { updateUserContext, userContext } from "../../../UserContext";
|
||||||
import { getMongoShellUrl } from "./getMongoShellUrl";
|
import { getMongoShellUrl } from "./getMongoShellUrl";
|
||||||
|
|
||||||
const mongoBackendEndpoint = "https://localhost:1234";
|
|
||||||
|
|
||||||
describe("getMongoShellUrl", () => {
|
describe("getMongoShellUrl", () => {
|
||||||
let queryString = "";
|
let queryString = "";
|
||||||
|
|
||||||
@@ -11,7 +9,6 @@ describe("getMongoShellUrl", () => {
|
|||||||
resetConfigContext();
|
resetConfigContext();
|
||||||
|
|
||||||
updateConfigContext({
|
updateConfigContext({
|
||||||
BACKEND_ENDPOINT: mongoBackendEndpoint,
|
|
||||||
platform: Platform.Hosted,
|
platform: Platform.Hosted,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -37,12 +34,7 @@ describe("getMongoShellUrl", () => {
|
|||||||
queryString = `resourceId=${userContext.databaseAccount.id}&accountName=${userContext.databaseAccount.name}&mongoEndpoint=${userContext.databaseAccount.properties.documentEndpoint}`;
|
queryString = `resourceId=${userContext.databaseAccount.id}&accountName=${userContext.databaseAccount.name}&mongoEndpoint=${userContext.databaseAccount.properties.documentEndpoint}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return /indexv2.html by default", () => {
|
it("should return /index.html by default", () => {
|
||||||
expect(getMongoShellUrl().toString()).toContain(`/indexv2.html?${queryString}`);
|
expect(getMongoShellUrl().toString()).toContain(`/index.html?${queryString}`);
|
||||||
});
|
|
||||||
|
|
||||||
it("should return /index.html when useMongoProxyEndpoint is true", () => {
|
|
||||||
const useMongoProxyEndpoint: boolean = true;
|
|
||||||
expect(getMongoShellUrl(useMongoProxyEndpoint).toString()).toContain(`/index.html?${queryString}`);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { userContext } from "../../../UserContext";
|
import { userContext } from "../../../UserContext";
|
||||||
|
|
||||||
export function getMongoShellUrl(useMongoProxyEndpoint?: boolean): string {
|
export function getMongoShellUrl(): string {
|
||||||
const { databaseAccount: account } = userContext;
|
const { databaseAccount: account } = userContext;
|
||||||
const resourceId = account?.id;
|
const resourceId = account?.id;
|
||||||
const accountName = account?.name;
|
const accountName = account?.name;
|
||||||
const mongoEndpoint = account?.properties?.mongoEndpoint || account?.properties?.documentEndpoint;
|
const mongoEndpoint = account?.properties?.mongoEndpoint || account?.properties?.documentEndpoint;
|
||||||
const queryString = `resourceId=${resourceId}&accountName=${accountName}&mongoEndpoint=${mongoEndpoint}`;
|
const queryString = `resourceId=${resourceId}&accountName=${accountName}&mongoEndpoint=${mongoEndpoint}`;
|
||||||
|
|
||||||
return useMongoProxyEndpoint ? `/mongoshell/index.html?${queryString}` : `/mongoshell/indexv2.html?${queryString}`;
|
return `/mongoshell/index.html?${queryString}`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -375,6 +375,7 @@ class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps,
|
|||||||
ruCapPerOperation: ruThreshold,
|
ruCapPerOperation: ruThreshold,
|
||||||
} as QueryOperationOptions;
|
} as QueryOperationOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
const queryDocuments = async (firstItemIndex: number) =>
|
const queryDocuments = async (firstItemIndex: number) =>
|
||||||
await queryDocumentsPage(
|
await queryDocumentsPage(
|
||||||
this.props.collection && this.props.collection.id(),
|
this.props.collection && this.props.collection.id(),
|
||||||
|
|||||||
@@ -1,21 +1,17 @@
|
|||||||
import { IMessageBarStyles, MessageBar, MessageBarType } from "@fluentui/react";
|
|
||||||
import { CassandraProxyEndpoints, MongoProxyEndpoints } from "Common/Constants";
|
|
||||||
import { configContext } from "ConfigContext";
|
|
||||||
import { IpRule } from "Contracts/DataModels";
|
|
||||||
import { CollectionTabKind } from "Contracts/ViewModels";
|
import { CollectionTabKind } from "Contracts/ViewModels";
|
||||||
import Explorer from "Explorer/Explorer";
|
import Explorer from "Explorer/Explorer";
|
||||||
import { useCommandBar } from "Explorer/Menus/CommandBar/CommandBarComponentAdapter";
|
import { useCommandBar } from "Explorer/Menus/CommandBar/CommandBarComponentAdapter";
|
||||||
import { QueryCopilotTab } from "Explorer/QueryCopilot/QueryCopilotTab";
|
import { QueryCopilotTab } from "Explorer/QueryCopilot/QueryCopilotTab";
|
||||||
|
import { FabricHomeScreen } from "Explorer/SplashScreen/FabricHome";
|
||||||
import { SplashScreen } from "Explorer/SplashScreen/SplashScreen";
|
import { SplashScreen } from "Explorer/SplashScreen/SplashScreen";
|
||||||
import { ConnectTab } from "Explorer/Tabs/ConnectTab";
|
import { ConnectTab } from "Explorer/Tabs/ConnectTab";
|
||||||
import { PostgresConnectTab } from "Explorer/Tabs/PostgresConnectTab";
|
import { PostgresConnectTab } from "Explorer/Tabs/PostgresConnectTab";
|
||||||
import { QuickstartTab } from "Explorer/Tabs/QuickstartTab";
|
import { QuickstartTab } from "Explorer/Tabs/QuickstartTab";
|
||||||
import { VcoreMongoConnectTab } from "Explorer/Tabs/VCoreMongoConnectTab";
|
import { VcoreMongoConnectTab } from "Explorer/Tabs/VCoreMongoConnectTab";
|
||||||
import { VcoreMongoQuickstartTab } from "Explorer/Tabs/VCoreMongoQuickstartTab";
|
import { VcoreMongoQuickstartTab } from "Explorer/Tabs/VCoreMongoQuickstartTab";
|
||||||
import { LayoutConstants } from "Explorer/Theme/ThemeUtil";
|
|
||||||
import { KeyboardAction, KeyboardActionGroup, useKeyboardActionGroup } from "KeyboardShortcuts";
|
import { KeyboardAction, KeyboardActionGroup, useKeyboardActionGroup } from "KeyboardShortcuts";
|
||||||
|
import { isFabricNative } from "Platform/Fabric/FabricUtil";
|
||||||
import { userContext } from "UserContext";
|
import { userContext } from "UserContext";
|
||||||
import { CassandraProxyOutboundIPs, MongoProxyOutboundIPs, PortalBackendIPs } from "Utils/EndpointUtils";
|
|
||||||
import { useTeachingBubble } from "hooks/useTeachingBubble";
|
import { useTeachingBubble } from "hooks/useTeachingBubble";
|
||||||
import ko from "knockout";
|
import ko from "knockout";
|
||||||
import React, { MutableRefObject, useEffect, useRef, useState } from "react";
|
import React, { MutableRefObject, useEffect, useRef, useState } from "react";
|
||||||
@@ -34,10 +30,6 @@ interface TabsProps {
|
|||||||
|
|
||||||
export const Tabs = ({ explorer }: TabsProps): JSX.Element => {
|
export const Tabs = ({ explorer }: TabsProps): JSX.Element => {
|
||||||
const { openedTabs, openedReactTabs, activeTab, activeReactTab } = useTabs();
|
const { openedTabs, openedReactTabs, activeTab, activeReactTab } = useTabs();
|
||||||
const [
|
|
||||||
showMongoAndCassandraProxiesNetworkSettingsWarningState,
|
|
||||||
setShowMongoAndCassandraProxiesNetworkSettingsWarningState,
|
|
||||||
] = useState<boolean>(showMongoAndCassandraProxiesNetworkSettingsWarning());
|
|
||||||
|
|
||||||
const setKeyboardHandlers = useKeyboardActionGroup(KeyboardActionGroup.TABS);
|
const setKeyboardHandlers = useKeyboardActionGroup(KeyboardActionGroup.TABS);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -48,28 +40,8 @@ export const Tabs = ({ explorer }: TabsProps): JSX.Element => {
|
|||||||
});
|
});
|
||||||
}, [setKeyboardHandlers]);
|
}, [setKeyboardHandlers]);
|
||||||
|
|
||||||
const defaultMessageBarStyles: IMessageBarStyles = {
|
|
||||||
root: {
|
|
||||||
height: `${LayoutConstants.rowHeight}px`,
|
|
||||||
overflow: "hidden",
|
|
||||||
flexDirection: "row",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="tabsManagerContainer">
|
<div className="tabsManagerContainer">
|
||||||
{showMongoAndCassandraProxiesNetworkSettingsWarningState && (
|
|
||||||
<MessageBar
|
|
||||||
messageBarType={MessageBarType.warning}
|
|
||||||
styles={defaultMessageBarStyles}
|
|
||||||
onDismiss={() => {
|
|
||||||
setShowMongoAndCassandraProxiesNetworkSettingsWarningState(false);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{`We have migrated our middleware to new infrastructure. To avoid issues with Data Explorer access, please
|
|
||||||
re-enable "Allow access from Azure Portal" on the Networking blade for your account.`}
|
|
||||||
</MessageBar>
|
|
||||||
)}
|
|
||||||
<div className="nav-tabs-margin">
|
<div className="nav-tabs-margin">
|
||||||
<ul className="nav nav-tabs level navTabHeight" id="navTabs" role="tablist">
|
<ul className="nav nav-tabs level navTabHeight" id="navTabs" role="tablist">
|
||||||
{openedReactTabs.map((tab) => (
|
{openedReactTabs.map((tab) => (
|
||||||
@@ -203,7 +175,7 @@ const CloseButton = ({
|
|||||||
onKeyPress={({ nativeEvent: e }) => (tab ? tab.onKeyPressClose(undefined, e) : onKeyPressReactTabClose(e, tabKind))}
|
onKeyPress={({ nativeEvent: e }) => (tab ? tab.onKeyPressClose(undefined, e) : onKeyPressReactTabClose(e, tabKind))}
|
||||||
>
|
>
|
||||||
<span className="tabIcon close-Icon">
|
<span className="tabIcon close-Icon">
|
||||||
<img src={errorIcon} title="Close" alt="Close" role="none" />
|
<img src={errorIcon} title="Close" alt="Close" aria-label="hidden" />
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
@@ -301,7 +273,11 @@ const getReactTabContent = (activeReactTab: ReactTabKind, explorer: Explorer): J
|
|||||||
<ConnectTab />
|
<ConnectTab />
|
||||||
);
|
);
|
||||||
case ReactTabKind.Home:
|
case ReactTabKind.Home:
|
||||||
return <SplashScreen explorer={explorer} />;
|
if (isFabricNative()) {
|
||||||
|
return <FabricHomeScreen explorer={explorer} />;
|
||||||
|
} else {
|
||||||
|
return <SplashScreen explorer={explorer} />;
|
||||||
|
}
|
||||||
case ReactTabKind.Quickstart:
|
case ReactTabKind.Quickstart:
|
||||||
return userContext.apiType === "VCoreMongo" ? (
|
return userContext.apiType === "VCoreMongo" ? (
|
||||||
<VcoreMongoQuickstartTab explorer={explorer} />
|
<VcoreMongoQuickstartTab explorer={explorer} />
|
||||||
@@ -314,57 +290,3 @@ const getReactTabContent = (activeReactTab: ReactTabKind, explorer: Explorer): J
|
|||||||
throw Error(`Unsupported tab kind ${ReactTabKind[activeReactTab]}`);
|
throw Error(`Unsupported tab kind ${ReactTabKind[activeReactTab]}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const showMongoAndCassandraProxiesNetworkSettingsWarning = (): boolean => {
|
|
||||||
const ipRules: IpRule[] = userContext.databaseAccount?.properties?.ipRules;
|
|
||||||
if (
|
|
||||||
((userContext.apiType === "Mongo" && configContext.MONGO_PROXY_ENDPOINT !== MongoProxyEndpoints.Development) ||
|
|
||||||
(userContext.apiType === "Cassandra" &&
|
|
||||||
configContext.CASSANDRA_PROXY_ENDPOINT !== CassandraProxyEndpoints.Development)) &&
|
|
||||||
ipRules?.length
|
|
||||||
) {
|
|
||||||
const legacyPortalBackendIPs: string[] = PortalBackendIPs[configContext.BACKEND_ENDPOINT];
|
|
||||||
const ipAddressesFromIPRules: string[] = ipRules.map((ipRule) => ipRule.ipAddressOrRange);
|
|
||||||
const ipRulesIncludeLegacyPortalBackend: boolean = legacyPortalBackendIPs.every((legacyPortalBackendIP: string) =>
|
|
||||||
ipAddressesFromIPRules.includes(legacyPortalBackendIP),
|
|
||||||
);
|
|
||||||
if (!ipRulesIncludeLegacyPortalBackend) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (userContext.apiType === "Mongo") {
|
|
||||||
const isProdOrMpacMongoProxyEndpoint: boolean = [MongoProxyEndpoints.Mpac, MongoProxyEndpoints.Prod].includes(
|
|
||||||
configContext.MONGO_PROXY_ENDPOINT,
|
|
||||||
);
|
|
||||||
|
|
||||||
const mongoProxyOutboundIPs: string[] = isProdOrMpacMongoProxyEndpoint
|
|
||||||
? [...MongoProxyOutboundIPs[MongoProxyEndpoints.Mpac], ...MongoProxyOutboundIPs[MongoProxyEndpoints.Prod]]
|
|
||||||
: MongoProxyOutboundIPs[configContext.MONGO_PROXY_ENDPOINT];
|
|
||||||
|
|
||||||
const ipRulesIncludeMongoProxy: boolean = mongoProxyOutboundIPs.every((mongoProxyOutboundIP: string) =>
|
|
||||||
ipAddressesFromIPRules.includes(mongoProxyOutboundIP),
|
|
||||||
);
|
|
||||||
|
|
||||||
return !ipRulesIncludeMongoProxy;
|
|
||||||
} else if (userContext.apiType === "Cassandra") {
|
|
||||||
const isProdOrMpacCassandraProxyEndpoint: boolean = [
|
|
||||||
CassandraProxyEndpoints.Mpac,
|
|
||||||
CassandraProxyEndpoints.Prod,
|
|
||||||
].includes(configContext.CASSANDRA_PROXY_ENDPOINT);
|
|
||||||
|
|
||||||
const cassandraProxyOutboundIPs: string[] = isProdOrMpacCassandraProxyEndpoint
|
|
||||||
? [
|
|
||||||
...CassandraProxyOutboundIPs[CassandraProxyEndpoints.Mpac],
|
|
||||||
...CassandraProxyOutboundIPs[CassandraProxyEndpoints.Prod],
|
|
||||||
]
|
|
||||||
: CassandraProxyOutboundIPs[configContext.CASSANDRA_PROXY_ENDPOINT];
|
|
||||||
|
|
||||||
const ipRulesIncludeCassandraProxy: boolean = cassandraProxyOutboundIPs.every(
|
|
||||||
(cassandraProxyOutboundIP: string) => ipAddressesFromIPRules.includes(cassandraProxyOutboundIP),
|
|
||||||
);
|
|
||||||
|
|
||||||
return !ipRulesIncludeCassandraProxy;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { checkFirewallRules } from "Explorer/Tabs/Shared/CheckFirewallRules";
|
|||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import FirewallRuleScreenshot from "../../../images/firewallRule.png";
|
import FirewallRuleScreenshot from "../../../images/firewallRule.png";
|
||||||
|
import VcoreFirewallRuleScreenshot from "../../../images/vcoreMongoFirewallRule.png";
|
||||||
import { ReactAdapter } from "../../Bindings/ReactBindingHandler";
|
import { ReactAdapter } from "../../Bindings/ReactBindingHandler";
|
||||||
import * as DataModels from "../../Contracts/DataModels";
|
import * as DataModels from "../../Contracts/DataModels";
|
||||||
import * as ViewModels from "../../Contracts/ViewModels";
|
import * as ViewModels from "../../Contracts/ViewModels";
|
||||||
@@ -42,7 +43,11 @@ class NotebookTerminalComponentAdapter implements ReactAdapter {
|
|||||||
return (
|
return (
|
||||||
<QuickstartFirewallNotification
|
<QuickstartFirewallNotification
|
||||||
messageType={MessageTypes.OpenPostgresNetworkingBlade}
|
messageType={MessageTypes.OpenPostgresNetworkingBlade}
|
||||||
screenshot={FirewallRuleScreenshot}
|
screenshot={
|
||||||
|
this.kind === ViewModels.TerminalKind.Mongo || this.kind === ViewModels.TerminalKind.VCoreMongo
|
||||||
|
? VcoreFirewallRuleScreenshot
|
||||||
|
: FirewallRuleScreenshot
|
||||||
|
}
|
||||||
shellName={this.getShellNameForDisplay(this.kind)}
|
shellName={this.getShellNameForDisplay(this.kind)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Resource, StoredProcedureDefinition, TriggerDefinition, UserDefinedFunctionDefinition } from "@azure/cosmos";
|
import { Resource, StoredProcedureDefinition, TriggerDefinition, UserDefinedFunctionDefinition } from "@azure/cosmos";
|
||||||
import { useNotebook } from "Explorer/Notebook/useNotebook";
|
import { useNotebook } from "Explorer/Notebook/useNotebook";
|
||||||
import { DocumentsTabV2 } from "Explorer/Tabs/DocumentsTabV2/DocumentsTabV2";
|
import { DocumentsTabV2 } from "Explorer/Tabs/DocumentsTabV2/DocumentsTabV2";
|
||||||
|
import { isFabricMirrored } from "Platform/Fabric/FabricUtil";
|
||||||
import * as ko from "knockout";
|
import * as ko from "knockout";
|
||||||
import * as _ from "underscore";
|
import * as _ from "underscore";
|
||||||
import * as Constants from "../../Common/Constants";
|
import * as Constants from "../../Common/Constants";
|
||||||
@@ -34,7 +35,6 @@ import QueryTablesTab from "../Tabs/QueryTablesTab";
|
|||||||
import { CollectionSettingsTabV2 } from "../Tabs/SettingsTabV2";
|
import { CollectionSettingsTabV2 } from "../Tabs/SettingsTabV2";
|
||||||
import { useDatabases } from "../useDatabases";
|
import { useDatabases } from "../useDatabases";
|
||||||
import { useSelectedNode } from "../useSelectedNode";
|
import { useSelectedNode } from "../useSelectedNode";
|
||||||
import { Platform, configContext } from "./../../ConfigContext";
|
|
||||||
import ConflictId from "./ConflictId";
|
import ConflictId from "./ConflictId";
|
||||||
import DocumentId from "./DocumentId";
|
import DocumentId from "./DocumentId";
|
||||||
import StoredProcedure from "./StoredProcedure";
|
import StoredProcedure from "./StoredProcedure";
|
||||||
@@ -210,7 +210,7 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const showScriptsMenus: boolean =
|
const showScriptsMenus: boolean =
|
||||||
configContext.platform != Platform.Fabric && (userContext.apiType === "SQL" || userContext.apiType === "Gremlin");
|
!isFabricMirrored() && (userContext.apiType === "SQL" || userContext.apiType === "Gremlin");
|
||||||
this.showStoredProcedures = ko.observable<boolean>(showScriptsMenus);
|
this.showStoredProcedures = ko.observable<boolean>(showScriptsMenus);
|
||||||
this.showTriggers = ko.observable<boolean>(showScriptsMenus);
|
this.showTriggers = ko.observable<boolean>(showScriptsMenus);
|
||||||
this.showUserDefinedFunctions = ko.observable<boolean>(showScriptsMenus);
|
this.showUserDefinedFunctions = ko.observable<boolean>(showScriptsMenus);
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { Tree, TreeItemValue, TreeOpenChangeData, TreeOpenChangeEvent } from "@fluentui/react-components";
|
import { Tree, TreeItemValue, TreeOpenChangeData, TreeOpenChangeEvent } from "@fluentui/react-components";
|
||||||
import { Home16Regular } from "@fluentui/react-icons";
|
import { Home16Regular } from "@fluentui/react-icons";
|
||||||
import { AuthType } from "AuthType";
|
import { AuthType } from "AuthType";
|
||||||
import { Platform, configContext } from "ConfigContext";
|
|
||||||
import { useTreeStyles } from "Explorer/Controls/TreeComponent/Styles";
|
import { useTreeStyles } from "Explorer/Controls/TreeComponent/Styles";
|
||||||
import { TreeNode, TreeNodeComponent } from "Explorer/Controls/TreeComponent/TreeNodeComponent";
|
import { TreeNode, TreeNodeComponent } from "Explorer/Controls/TreeComponent/TreeNodeComponent";
|
||||||
import {
|
import {
|
||||||
@@ -11,6 +10,7 @@ import {
|
|||||||
} from "Explorer/Tree/treeNodeUtil";
|
} from "Explorer/Tree/treeNodeUtil";
|
||||||
import { useDatabases } from "Explorer/useDatabases";
|
import { useDatabases } from "Explorer/useDatabases";
|
||||||
import { useSelectedNode } from "Explorer/useSelectedNode";
|
import { useSelectedNode } from "Explorer/useSelectedNode";
|
||||||
|
import { isFabricMirrored } from "Platform/Fabric/FabricUtil";
|
||||||
import { userContext } from "UserContext";
|
import { userContext } from "UserContext";
|
||||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||||
import { ReactTabKind, useTabs } from "hooks/useTabs";
|
import { ReactTabKind, useTabs } from "hooks/useTabs";
|
||||||
@@ -76,23 +76,22 @@ export const ResourceTree: React.FC<ResourceTreeProps> = ({ explorer }: Resource
|
|||||||
: [];
|
: [];
|
||||||
}, [isSampleDataEnabled, sampleDataResourceTokenCollection]);
|
}, [isSampleDataEnabled, sampleDataResourceTokenCollection]);
|
||||||
|
|
||||||
const headerNodes: TreeNode[] =
|
const headerNodes: TreeNode[] = isFabricMirrored()
|
||||||
configContext.platform === Platform.Fabric
|
? []
|
||||||
? []
|
: [
|
||||||
: [
|
{
|
||||||
{
|
id: "home",
|
||||||
id: "home",
|
iconSrc: <Home16Regular />,
|
||||||
iconSrc: <Home16Regular />,
|
label: "Home",
|
||||||
label: "Home",
|
isSelected: () =>
|
||||||
isSelected: () =>
|
useSelectedNode.getState().selectedNode === undefined &&
|
||||||
useSelectedNode.getState().selectedNode === undefined &&
|
useTabs.getState().activeReactTab === ReactTabKind.Home,
|
||||||
useTabs.getState().activeReactTab === ReactTabKind.Home,
|
onClick: () => {
|
||||||
onClick: () => {
|
useSelectedNode.getState().setSelectedNode(undefined);
|
||||||
useSelectedNode.getState().setSelectedNode(undefined);
|
useTabs.getState().openAndActivateReactTab(ReactTabKind.Home);
|
||||||
useTabs.getState().openAndActivateReactTab(ReactTabKind.Home);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
];
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const rootNodes: TreeNode[] = useMemo(() => {
|
const rootNodes: TreeNode[] = useMemo(() => {
|
||||||
if (sampleDataNodes.length > 0) {
|
if (sampleDataNodes.length > 0) {
|
||||||
|
|||||||
@@ -740,7 +740,7 @@ exports[`createDatabaseTreeNodes generates the correct tree structure for the Mo
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`createDatabaseTreeNodes generates the correct tree structure for the SQL API, on Fabric 1`] = `
|
exports[`createDatabaseTreeNodes generates the correct tree structure for the SQL API, on Fabric non read-only 1`] = `
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"children": [
|
"children": [
|
||||||
@@ -753,6 +753,12 @@ exports[`createDatabaseTreeNodes generates the correct tree structure for the SQ
|
|||||||
"label": "New SQL Query",
|
"label": "New SQL Query",
|
||||||
"onClick": [Function],
|
"onClick": [Function],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"iconSrc": {},
|
||||||
|
"label": "Delete Container",
|
||||||
|
"onClick": [Function],
|
||||||
|
"styleClass": "deleteCollectionMenuItem",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
"iconSrc": <DocumentMultipleRegular
|
"iconSrc": <DocumentMultipleRegular
|
||||||
fontSize={16}
|
fontSize={16}
|
||||||
@@ -774,6 +780,12 @@ exports[`createDatabaseTreeNodes generates the correct tree structure for the SQ
|
|||||||
"label": "New SQL Query",
|
"label": "New SQL Query",
|
||||||
"onClick": [Function],
|
"onClick": [Function],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"iconSrc": {},
|
||||||
|
"label": "Delete Container",
|
||||||
|
"onClick": [Function],
|
||||||
|
"styleClass": "deleteCollectionMenuItem",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
"iconSrc": <DocumentMultipleRegular
|
"iconSrc": <DocumentMultipleRegular
|
||||||
fontSize={16}
|
fontSize={16}
|
||||||
@@ -822,6 +834,12 @@ exports[`createDatabaseTreeNodes generates the correct tree structure for the SQ
|
|||||||
"label": "New SQL Query",
|
"label": "New SQL Query",
|
||||||
"onClick": [Function],
|
"onClick": [Function],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"iconSrc": {},
|
||||||
|
"label": "Delete Container",
|
||||||
|
"onClick": [Function],
|
||||||
|
"styleClass": "deleteCollectionMenuItem",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
"iconSrc": <DocumentMultipleRegular
|
"iconSrc": <DocumentMultipleRegular
|
||||||
fontSize={16}
|
fontSize={16}
|
||||||
@@ -870,6 +888,12 @@ exports[`createDatabaseTreeNodes generates the correct tree structure for the SQ
|
|||||||
"label": "New SQL Query",
|
"label": "New SQL Query",
|
||||||
"onClick": [Function],
|
"onClick": [Function],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"iconSrc": {},
|
||||||
|
"label": "Delete Container",
|
||||||
|
"onClick": [Function],
|
||||||
|
"styleClass": "deleteCollectionMenuItem",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
"iconSrc": <DocumentMultipleRegular
|
"iconSrc": <DocumentMultipleRegular
|
||||||
fontSize={16}
|
fontSize={16}
|
||||||
@@ -915,6 +939,145 @@ exports[`createDatabaseTreeNodes generates the correct tree structure for the SQ
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`createDatabaseTreeNodes generates the correct tree structure for the SQL API, on Fabric read-only 1`] = `
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"children": undefined,
|
||||||
|
"className": "collectionNode",
|
||||||
|
"contextMenu": [
|
||||||
|
{
|
||||||
|
"iconSrc": {},
|
||||||
|
"label": "New SQL Query",
|
||||||
|
"onClick": [Function],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"iconSrc": <DocumentMultipleRegular
|
||||||
|
fontSize={16}
|
||||||
|
/>,
|
||||||
|
"isExpanded": true,
|
||||||
|
"isSelected": [Function],
|
||||||
|
"label": "standardCollection",
|
||||||
|
"onClick": [Function],
|
||||||
|
"onCollapsed": [Function],
|
||||||
|
"onContextMenuOpen": [Function],
|
||||||
|
"onExpanded": [Function],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"children": undefined,
|
||||||
|
"className": "collectionNode",
|
||||||
|
"contextMenu": [
|
||||||
|
{
|
||||||
|
"iconSrc": {},
|
||||||
|
"label": "New SQL Query",
|
||||||
|
"onClick": [Function],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"iconSrc": <DocumentMultipleRegular
|
||||||
|
fontSize={16}
|
||||||
|
/>,
|
||||||
|
"isExpanded": true,
|
||||||
|
"isSelected": [Function],
|
||||||
|
"label": "conflictsCollection",
|
||||||
|
"onClick": [Function],
|
||||||
|
"onCollapsed": [Function],
|
||||||
|
"onContextMenuOpen": [Function],
|
||||||
|
"onExpanded": [Function],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"className": "databaseNode",
|
||||||
|
"contextMenu": undefined,
|
||||||
|
"iconSrc": <DatabaseRegular
|
||||||
|
fontSize={16}
|
||||||
|
/>,
|
||||||
|
"isExpanded": true,
|
||||||
|
"isSelected": [Function],
|
||||||
|
"label": "standardDb",
|
||||||
|
"onCollapsed": [Function],
|
||||||
|
"onContextMenuOpen": [Function],
|
||||||
|
"onExpanded": [Function],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"children": undefined,
|
||||||
|
"className": "collectionNode",
|
||||||
|
"contextMenu": [
|
||||||
|
{
|
||||||
|
"iconSrc": {},
|
||||||
|
"label": "New SQL Query",
|
||||||
|
"onClick": [Function],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"iconSrc": <DocumentMultipleRegular
|
||||||
|
fontSize={16}
|
||||||
|
/>,
|
||||||
|
"isExpanded": true,
|
||||||
|
"isSelected": [Function],
|
||||||
|
"label": "sampleItemsCollection",
|
||||||
|
"onClick": [Function],
|
||||||
|
"onCollapsed": [Function],
|
||||||
|
"onContextMenuOpen": [Function],
|
||||||
|
"onExpanded": [Function],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"className": "databaseNode",
|
||||||
|
"contextMenu": undefined,
|
||||||
|
"iconSrc": <DatabaseRegular
|
||||||
|
fontSize={16}
|
||||||
|
/>,
|
||||||
|
"isExpanded": true,
|
||||||
|
"isSelected": [Function],
|
||||||
|
"label": "sharedDatabase",
|
||||||
|
"onCollapsed": [Function],
|
||||||
|
"onContextMenuOpen": [Function],
|
||||||
|
"onExpanded": [Function],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"children": undefined,
|
||||||
|
"className": "collectionNode",
|
||||||
|
"contextMenu": [
|
||||||
|
{
|
||||||
|
"iconSrc": {},
|
||||||
|
"label": "New SQL Query",
|
||||||
|
"onClick": [Function],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"iconSrc": <DocumentMultipleRegular
|
||||||
|
fontSize={16}
|
||||||
|
/>,
|
||||||
|
"isExpanded": true,
|
||||||
|
"isSelected": [Function],
|
||||||
|
"label": "schemaCollection",
|
||||||
|
"onClick": [Function],
|
||||||
|
"onCollapsed": [Function],
|
||||||
|
"onContextMenuOpen": [Function],
|
||||||
|
"onExpanded": [Function],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"className": "loadMoreNode",
|
||||||
|
"label": "load more",
|
||||||
|
"onClick": [Function],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"className": "databaseNode",
|
||||||
|
"contextMenu": undefined,
|
||||||
|
"iconSrc": <DatabaseRegular
|
||||||
|
fontSize={16}
|
||||||
|
/>,
|
||||||
|
"isExpanded": true,
|
||||||
|
"isSelected": [Function],
|
||||||
|
"label": "giganticDatabase",
|
||||||
|
"onCollapsed": [Function],
|
||||||
|
"onContextMenuOpen": [Function],
|
||||||
|
"onExpanded": [Function],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`createDatabaseTreeNodes generates the correct tree structure for the SQL API, on Portal 1`] = `
|
exports[`createDatabaseTreeNodes generates the correct tree structure for the SQL API, on Portal 1`] = `
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
@@ -972,7 +1135,7 @@ exports[`createDatabaseTreeNodes generates the correct tree structure for the SQ
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
"isSelected": [Function],
|
"isSelected": [Function],
|
||||||
"label": "mockSproc3",
|
"label": "mockSproc4",
|
||||||
"onClick": [Function],
|
"onClick": [Function],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -990,7 +1153,7 @@ exports[`createDatabaseTreeNodes generates the correct tree structure for the SQ
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
"isSelected": [Function],
|
"isSelected": [Function],
|
||||||
"label": "mockUdf3",
|
"label": "mockUdf4",
|
||||||
"onClick": [Function],
|
"onClick": [Function],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -1008,7 +1171,7 @@ exports[`createDatabaseTreeNodes generates the correct tree structure for the SQ
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
"isSelected": [Function],
|
"isSelected": [Function],
|
||||||
"label": "mockTrigger3",
|
"label": "mockTrigger4",
|
||||||
"onClick": [Function],
|
"onClick": [Function],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { CapabilityNames } from "Common/Constants";
|
import { CapabilityNames } from "Common/Constants";
|
||||||
import { Platform, updateConfigContext } from "ConfigContext";
|
import { Platform, updateConfigContext } from "ConfigContext";
|
||||||
|
import { CosmosDbArtifactType } from "Contracts/FabricMessagesContract";
|
||||||
import { TreeNode } from "Explorer/Controls/TreeComponent/TreeNodeComponent";
|
import { TreeNode } from "Explorer/Controls/TreeComponent/TreeNodeComponent";
|
||||||
import Explorer from "Explorer/Explorer";
|
import Explorer from "Explorer/Explorer";
|
||||||
import { useCommandBar } from "Explorer/Menus/CommandBar/CommandBarComponentAdapter";
|
import { useCommandBar } from "Explorer/Menus/CommandBar/CommandBarComponentAdapter";
|
||||||
@@ -16,7 +17,7 @@ import {
|
|||||||
} from "Explorer/Tree/treeNodeUtil";
|
} from "Explorer/Tree/treeNodeUtil";
|
||||||
import { useDatabases } from "Explorer/useDatabases";
|
import { useDatabases } from "Explorer/useDatabases";
|
||||||
import { useSelectedNode } from "Explorer/useSelectedNode";
|
import { useSelectedNode } from "Explorer/useSelectedNode";
|
||||||
import { updateUserContext } from "UserContext";
|
import { FabricContext, updateUserContext, UserContext } from "UserContext";
|
||||||
import PromiseSource from "Utils/PromiseSource";
|
import PromiseSource from "Utils/PromiseSource";
|
||||||
import { useSidePanel } from "hooks/useSidePanel";
|
import { useSidePanel } from "hooks/useSidePanel";
|
||||||
import { useTabs } from "hooks/useTabs";
|
import { useTabs } from "hooks/useTabs";
|
||||||
@@ -360,9 +361,30 @@ describe("createDatabaseTreeNodes", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it.each<[string, Platform, boolean, Partial<DataModels.DatabaseAccountExtendedProperties>]>([
|
it.each<[string, Platform, boolean, Partial<DataModels.DatabaseAccountExtendedProperties>, Partial<UserContext>]>([
|
||||||
["the SQL API, on Fabric", Platform.Fabric, false, { capabilities: [], enableMultipleWriteLocations: true }],
|
[
|
||||||
["the SQL API, on Portal", Platform.Portal, false, { capabilities: [], enableMultipleWriteLocations: true }],
|
"the SQL API, on Fabric read-only",
|
||||||
|
Platform.Fabric,
|
||||||
|
false,
|
||||||
|
{ capabilities: [], enableMultipleWriteLocations: true },
|
||||||
|
{ fabricContext: { isReadOnly: true } as FabricContext<CosmosDbArtifactType> },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"the SQL API, on Fabric non read-only",
|
||||||
|
Platform.Fabric,
|
||||||
|
false,
|
||||||
|
{ capabilities: [], enableMultipleWriteLocations: true },
|
||||||
|
{ fabricContext: { isReadOnly: false } as FabricContext<CosmosDbArtifactType> },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"the SQL API, on Portal",
|
||||||
|
Platform.Portal,
|
||||||
|
false,
|
||||||
|
{ capabilities: [], enableMultipleWriteLocations: true },
|
||||||
|
{
|
||||||
|
fabricContext: undefined,
|
||||||
|
},
|
||||||
|
],
|
||||||
[
|
[
|
||||||
"the Cassandra API, serverless, on Hosted",
|
"the Cassandra API, serverless, on Hosted",
|
||||||
Platform.Hosted,
|
Platform.Hosted,
|
||||||
@@ -373,6 +395,7 @@ describe("createDatabaseTreeNodes", () => {
|
|||||||
{ name: CapabilityNames.EnableServerless, description: "" },
|
{ name: CapabilityNames.EnableServerless, description: "" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{ fabricContext: undefined },
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"the Mongo API, with Notebooks and Phoenix features, on Emulator",
|
"the Mongo API, with Notebooks and Phoenix features, on Emulator",
|
||||||
@@ -381,26 +404,31 @@ describe("createDatabaseTreeNodes", () => {
|
|||||||
{
|
{
|
||||||
capabilities: [{ name: CapabilityNames.EnableMongo, description: "" }],
|
capabilities: [{ name: CapabilityNames.EnableMongo, description: "" }],
|
||||||
},
|
},
|
||||||
|
{ fabricContext: undefined },
|
||||||
],
|
],
|
||||||
])("generates the correct tree structure for %s", (_, platform, isNotebookEnabled, dbAccountProperties) => {
|
])(
|
||||||
useNotebook.setState({ isPhoenixFeatures: isNotebookEnabled });
|
"generates the correct tree structure for %s",
|
||||||
updateConfigContext({ platform });
|
(_, platform, isNotebookEnabled, dbAccountProperties, userContext) => {
|
||||||
updateUserContext({
|
useNotebook.setState({ isPhoenixFeatures: isNotebookEnabled });
|
||||||
databaseAccount: {
|
updateConfigContext({ platform });
|
||||||
properties: {
|
updateUserContext({
|
||||||
enableMultipleWriteLocations: true,
|
...userContext,
|
||||||
...dbAccountProperties,
|
databaseAccount: {
|
||||||
},
|
properties: {
|
||||||
} as unknown as DataModels.DatabaseAccount,
|
enableMultipleWriteLocations: true,
|
||||||
});
|
...dbAccountProperties,
|
||||||
const nodes = createDatabaseTreeNodes(
|
},
|
||||||
explorer,
|
} as unknown as DataModels.DatabaseAccount,
|
||||||
isNotebookEnabled,
|
});
|
||||||
useDatabases.getState().databases,
|
const nodes = createDatabaseTreeNodes(
|
||||||
refreshActiveTab,
|
explorer,
|
||||||
);
|
isNotebookEnabled,
|
||||||
expect(nodes).toMatchSnapshot();
|
useDatabases.getState().databases,
|
||||||
});
|
refreshActiveTab,
|
||||||
|
);
|
||||||
|
expect(nodes).toMatchSnapshot();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
// The above tests focused on the tree structure. The below tests focus on some core behaviors of the nodes.
|
// The above tests focused on the tree structure. The below tests focus on some core behaviors of the nodes.
|
||||||
// They are not exhaustive, because exhaustive tests here require a lot of mocking and can become very brittle.
|
// They are not exhaustive, because exhaustive tests here require a lot of mocking and can become very brittle.
|
||||||
@@ -551,7 +579,18 @@ describe("createDatabaseTreeNodes", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
["in Fabric", () => updateConfigContext({ platform: Platform.Fabric })],
|
[
|
||||||
|
"in Fabric",
|
||||||
|
() => {
|
||||||
|
updateConfigContext({ platform: Platform.Fabric });
|
||||||
|
updateUserContext({
|
||||||
|
fabricContext: {
|
||||||
|
artifactType: CosmosDbArtifactType.MIRRORED_KEY,
|
||||||
|
isReadOnly: true,
|
||||||
|
} as FabricContext<CosmosDbArtifactType>,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
],
|
||||||
[
|
[
|
||||||
"for Cassandra API",
|
"for Cassandra API",
|
||||||
() =>
|
() =>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import StoredProcedure from "Explorer/Tree/StoredProcedure";
|
|||||||
import Trigger from "Explorer/Tree/Trigger";
|
import Trigger from "Explorer/Tree/Trigger";
|
||||||
import UserDefinedFunction from "Explorer/Tree/UserDefinedFunction";
|
import UserDefinedFunction from "Explorer/Tree/UserDefinedFunction";
|
||||||
import { useDatabases } from "Explorer/useDatabases";
|
import { useDatabases } from "Explorer/useDatabases";
|
||||||
|
import { isFabricMirrored } from "Platform/Fabric/FabricUtil";
|
||||||
import { getItemName } from "Utils/APITypeUtils";
|
import { getItemName } from "Utils/APITypeUtils";
|
||||||
import { isServerlessAccount } from "Utils/CapabilityUtils";
|
import { isServerlessAccount } from "Utils/CapabilityUtils";
|
||||||
import { useTabs } from "hooks/useTabs";
|
import { useTabs } from "hooks/useTabs";
|
||||||
@@ -22,9 +23,7 @@ import { useNotebook } from "../Notebook/useNotebook";
|
|||||||
import { useSelectedNode } from "../useSelectedNode";
|
import { useSelectedNode } from "../useSelectedNode";
|
||||||
|
|
||||||
export const shouldShowScriptNodes = (): boolean => {
|
export const shouldShowScriptNodes = (): boolean => {
|
||||||
return (
|
return !isFabricMirrored() && (userContext.apiType === "SQL" || userContext.apiType === "Gremlin");
|
||||||
configContext.platform !== Platform.Fabric && (userContext.apiType === "SQL" || userContext.apiType === "Gremlin")
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const TreeDatabaseIcon = <DatabaseRegular fontSize={16} />;
|
const TreeDatabaseIcon = <DatabaseRegular fontSize={16} />;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { Action } from "Shared/Telemetry/TelemetryConstants";
|
|||||||
import { userContext } from "UserContext";
|
import { userContext } from "UserContext";
|
||||||
import { allowedJunoOrigins, validateEndpoint } from "Utils/EndpointUtils";
|
import { allowedJunoOrigins, validateEndpoint } from "Utils/EndpointUtils";
|
||||||
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
import { useQueryCopilot } from "hooks/useQueryCopilot";
|
||||||
import promiseRetry, { AbortError } from "p-retry";
|
import promiseRetry, { AbortError, Options } from "p-retry";
|
||||||
import {
|
import {
|
||||||
Areas,
|
Areas,
|
||||||
ConnectionStatusType,
|
ConnectionStatusType,
|
||||||
@@ -35,21 +35,26 @@ import { getAuthorizationHeader } from "../Utils/AuthorizationUtils";
|
|||||||
export class PhoenixClient {
|
export class PhoenixClient {
|
||||||
private armResourceId: string;
|
private armResourceId: string;
|
||||||
private containerHealthHandler: NodeJS.Timeout;
|
private containerHealthHandler: NodeJS.Timeout;
|
||||||
private retryOptions: promiseRetry.Options = {
|
private retryOptions: Options = {
|
||||||
retries: Notebook.retryAttempts,
|
retries: Notebook.retryAttempts,
|
||||||
maxTimeout: Notebook.retryAttemptDelayMs,
|
maxTimeout: Notebook.retryAttemptDelayMs,
|
||||||
minTimeout: Notebook.retryAttemptDelayMs,
|
minTimeout: Notebook.retryAttemptDelayMs,
|
||||||
};
|
};
|
||||||
|
private abortController: AbortController;
|
||||||
|
private abortSignal: AbortSignal;
|
||||||
|
|
||||||
constructor(armResourceId: string) {
|
constructor(armResourceId: string) {
|
||||||
this.armResourceId = armResourceId;
|
this.armResourceId = armResourceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async allocateContainer(provisionData: IProvisionData): Promise<IResponse<IPhoenixServiceInfo>> {
|
public async allocateContainer(provisionData: IProvisionData): Promise<IResponse<IPhoenixServiceInfo>> {
|
||||||
|
this.initializeCancelEventListener();
|
||||||
|
|
||||||
return promiseRetry(() => this.executeContainerAssignmentOperation(provisionData, "allocate"), {
|
return promiseRetry(() => this.executeContainerAssignmentOperation(provisionData, "allocate"), {
|
||||||
retries: 4,
|
retries: 4,
|
||||||
maxTimeout: 20000,
|
maxTimeout: 20000,
|
||||||
minTimeout: 20000,
|
minTimeout: 20000,
|
||||||
|
signal: this.abortSignal,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -270,6 +275,17 @@ export class PhoenixClient {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private initializeCancelEventListener(): void {
|
||||||
|
this.abortController = new AbortController();
|
||||||
|
this.abortSignal = this.abortController.signal;
|
||||||
|
|
||||||
|
document.addEventListener("keydown", (event: KeyboardEvent) => {
|
||||||
|
if (event.ctrlKey && (event.key === "c" || event.key === "z")) {
|
||||||
|
this.abortController.abort(new AbortError("Request canceled"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public ConvertToForbiddenErrorString(jsonData: IPhoenixError): string {
|
public ConvertToForbiddenErrorString(jsonData: IPhoenixError): string {
|
||||||
const errInfo = jsonData;
|
const errInfo = jsonData;
|
||||||
switch (errInfo?.type) {
|
switch (errInfo?.type) {
|
||||||
|
|||||||
@@ -1,56 +1,112 @@
|
|||||||
import { sendCachedDataMessage } from "Common/MessageHandler";
|
import { sendCachedDataMessage } from "Common/MessageHandler";
|
||||||
|
import { configContext, Platform } from "ConfigContext";
|
||||||
import { FabricMessageTypes } from "Contracts/FabricMessageTypes";
|
import { FabricMessageTypes } from "Contracts/FabricMessageTypes";
|
||||||
import { FabricDatabaseConnectionInfo } from "Contracts/FabricMessagesContract";
|
import { CosmosDbArtifactType, ResourceTokenInfo } from "Contracts/FabricMessagesContract";
|
||||||
import { updateUserContext, userContext } from "UserContext";
|
import { FabricArtifactInfo, updateUserContext, userContext } from "UserContext";
|
||||||
import { logConsoleError } from "Utils/NotificationConsoleUtils";
|
import { logConsoleError } from "Utils/NotificationConsoleUtils";
|
||||||
|
|
||||||
const TOKEN_VALIDITY_MS = (3600 - 600) * 1000; // 1 hour minus 10 minutes to be safe
|
const TOKEN_VALIDITY_MS = (3600 - 600) * 1000; // 1 hour minus 10 minutes to be safe
|
||||||
const DEBOUNCE_DELAY_MS = 1000 * 20; // 20 second
|
const DEBOUNCE_DELAY_MS = 1000 * 20; // 20 second
|
||||||
let timeoutId: NodeJS.Timeout;
|
let timeoutId: NodeJS.Timeout | undefined;
|
||||||
|
|
||||||
// Prevents multiple parallel requests during DEBOUNCE_DELAY_MS
|
// Prevents multiple parallel requests during DEBOUNCE_DELAY_MS
|
||||||
let lastRequestTimestamp: number = undefined;
|
let lastRequestTimestamp: number | undefined = undefined;
|
||||||
|
|
||||||
const requestDatabaseResourceTokens = async (): Promise<void> => {
|
/**
|
||||||
|
* Request fabric token:
|
||||||
|
* - Mirrored key and AAD: Database Resource Tokens
|
||||||
|
* - Native: AAD token
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const requestFabricToken = async (): Promise<void> => {
|
||||||
if (lastRequestTimestamp !== undefined && lastRequestTimestamp + DEBOUNCE_DELAY_MS > Date.now()) {
|
if (lastRequestTimestamp !== undefined && lastRequestTimestamp + DEBOUNCE_DELAY_MS > Date.now()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
lastRequestTimestamp = Date.now();
|
lastRequestTimestamp = Date.now();
|
||||||
try {
|
try {
|
||||||
const fabricDatabaseConnectionInfo = await sendCachedDataMessage<FabricDatabaseConnectionInfo>(
|
if (isFabricMirrored()) {
|
||||||
FabricMessageTypes.GetAllResourceTokens,
|
await requestAndStoreDatabaseResourceTokens();
|
||||||
[],
|
} else if (isFabricNative()) {
|
||||||
userContext.fabricContext.connectionId,
|
await requestAndStoreAccessToken();
|
||||||
);
|
|
||||||
|
|
||||||
if (!userContext.databaseAccount.properties.documentEndpoint) {
|
|
||||||
userContext.databaseAccount.properties.documentEndpoint = fabricDatabaseConnectionInfo.endpoint;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updateUserContext({
|
scheduleRefreshFabricToken();
|
||||||
fabricContext: {
|
|
||||||
...userContext.fabricContext,
|
|
||||||
databaseConnectionInfo: fabricDatabaseConnectionInfo,
|
|
||||||
isReadOnly: true,
|
|
||||||
},
|
|
||||||
databaseAccount: { ...userContext.databaseAccount },
|
|
||||||
});
|
|
||||||
scheduleRefreshDatabaseResourceToken();
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleError(error);
|
logConsoleError(error as string);
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
lastRequestTimestamp = undefined;
|
lastRequestTimestamp = undefined;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const requestAndStoreDatabaseResourceTokens = async (): Promise<void> => {
|
||||||
|
if (!userContext.fabricContext || !userContext.databaseAccount) {
|
||||||
|
// This should not happen
|
||||||
|
logConsoleError("Fabric context or database account is missing: cannot request tokens");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const resourceTokenInfo = await sendCachedDataMessage<ResourceTokenInfo>(
|
||||||
|
FabricMessageTypes.GetAllResourceTokens,
|
||||||
|
[],
|
||||||
|
userContext.fabricContext.artifactInfo?.connectionId,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!userContext.databaseAccount.properties.documentEndpoint) {
|
||||||
|
userContext.databaseAccount.properties.documentEndpoint = resourceTokenInfo.endpoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resourceTokenInfo.credentialType === "OAuth2") {
|
||||||
|
// Mirrored AAD
|
||||||
|
updateUserContext({
|
||||||
|
fabricContext: {
|
||||||
|
...userContext.fabricContext,
|
||||||
|
databaseName: resourceTokenInfo.databaseId,
|
||||||
|
artifactInfo: undefined,
|
||||||
|
isReadOnly: resourceTokenInfo.isReadOnly ?? userContext.fabricContext.isReadOnly,
|
||||||
|
},
|
||||||
|
databaseAccount: { ...userContext.databaseAccount },
|
||||||
|
aadToken: resourceTokenInfo.accessToken,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// TODO: In Fabric contract V2, credentialType is undefined. For V3, it is "Key". Check for "Key" when V3 is supported for Fabric Mirroring Key
|
||||||
|
// Mirrored key
|
||||||
|
updateUserContext({
|
||||||
|
fabricContext: {
|
||||||
|
...userContext.fabricContext,
|
||||||
|
databaseName: resourceTokenInfo.databaseId,
|
||||||
|
artifactInfo: {
|
||||||
|
...(userContext.fabricContext.artifactInfo as FabricArtifactInfo[CosmosDbArtifactType.MIRRORED_KEY]),
|
||||||
|
resourceTokenInfo,
|
||||||
|
},
|
||||||
|
isReadOnly: resourceTokenInfo.isReadOnly ?? userContext.fabricContext.isReadOnly,
|
||||||
|
},
|
||||||
|
databaseAccount: { ...userContext.databaseAccount },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const requestAndStoreAccessToken = async (): Promise<void> => {
|
||||||
|
if (!userContext.fabricContext || !userContext.databaseAccount) {
|
||||||
|
// This should not happen
|
||||||
|
logConsoleError("Fabric context or database account is missing: cannot request tokens");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const accessTokenInfo = await sendCachedDataMessage<{ accessToken: string }>(FabricMessageTypes.GetAccessToken, []);
|
||||||
|
|
||||||
|
updateUserContext({
|
||||||
|
aadToken: accessTokenInfo.accessToken,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check token validity and schedule a refresh if necessary
|
* Check token validity and schedule a refresh if necessary
|
||||||
* @param tokenTimestamp
|
* @param tokenTimestamp
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const scheduleRefreshDatabaseResourceToken = (refreshNow?: boolean): Promise<void> => {
|
export const scheduleRefreshFabricToken = (refreshNow?: boolean): Promise<void> => {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
if (timeoutId !== undefined) {
|
if (timeoutId !== undefined) {
|
||||||
clearTimeout(timeoutId);
|
clearTimeout(timeoutId);
|
||||||
@@ -59,7 +115,7 @@ export const scheduleRefreshDatabaseResourceToken = (refreshNow?: boolean): Prom
|
|||||||
|
|
||||||
timeoutId = setTimeout(
|
timeoutId = setTimeout(
|
||||||
() => {
|
() => {
|
||||||
requestDatabaseResourceTokens().then(resolve);
|
requestFabricToken().then(resolve);
|
||||||
},
|
},
|
||||||
refreshNow ? 0 : TOKEN_VALIDITY_MS,
|
refreshNow ? 0 : TOKEN_VALIDITY_MS,
|
||||||
);
|
);
|
||||||
@@ -68,6 +124,15 @@ export const scheduleRefreshDatabaseResourceToken = (refreshNow?: boolean): Prom
|
|||||||
|
|
||||||
export const checkDatabaseResourceTokensValidity = (tokenTimestamp: number): void => {
|
export const checkDatabaseResourceTokensValidity = (tokenTimestamp: number): void => {
|
||||||
if (tokenTimestamp + TOKEN_VALIDITY_MS < Date.now()) {
|
if (tokenTimestamp + TOKEN_VALIDITY_MS < Date.now()) {
|
||||||
scheduleRefreshDatabaseResourceToken(true);
|
scheduleRefreshFabricToken(true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const isFabric = (): boolean => configContext.platform === Platform.Fabric;
|
||||||
|
export const isFabricMirroredKey = (): boolean =>
|
||||||
|
isFabric() && userContext.fabricContext?.artifactType === CosmosDbArtifactType.MIRRORED_KEY;
|
||||||
|
export const isFabricMirroredAAD = (): boolean =>
|
||||||
|
isFabric() && userContext.fabricContext?.artifactType === CosmosDbArtifactType.MIRRORED_AAD;
|
||||||
|
export const isFabricMirrored = (): boolean => isFabricMirroredKey() || isFabricMirroredAAD();
|
||||||
|
export const isFabricNative = (): boolean =>
|
||||||
|
isFabric() && userContext.fabricContext?.artifactType === CosmosDbArtifactType.NATIVE;
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
import { useBoolean } from "@fluentui/react-hooks";
|
import { useBoolean } from "@fluentui/react-hooks";
|
||||||
import { userContext } from "UserContext";
|
import { userContext } from "UserContext";
|
||||||
import { useNewPortalBackendEndpoint } from "Utils/EndpointUtils";
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import ConnectImage from "../../../../images/HdeConnectCosmosDB.svg";
|
import ConnectImage from "../../../../images/HdeConnectCosmosDB.svg";
|
||||||
import ErrorImage from "../../../../images/error.svg";
|
import ErrorImage from "../../../../images/error.svg";
|
||||||
import { AuthType } from "../../../AuthType";
|
import { AuthType } from "../../../AuthType";
|
||||||
import { BackendApi, HttpHeaders } from "../../../Common/Constants";
|
import { HttpHeaders } from "../../../Common/Constants";
|
||||||
import { configContext } from "../../../ConfigContext";
|
import { configContext } from "../../../ConfigContext";
|
||||||
import { GenerateTokenResponse } from "../../../Contracts/DataModels";
|
|
||||||
import { isResourceTokenConnectionString } from "../Helpers/ResourceTokenUtils";
|
import { isResourceTokenConnectionString } from "../Helpers/ResourceTokenUtils";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -19,10 +17,6 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const fetchEncryptedToken = async (connectionString: string): Promise<string> => {
|
export const fetchEncryptedToken = async (connectionString: string): Promise<string> => {
|
||||||
if (!useNewPortalBackendEndpoint(BackendApi.GenerateToken)) {
|
|
||||||
return await fetchEncryptedToken_ToBeDeprecated(connectionString);
|
|
||||||
}
|
|
||||||
|
|
||||||
const headers = new Headers();
|
const headers = new Headers();
|
||||||
headers.append(HttpHeaders.connectionString, connectionString);
|
headers.append(HttpHeaders.connectionString, connectionString);
|
||||||
const url = configContext.PORTAL_BACKEND_ENDPOINT + "/api/connectionstring/token/generatetoken";
|
const url = configContext.PORTAL_BACKEND_ENDPOINT + "/api/connectionstring/token/generatetoken";
|
||||||
@@ -35,28 +29,10 @@ export const fetchEncryptedToken = async (connectionString: string): Promise<str
|
|||||||
return decodeURIComponent(encryptedTokenResponse);
|
return decodeURIComponent(encryptedTokenResponse);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchEncryptedToken_ToBeDeprecated = async (connectionString: string): Promise<string> => {
|
|
||||||
const headers = new Headers();
|
|
||||||
headers.append(HttpHeaders.connectionString, connectionString);
|
|
||||||
const url = configContext.BACKEND_ENDPOINT + "/api/guest/tokens/generateToken";
|
|
||||||
const response = await fetch(url, { headers, method: "POST" });
|
|
||||||
if (!response.ok) {
|
|
||||||
throw response;
|
|
||||||
}
|
|
||||||
// This API has a quirk where it must be parsed twice
|
|
||||||
const result: GenerateTokenResponse = JSON.parse(await response.json());
|
|
||||||
return decodeURIComponent(result.readWrite || result.read);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const isAccountRestrictedForConnectionStringLogin = async (connectionString: string): Promise<boolean> => {
|
export const isAccountRestrictedForConnectionStringLogin = async (connectionString: string): Promise<boolean> => {
|
||||||
const headers = new Headers();
|
const headers = new Headers();
|
||||||
headers.append(HttpHeaders.connectionString, connectionString);
|
headers.append(HttpHeaders.connectionString, connectionString);
|
||||||
|
const url = configContext.PORTAL_BACKEND_ENDPOINT + "/api/guest/accountrestrictions/checkconnectionstringlogin";
|
||||||
const backendEndpoint: string = useNewPortalBackendEndpoint(BackendApi.AccountRestrictions)
|
|
||||||
? configContext.PORTAL_BACKEND_ENDPOINT
|
|
||||||
: configContext.BACKEND_ENDPOINT;
|
|
||||||
|
|
||||||
const url = backendEndpoint + "/api/guest/accountrestrictions/checkconnectionstringlogin";
|
|
||||||
const response = await fetch(url, { headers, method: "POST" });
|
const response = await fetch(url, { headers, method: "POST" });
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw response;
|
throw response;
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export type Features = {
|
|||||||
readonly enableAadDataPlane: boolean;
|
readonly enableAadDataPlane: boolean;
|
||||||
readonly enableResourceGraph: boolean;
|
readonly enableResourceGraph: boolean;
|
||||||
readonly enableKoResourceTree: boolean;
|
readonly enableKoResourceTree: boolean;
|
||||||
|
readonly enableThroughputBuckets: boolean;
|
||||||
readonly hostedDataExplorer: boolean;
|
readonly hostedDataExplorer: boolean;
|
||||||
readonly junoEndpoint?: string;
|
readonly junoEndpoint?: string;
|
||||||
readonly phoenixEndpoint?: string;
|
readonly phoenixEndpoint?: string;
|
||||||
@@ -38,7 +39,6 @@ export type Features = {
|
|||||||
readonly copilotChatFixedMonacoEditorHeight: boolean;
|
readonly copilotChatFixedMonacoEditorHeight: boolean;
|
||||||
readonly enablePriorityBasedExecution: boolean;
|
readonly enablePriorityBasedExecution: boolean;
|
||||||
readonly disableConnectionStringLogin: boolean;
|
readonly disableConnectionStringLogin: boolean;
|
||||||
readonly restoreTabs: boolean;
|
|
||||||
|
|
||||||
// can be set via both flight and feature flag
|
// can be set via both flight and feature flag
|
||||||
autoscaleDefault: boolean;
|
autoscaleDefault: boolean;
|
||||||
@@ -82,6 +82,7 @@ export function extractFeatures(given = new URLSearchParams(window.location.sear
|
|||||||
enableSpark: "true" === get("enablespark"),
|
enableSpark: "true" === get("enablespark"),
|
||||||
enableTtl: "true" === get("enablettl"),
|
enableTtl: "true" === get("enablettl"),
|
||||||
enableKoResourceTree: "true" === get("enablekoresourcetree"),
|
enableKoResourceTree: "true" === get("enablekoresourcetree"),
|
||||||
|
enableThroughputBuckets: "true" === get("enablethroughputbuckets"),
|
||||||
executeSproc: "true" === get("dataexplorerexecutesproc"),
|
executeSproc: "true" === get("dataexplorerexecutesproc"),
|
||||||
hostedDataExplorer: "true" === get("hosteddataexplorerenabled"),
|
hostedDataExplorer: "true" === get("hosteddataexplorerenabled"),
|
||||||
mongoProxyEndpoint: get("mongoproxyendpoint"),
|
mongoProxyEndpoint: get("mongoproxyendpoint"),
|
||||||
@@ -109,7 +110,6 @@ export function extractFeatures(given = new URLSearchParams(window.location.sear
|
|||||||
copilotChatFixedMonacoEditorHeight: "true" === get("copilotchatfixedmonacoeditorheight"),
|
copilotChatFixedMonacoEditorHeight: "true" === get("copilotchatfixedmonacoeditorheight"),
|
||||||
enablePriorityBasedExecution: "true" === get("enableprioritybasedexecution"),
|
enablePriorityBasedExecution: "true" === get("enableprioritybasedexecution"),
|
||||||
disableConnectionStringLogin: "true" === get("disableconnectionstringlogin"),
|
disableConnectionStringLogin: "true" === get("disableconnectionstringlogin"),
|
||||||
restoreTabs: "true" === get("restoretabs"),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,13 +41,13 @@ const getDescriptor = async (selfServeType: SelfServeType): Promise<SelfServeDes
|
|||||||
case SelfServeType.example: {
|
case SelfServeType.example: {
|
||||||
const SelfServeExample = await import(/* webpackChunkName: "SelfServeExample" */ "./Example/SelfServeExample");
|
const SelfServeExample = await import(/* webpackChunkName: "SelfServeExample" */ "./Example/SelfServeExample");
|
||||||
const selfServeExample = new SelfServeExample.default();
|
const selfServeExample = new SelfServeExample.default();
|
||||||
await loadTranslations(selfServeExample.constructor.name);
|
await loadTranslations(selfServeType);
|
||||||
return selfServeExample.toSelfServeDescriptor();
|
return selfServeExample.toSelfServeDescriptor();
|
||||||
}
|
}
|
||||||
case SelfServeType.sqlx: {
|
case SelfServeType.sqlx: {
|
||||||
const SqlX = await import(/* webpackChunkName: "SqlX" */ "./SqlX/SqlX");
|
const SqlX = await import(/* webpackChunkName: "SqlX" */ "./SqlX/SqlX");
|
||||||
const sqlX = new SqlX.default();
|
const sqlX = new SqlX.default();
|
||||||
await loadTranslations(sqlX.constructor.name);
|
await loadTranslations(selfServeType);
|
||||||
return sqlX.toSelfServeDescriptor();
|
return sqlX.toSelfServeDescriptor();
|
||||||
}
|
}
|
||||||
case SelfServeType.graphapicompute: {
|
case SelfServeType.graphapicompute: {
|
||||||
@@ -55,7 +55,7 @@ const getDescriptor = async (selfServeType: SelfServeType): Promise<SelfServeDes
|
|||||||
/* webpackChunkName: "GraphAPICompute" */ "./GraphAPICompute/GraphAPICompute"
|
/* webpackChunkName: "GraphAPICompute" */ "./GraphAPICompute/GraphAPICompute"
|
||||||
);
|
);
|
||||||
const graphAPICompute = new GraphAPICompute.default();
|
const graphAPICompute = new GraphAPICompute.default();
|
||||||
await loadTranslations(graphAPICompute.constructor.name);
|
await loadTranslations(selfServeType);
|
||||||
return graphAPICompute.toSelfServeDescriptor();
|
return graphAPICompute.toSelfServeDescriptor();
|
||||||
}
|
}
|
||||||
case SelfServeType.materializedviewsbuilder: {
|
case SelfServeType.materializedviewsbuilder: {
|
||||||
@@ -63,7 +63,7 @@ const getDescriptor = async (selfServeType: SelfServeType): Promise<SelfServeDes
|
|||||||
/* webpackChunkName: "MaterializedViewsBuilder" */ "./MaterializedViewsBuilder/MaterializedViewsBuilder"
|
/* webpackChunkName: "MaterializedViewsBuilder" */ "./MaterializedViewsBuilder/MaterializedViewsBuilder"
|
||||||
);
|
);
|
||||||
const materializedViewsBuilder = new MaterializedViewsBuilder.default();
|
const materializedViewsBuilder = new MaterializedViewsBuilder.default();
|
||||||
await loadTranslations(materializedViewsBuilder.constructor.name);
|
await loadTranslations(selfServeType);
|
||||||
return materializedViewsBuilder.toSelfServeDescriptor();
|
return materializedViewsBuilder.toSelfServeDescriptor();
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@@ -103,7 +103,7 @@ const handleMessage = async (event: MessageEvent): Promise<void> => {
|
|||||||
|
|
||||||
const urlSearchParams = new URLSearchParams(window.location.search);
|
const urlSearchParams = new URLSearchParams(window.location.search);
|
||||||
const selfServeTypeText = urlSearchParams.get("selfServeType") || inputs.selfServeType;
|
const selfServeTypeText = urlSearchParams.get("selfServeType") || inputs.selfServeType;
|
||||||
const selfServeType = SelfServeType[selfServeTypeText?.toLowerCase() as keyof typeof SelfServeType];
|
const selfServeType = SelfServeType[selfServeTypeText.toLocaleLowerCase() as keyof typeof SelfServeType];
|
||||||
if (
|
if (
|
||||||
!inputs.subscriptionId ||
|
!inputs.subscriptionId ||
|
||||||
!inputs.resourceGroup ||
|
!inputs.resourceGroup ||
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
Text,
|
Text,
|
||||||
} from "@fluentui/react";
|
} from "@fluentui/react";
|
||||||
import { TFunction } from "i18next";
|
import { TFunction } from "i18next";
|
||||||
import promiseRetry, { AbortError } from "p-retry";
|
import promiseRetry, { AbortError, Options } from "p-retry";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { WithTranslation } from "react-i18next";
|
import { WithTranslation } from "react-i18next";
|
||||||
import * as _ from "underscore";
|
import * as _ from "underscore";
|
||||||
@@ -80,7 +80,7 @@ export class SelfServeComponent extends React.Component<SelfServeComponentProps,
|
|||||||
private static readonly defaultRetryIntervalInMs = 30000;
|
private static readonly defaultRetryIntervalInMs = 30000;
|
||||||
private smartUiGeneratorClassName: string;
|
private smartUiGeneratorClassName: string;
|
||||||
private retryIntervalInMs: number;
|
private retryIntervalInMs: number;
|
||||||
private retryOptions: promiseRetry.Options;
|
private retryOptions: Options;
|
||||||
private translationFunction: TFunction;
|
private translationFunction: TFunction;
|
||||||
|
|
||||||
componentDidMount(): void {
|
componentDidMount(): void {
|
||||||
|
|||||||
@@ -29,10 +29,11 @@ export enum SelfServeType {
|
|||||||
// Unsupported self serve type passed as feature flag
|
// Unsupported self serve type passed as feature flag
|
||||||
invalid = "invalid",
|
invalid = "invalid",
|
||||||
// Add your self serve types here
|
// Add your self serve types here
|
||||||
example = "example",
|
// NOTE: text and casing of the enum's value must match the corresponding file in Localization\en\
|
||||||
sqlx = "sqlx",
|
example = "SelfServeExample",
|
||||||
graphapicompute = "graphapicompute",
|
sqlx = "SqlX",
|
||||||
materializedviewsbuilder = "materializedviewsbuilder",
|
graphapicompute = "GraphAPICompute",
|
||||||
|
materializedviewsbuilder = "MaterializedViewsBuilder",
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -197,6 +197,11 @@ export const getPriceMapAndCurrencyCode = async (map: OfferingIdMap): Promise<Pr
|
|||||||
const priceMap = new Map<string, Map<string, number>>();
|
const priceMap = new Map<string, Map<string, number>>();
|
||||||
let billingCurrency;
|
let billingCurrency;
|
||||||
for (const region of map.keys()) {
|
for (const region of map.keys()) {
|
||||||
|
// if no offering id is found for that region, skipping calling price API
|
||||||
|
const subMap = map.get(region);
|
||||||
|
if (!subMap || subMap.size === 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
const regionPriceMap = new Map<string, number>();
|
const regionPriceMap = new Map<string, number>();
|
||||||
const regionShortName = await getRegionShortName(region);
|
const regionShortName = await getRegionShortName(region);
|
||||||
const requestBody: OfferingIdRequest = {
|
const requestBody: OfferingIdRequest = {
|
||||||
@@ -237,7 +242,7 @@ export const getPriceMapAndCurrencyCode = async (map: OfferingIdMap): Promise<Pr
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
const failureTelemetry = { err, selfServeClassName: SqlX.name };
|
const failureTelemetry = { err, selfServeClassName: SqlX.name };
|
||||||
selfServeTraceFailure(failureTelemetry, getPriceMapAndCurrencyCodeTimestamp);
|
selfServeTraceFailure(failureTelemetry, getPriceMapAndCurrencyCodeTimestamp);
|
||||||
return { priceMap: undefined, billingCurrency: undefined };
|
return { priceMap: new Map<string, Map<string, number>>(), billingCurrency: undefined };
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -286,6 +291,6 @@ export const getOfferingIds = async (regions: Array<RegionItem>): Promise<Offeri
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
const failureTelemetry = { err, selfServeClassName: SqlX.name };
|
const failureTelemetry = { err, selfServeClassName: SqlX.name };
|
||||||
selfServeTraceFailure(failureTelemetry, getOfferingIdsCodeTimestamp);
|
selfServeTraceFailure(failureTelemetry, getOfferingIdsCodeTimestamp);
|
||||||
return undefined;
|
return new Map<string, Map<string, string>>();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -227,11 +227,13 @@ const calculateCost = (skuName: string, instanceCount: number): Description => {
|
|||||||
let costPerHour = 0;
|
let costPerHour = 0;
|
||||||
let costBreakdown = "";
|
let costBreakdown = "";
|
||||||
for (const regionItem of regions) {
|
for (const regionItem of regions) {
|
||||||
const incrementalCost = priceMap.get(regionItem.locationName).get(skuName.replace("Cosmos.", ""));
|
const incrementalCost = priceMap?.get(regionItem.locationName)?.get(skuName.replace("Cosmos.", ""));
|
||||||
if (incrementalCost === undefined) {
|
if (incrementalCost === undefined) {
|
||||||
throw new Error(`${regionItem.locationName} not found in price map.`);
|
throw new Error(`${regionItem.locationName} not found in price map.`);
|
||||||
} else if (incrementalCost === 0) {
|
} else if (incrementalCost === 0) {
|
||||||
throw new Error(`${regionItem.locationName} cost per hour = 0`);
|
throw new Error(`${regionItem.locationName} cost per hour = 0`);
|
||||||
|
} else if (currencyCode === undefined) {
|
||||||
|
throw new Error(`Currency code not found in price map.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
let regionalInstanceCount = instanceCount;
|
let regionalInstanceCount = instanceCount;
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export class JupyterLabAppFactory {
|
|||||||
if (userContext.apiType === "VCoreMongo" && content?.includes("MongoServerError: Invalid key")) {
|
if (userContext.apiType === "VCoreMongo" && content?.includes("MongoServerError: Invalid key")) {
|
||||||
this.restartShell = true;
|
this.restartShell = true;
|
||||||
}
|
}
|
||||||
return content?.includes("cosmosuser@");
|
return content?.includes("cosmosshelluser@");
|
||||||
}
|
}
|
||||||
|
|
||||||
private isMongoShellStarted(content: string | undefined) {
|
private isMongoShellStarted(content: string | undefined) {
|
||||||
@@ -68,7 +68,6 @@ export class JupyterLabAppFactory {
|
|||||||
const session = await manager.startNew();
|
const session = await manager.startNew();
|
||||||
session.messageReceived.connect(async (_, message: IMessage) => {
|
session.messageReceived.connect(async (_, message: IMessage) => {
|
||||||
const content = message.content && message.content[0]?.toString();
|
const content = message.content && message.content[0]?.toString();
|
||||||
|
|
||||||
if (this.checkShellStarted && message.type == "stdout") {
|
if (this.checkShellStarted && message.type == "stdout") {
|
||||||
//Close the terminal tab once the shell closed messages are received
|
//Close the terminal tab once the shell closed messages are received
|
||||||
if (!this.isShellStarted) {
|
if (!this.isShellStarted) {
|
||||||
@@ -114,6 +113,13 @@ export class JupyterLabAppFactory {
|
|||||||
panel.dispose();
|
panel.dispose();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Close terminal when Ctrl key is pressed
|
||||||
|
term.node.addEventListener("keydown", (event: KeyboardEvent) => {
|
||||||
|
if (event.ctrlKey) {
|
||||||
|
this.onShellExited(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return session;
|
return session;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { FabricDatabaseConnectionInfo } from "Contracts/FabricMessagesContract";
|
import { CosmosDbArtifactType, ResourceTokenInfo } from "Contracts/FabricMessagesContract";
|
||||||
import { ParsedResourceTokenConnectionString } from "Platform/Hosted/Helpers/ResourceTokenUtils";
|
import { ParsedResourceTokenConnectionString } from "Platform/Hosted/Helpers/ResourceTokenUtils";
|
||||||
import { Action } from "Shared/Telemetry/TelemetryConstants";
|
import { Action } from "Shared/Telemetry/TelemetryConstants";
|
||||||
import { traceOpen } from "Shared/Telemetry/TelemetryProcessor";
|
import { traceOpen } from "Shared/Telemetry/TelemetryProcessor";
|
||||||
@@ -47,11 +47,21 @@ export interface VCoreMongoConnectionParams {
|
|||||||
connectionString: string;
|
connectionString: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FabricContext {
|
export interface FabricArtifactInfo {
|
||||||
connectionId: string;
|
[CosmosDbArtifactType.MIRRORED_KEY]: {
|
||||||
databaseConnectionInfo: FabricDatabaseConnectionInfo | undefined;
|
connectionId: string;
|
||||||
|
resourceTokenInfo: ResourceTokenInfo | undefined;
|
||||||
|
};
|
||||||
|
[CosmosDbArtifactType.MIRRORED_AAD]: undefined;
|
||||||
|
[CosmosDbArtifactType.NATIVE]: undefined;
|
||||||
|
}
|
||||||
|
export interface FabricContext<T extends CosmosDbArtifactType> {
|
||||||
|
fabricClientRpcVersion: string;
|
||||||
isReadOnly: boolean;
|
isReadOnly: boolean;
|
||||||
isVisible: boolean;
|
isVisible: boolean;
|
||||||
|
databaseName: string;
|
||||||
|
artifactType: CosmosDbArtifactType;
|
||||||
|
artifactInfo: FabricArtifactInfo[T];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AdminFeedbackControlPolicy =
|
export type AdminFeedbackControlPolicy =
|
||||||
@@ -70,7 +80,7 @@ export type AdminFeedbackPolicySettings = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export interface UserContext {
|
export interface UserContext {
|
||||||
readonly fabricContext?: FabricContext;
|
readonly fabricContext?: FabricContext<CosmosDbArtifactType>;
|
||||||
readonly authType?: AuthType;
|
readonly authType?: AuthType;
|
||||||
readonly masterKey?: string;
|
readonly masterKey?: string;
|
||||||
readonly subscriptionId?: string;
|
readonly subscriptionId?: string;
|
||||||
|
|||||||
@@ -89,3 +89,7 @@ export const getItemName = (): string => {
|
|||||||
return "Items";
|
return "Items";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const isDataplaneRbacSupported = (apiType: string): boolean => {
|
||||||
|
return apiType === "SQL" || apiType === "Tables";
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
export const autoPilotThroughput1K = 1000;
|
export const autoPilotThroughput1K = 1000;
|
||||||
export const autoPilotIncrementStep = 1000;
|
export const autoPilotIncrementStep = 1000;
|
||||||
export const autoPilotThroughput4K = 4000;
|
export const autoPilotThroughput4K = 4000;
|
||||||
|
export const autoPilotThroughput10K = 10000;
|
||||||
|
|
||||||
export function isValidAutoPilotThroughput(maxThroughput: number): boolean {
|
export function isValidAutoPilotThroughput(maxThroughput: number): boolean {
|
||||||
if (!maxThroughput) {
|
if (!maxThroughput) {
|
||||||
|
|||||||
@@ -1,11 +1,4 @@
|
|||||||
import {
|
import { CassandraProxyEndpoints, JunoEndpoints, MongoProxyEndpoints, PortalBackendEndpoints } from "Common/Constants";
|
||||||
BackendApi,
|
|
||||||
CassandraProxyEndpoints,
|
|
||||||
JunoEndpoints,
|
|
||||||
MongoProxyEndpoints,
|
|
||||||
PortalBackendEndpoints,
|
|
||||||
} from "Common/Constants";
|
|
||||||
import { configContext } from "ConfigContext";
|
|
||||||
import * as Logger from "../Common/Logger";
|
import * as Logger from "../Common/Logger";
|
||||||
|
|
||||||
export function validateEndpoint(
|
export function validateEndpoint(
|
||||||
@@ -73,9 +66,6 @@ export const PortalBackendIPs: { [key: string]: string[] } = {
|
|||||||
//"https://main2.documentdb.ext.azure.com": ["104.42.196.69"],
|
//"https://main2.documentdb.ext.azure.com": ["104.42.196.69"],
|
||||||
"https://main.documentdb.ext.azure.cn": ["139.217.8.252"],
|
"https://main.documentdb.ext.azure.cn": ["139.217.8.252"],
|
||||||
"https://main.documentdb.ext.azure.us": ["52.244.48.71"],
|
"https://main.documentdb.ext.azure.us": ["52.244.48.71"],
|
||||||
// Add ussec and usnat when endpoint address is known:
|
|
||||||
//ussec: ["29.26.26.67", "29.26.26.66"],
|
|
||||||
//usnat: ["7.28.202.68"],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PortalBackendOutboundIPs: { [key: string]: string[] } = {
|
export const PortalBackendOutboundIPs: { [key: string]: string[] } = {
|
||||||
@@ -100,14 +90,6 @@ export const defaultAllowedMongoProxyEndpoints: ReadonlyArray<string> = [
|
|||||||
MongoProxyEndpoints.Mooncake,
|
MongoProxyEndpoints.Mooncake,
|
||||||
];
|
];
|
||||||
|
|
||||||
export const allowedMongoProxyEndpoints_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 defaultAllowedCassandraProxyEndpoints: ReadonlyArray<string> = [
|
export const defaultAllowedCassandraProxyEndpoints: ReadonlyArray<string> = [
|
||||||
CassandraProxyEndpoints.Development,
|
CassandraProxyEndpoints.Development,
|
||||||
CassandraProxyEndpoints.Mpac,
|
CassandraProxyEndpoints.Mpac,
|
||||||
@@ -141,9 +123,7 @@ export const allowedArcadiaEndpoints: ReadonlyArray<string> = ["https://workspac
|
|||||||
|
|
||||||
export const allowedHostedExplorerEndpoints: ReadonlyArray<string> = ["https://cosmos.azure.com/"];
|
export const allowedHostedExplorerEndpoints: ReadonlyArray<string> = ["https://cosmos.azure.com/"];
|
||||||
|
|
||||||
export const allowedMsalRedirectEndpoints: ReadonlyArray<string> = [
|
export const allowedMsalRedirectEndpoints: ReadonlyArray<string> = ["https://dataexplorer-preview.azurewebsites.net/"];
|
||||||
"https://cosmos-explorer-preview.azurewebsites.net/",
|
|
||||||
];
|
|
||||||
|
|
||||||
export const allowedJunoOrigins: ReadonlyArray<string> = [
|
export const allowedJunoOrigins: ReadonlyArray<string> = [
|
||||||
JunoEndpoints.Test,
|
JunoEndpoints.Test,
|
||||||
@@ -155,53 +135,3 @@ export const allowedJunoOrigins: ReadonlyArray<string> = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export const allowedNotebookServerUrls: ReadonlyArray<string> = [];
|
export const allowedNotebookServerUrls: ReadonlyArray<string> = [];
|
||||||
|
|
||||||
//
|
|
||||||
// Temporary function to determine if a portal backend API is supported by the
|
|
||||||
// new backend in this environment.
|
|
||||||
//
|
|
||||||
// TODO: Remove this function once new backend migration is completed for all environments.
|
|
||||||
//
|
|
||||||
export function useNewPortalBackendEndpoint(backendApi: string): boolean {
|
|
||||||
// This maps backend APIs to the environments supported by the new backend.
|
|
||||||
const newBackendApiEnvironmentMap: { [key: string]: string[] } = {
|
|
||||||
[BackendApi.GenerateToken]: [
|
|
||||||
PortalBackendEndpoints.Development,
|
|
||||||
PortalBackendEndpoints.Mpac,
|
|
||||||
PortalBackendEndpoints.Prod,
|
|
||||||
],
|
|
||||||
[BackendApi.PortalSettings]: [
|
|
||||||
PortalBackendEndpoints.Development,
|
|
||||||
PortalBackendEndpoints.Mpac,
|
|
||||||
PortalBackendEndpoints.Prod,
|
|
||||||
],
|
|
||||||
[BackendApi.AccountRestrictions]: [
|
|
||||||
PortalBackendEndpoints.Development,
|
|
||||||
PortalBackendEndpoints.Mpac,
|
|
||||||
PortalBackendEndpoints.Prod,
|
|
||||||
],
|
|
||||||
[BackendApi.RuntimeProxy]: [
|
|
||||||
PortalBackendEndpoints.Development,
|
|
||||||
PortalBackendEndpoints.Mpac,
|
|
||||||
PortalBackendEndpoints.Prod,
|
|
||||||
],
|
|
||||||
[BackendApi.DisallowedLocations]: [
|
|
||||||
PortalBackendEndpoints.Development,
|
|
||||||
PortalBackendEndpoints.Mpac,
|
|
||||||
PortalBackendEndpoints.Prod,
|
|
||||||
PortalBackendEndpoints.Fairfax,
|
|
||||||
PortalBackendEndpoints.Mooncake,
|
|
||||||
],
|
|
||||||
[BackendApi.SampleData]: [
|
|
||||||
PortalBackendEndpoints.Development,
|
|
||||||
PortalBackendEndpoints.Mpac,
|
|
||||||
PortalBackendEndpoints.Prod,
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!newBackendApiEnvironmentMap[backendApi] || !configContext.PORTAL_BACKEND_ENDPOINT) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return newBackendApiEnvironmentMap[backendApi].includes(configContext.PORTAL_BACKEND_ENDPOINT);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -35,6 +35,13 @@ describe("Query Utils", () => {
|
|||||||
version: 2,
|
version: 2,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
const generatePartitionKeysForPaths = (paths: string[]): DataModels.PartitionKey => {
|
||||||
|
return {
|
||||||
|
paths: paths,
|
||||||
|
kind: "Hash",
|
||||||
|
version: 2,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
describe("buildDocumentsQueryPartitionProjections()", () => {
|
describe("buildDocumentsQueryPartitionProjections()", () => {
|
||||||
it("should return empty string if partition key is undefined", () => {
|
it("should return empty string if partition key is undefined", () => {
|
||||||
@@ -89,6 +96,18 @@ describe("Query Utils", () => {
|
|||||||
|
|
||||||
expect(query).toContain("c.id");
|
expect(query).toContain("c.id");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should always include {} for any missing partition keys", () => {
|
||||||
|
const query = QueryUtils.buildDocumentsQuery(
|
||||||
|
"",
|
||||||
|
["a", "b", "c"],
|
||||||
|
generatePartitionKeysForPaths(["/a", "/b", "/c"]),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
expect(query).toContain('IIF(IS_DEFINED(c["a"]), c["a"], {})');
|
||||||
|
expect(query).toContain('IIF(IS_DEFINED(c["b"]), c["b"], {})');
|
||||||
|
expect(query).toContain('IIF(IS_DEFINED(c["c"]), c["c"], {})');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("queryPagesUntilContentPresent()", () => {
|
describe("queryPagesUntilContentPresent()", () => {
|
||||||
@@ -201,18 +220,6 @@ describe("Query Utils", () => {
|
|||||||
expect(expectedPartitionKeyValues).toContain(documentContent["Category"]);
|
expect(expectedPartitionKeyValues).toContain(documentContent["Category"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should extract no partition key values in the case nested partition key", () => {
|
|
||||||
const singlePartitionKeyDefinition: PartitionKeyDefinition = {
|
|
||||||
kind: PartitionKeyKind.Hash,
|
|
||||||
paths: ["/Location.type"],
|
|
||||||
};
|
|
||||||
const partitionKeyValues: PartitionKey[] = extractPartitionKeyValues(
|
|
||||||
documentContent,
|
|
||||||
singlePartitionKeyDefinition,
|
|
||||||
);
|
|
||||||
expect(partitionKeyValues.length).toBe(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should extract all partition key values for hierarchical and nested partition keys", () => {
|
it("should extract all partition key values for hierarchical and nested partition keys", () => {
|
||||||
const mixedPartitionKeyDefinition: PartitionKeyDefinition = {
|
const mixedPartitionKeyDefinition: PartitionKeyDefinition = {
|
||||||
kind: PartitionKeyKind.MultiHash,
|
kind: PartitionKeyKind.MultiHash,
|
||||||
@@ -225,5 +232,52 @@ describe("Query Utils", () => {
|
|||||||
expect(partitionKeyValues.length).toBe(2);
|
expect(partitionKeyValues.length).toBe(2);
|
||||||
expect(partitionKeyValues).toEqual(["United States", "Point"]);
|
expect(partitionKeyValues).toEqual(["United States", "Point"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("if any partition key is null or empty string, the partitionKeyValues shall match", () => {
|
||||||
|
const newDocumentContent = {
|
||||||
|
...documentContent,
|
||||||
|
...{
|
||||||
|
Country: null,
|
||||||
|
Location: {
|
||||||
|
type: "",
|
||||||
|
coordinates: [-121.49, 46.206],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const mixedPartitionKeyDefinition: PartitionKeyDefinition = {
|
||||||
|
kind: PartitionKeyKind.MultiHash,
|
||||||
|
paths: ["/Country", "/Location/type"],
|
||||||
|
};
|
||||||
|
const partitionKeyValues: PartitionKey[] = extractPartitionKeyValues(
|
||||||
|
newDocumentContent,
|
||||||
|
mixedPartitionKeyDefinition,
|
||||||
|
);
|
||||||
|
expect(partitionKeyValues.length).toBe(2);
|
||||||
|
expect(partitionKeyValues).toEqual([null, ""]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("if any partition key doesn't exist, it should still set partitionkey value as {}", () => {
|
||||||
|
const newDocumentContent = {
|
||||||
|
...documentContent,
|
||||||
|
...{
|
||||||
|
Country: null,
|
||||||
|
Location: {
|
||||||
|
coordinates: [-121.49, 46.206],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const mixedPartitionKeyDefinition: PartitionKeyDefinition = {
|
||||||
|
kind: PartitionKeyKind.MultiHash,
|
||||||
|
paths: ["/Country", "/Location/type"],
|
||||||
|
};
|
||||||
|
const partitionKeyValues: PartitionKey[] = extractPartitionKeyValues(
|
||||||
|
newDocumentContent,
|
||||||
|
mixedPartitionKeyDefinition,
|
||||||
|
);
|
||||||
|
expect(partitionKeyValues.length).toBe(2);
|
||||||
|
expect(partitionKeyValues).toEqual([null, {}]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -61,8 +61,9 @@ export function buildDocumentsQueryPartitionProjections(
|
|||||||
projectedProperty += `[${projection}]`;
|
projectedProperty += `[${projection}]`;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
const fullAccess = `${collectionAlias}${projectedProperty}`;
|
||||||
projections.push(`${collectionAlias}${projectedProperty}`);
|
const wrappedProjection = `IIF(IS_DEFINED(${fullAccess}), ${fullAccess}, {})`;
|
||||||
|
projections.push(wrappedProjection);
|
||||||
}
|
}
|
||||||
|
|
||||||
return projections.join(",");
|
return projections.join(",");
|
||||||
@@ -130,6 +131,8 @@ export const extractPartitionKeyValues = (
|
|||||||
|
|
||||||
if (value !== undefined) {
|
if (value !== undefined) {
|
||||||
partitionKeyValues.push(value);
|
partitionKeyValues.push(value);
|
||||||
|
} else {
|
||||||
|
partitionKeyValues.push({});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { isFabric } from "Platform/Fabric/FabricUtil";
|
||||||
import { Platform, configContext } from "./../ConfigContext";
|
import { Platform, configContext } from "./../ConfigContext";
|
||||||
|
|
||||||
export const getDataExplorerWindow = (currentWindow: Window): Window | undefined => {
|
export const getDataExplorerWindow = (currentWindow: Window): Window | undefined => {
|
||||||
@@ -7,7 +8,7 @@ export const getDataExplorerWindow = (currentWindow: Window): Window | undefined
|
|||||||
if (currentWindow.parent === currentWindow) {
|
if (currentWindow.parent === currentWindow) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
if (configContext.platform === Platform.Fabric && currentWindow.parent.parent === currentWindow.top) {
|
if (isFabric() && currentWindow.parent.parent === currentWindow.top) {
|
||||||
// in Fabric data explorer is inside an extension iframe, so we have two parent iframes
|
// in Fabric data explorer is inside an extension iframe, so we have two parent iframes
|
||||||
return currentWindow;
|
return currentWindow;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,13 +3,13 @@
|
|||||||
Run "npm run generateARMClients" to regenerate
|
Run "npm run generateARMClients" to regenerate
|
||||||
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
|
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
|
||||||
|
|
||||||
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json
|
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
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { configContext } from "../../../../ConfigContext";
|
import { configContext } from "../../../../ConfigContext";
|
||||||
import { armRequest } from "../../request";
|
import { armRequest } from "../../request";
|
||||||
import * as Types from "./types";
|
import * as Types from "./types";
|
||||||
const apiVersion = "2024-02-15-preview";
|
const apiVersion = "2024-12-01-preview";
|
||||||
|
|
||||||
/* Lists the Cassandra keyspaces under an existing Azure Cosmos DB database account. */
|
/* Lists the Cassandra keyspaces under an existing Azure Cosmos DB database account. */
|
||||||
export async function listCassandraKeyspaces(
|
export async function listCassandraKeyspaces(
|
||||||
|
|||||||
@@ -3,13 +3,13 @@
|
|||||||
Run "npm run generateARMClients" to regenerate
|
Run "npm run generateARMClients" to regenerate
|
||||||
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
|
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
|
||||||
|
|
||||||
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json
|
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
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { configContext } from "../../../../ConfigContext";
|
import { configContext } from "../../../../ConfigContext";
|
||||||
import { armRequest } from "../../request";
|
import { armRequest } from "../../request";
|
||||||
import * as Types from "./types";
|
import * as Types from "./types";
|
||||||
const apiVersion = "2024-02-15-preview";
|
const apiVersion = "2024-12-01-preview";
|
||||||
|
|
||||||
/* Retrieves the metrics determined by the given filter for the given database account and collection. */
|
/* Retrieves the metrics determined by the given filter for the given database account and collection. */
|
||||||
export async function listMetrics(
|
export async function listMetrics(
|
||||||
|
|||||||
@@ -3,13 +3,13 @@
|
|||||||
Run "npm run generateARMClients" to regenerate
|
Run "npm run generateARMClients" to regenerate
|
||||||
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
|
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
|
||||||
|
|
||||||
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json
|
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
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { configContext } from "../../../../ConfigContext";
|
import { configContext } from "../../../../ConfigContext";
|
||||||
import { armRequest } from "../../request";
|
import { armRequest } from "../../request";
|
||||||
import * as Types from "./types";
|
import * as Types from "./types";
|
||||||
const apiVersion = "2024-02-15-preview";
|
const apiVersion = "2024-12-01-preview";
|
||||||
|
|
||||||
/* Retrieves the metrics determined by the given filter for the given collection, split by partition. */
|
/* Retrieves the metrics determined by the given filter for the given collection, split by partition. */
|
||||||
export async function listMetrics(
|
export async function listMetrics(
|
||||||
|
|||||||
@@ -3,13 +3,13 @@
|
|||||||
Run "npm run generateARMClients" to regenerate
|
Run "npm run generateARMClients" to regenerate
|
||||||
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
|
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
|
||||||
|
|
||||||
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-02-15-preview/cosmos-db.json
|
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
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { configContext } from "../../../../ConfigContext";
|
import { configContext } from "../../../../ConfigContext";
|
||||||
import { armRequest } from "../../request";
|
import { armRequest } from "../../request";
|
||||||
import * as Types from "./types";
|
import * as Types from "./types";
|
||||||
const apiVersion = "2024-02-15-preview";
|
const apiVersion = "2024-12-01-preview";
|
||||||
|
|
||||||
/* Retrieves the metrics determined by the given filter for the given collection and region, split by partition. */
|
/* Retrieves the metrics determined by the given filter for the given collection and region, split by partition. */
|
||||||
export async function listMetrics(
|
export async function listMetrics(
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user