Compare commits

..

48 Commits

Author SHA1 Message Date
Senthamil Sindhu
66a1ca321d Add changes for ARM/AAD Token renewal 2024-11-21 16:01:49 -08:00
Senthamil Sindhu
d12f10afd8 Merge branch 'master' of https://github.com/Azure/cosmos-explorer 2024-11-19 11:50:50 -08:00
Senthamil Sindhu
b456e53b2f Merge branch 'master' of https://github.com/Azure/cosmos-explorer 2024-10-10 00:16:31 -07:00
Senthamil Sindhu
de5ba041e9 Merge branch 'master' of https://github.com/Azure/cosmos-explorer 2024-10-08 14:40:04 -07:00
Senthamil Sindhu
ae7184f7ea Merge branch 'master' of https://github.com/Azure/cosmos-explorer 2024-10-04 15:50:19 -07:00
Senthamil Sindhu
3a6769280b t Merge branch 'master' of https://github.com/Azure/cosmos-explorer 2024-10-02 19:56:51 -07:00
Senthamil Sindhu
4768ba3642 Merge branch 'master' of https://github.com/Azure/cosmos-explorer 2024-10-01 19:55:09 -07:00
Senthamil Sindhu
bd564c665b Merge branch 'master' of https://github.com/Azure/cosmos-explorer 2024-09-17 16:12:01 -07:00
Senthamil Sindhu
7e5c6420ad Run npm format 2024-08-21 13:36:46 -07:00
Senthamil Sindhu
89a3a040d8 Add AAD endpoints 2024-08-21 13:31:02 -07:00
Senthamil Sindhu
ec3afa0526 Add AAD endpoints for all environments 2024-08-21 10:11:35 -07:00
Senthamil Sindhu
4176a8a9a9 Merge branch 'master' of https://github.com/Azure/cosmos-explorer 2024-08-21 10:11:04 -07:00
Senthamil Sindhu
bc8094f44f Merge branch 'master' of https://github.com/Azure/cosmos-explorer 2024-08-05 10:02:29 -07:00
Senthamil Sindhu
d7825f4f78 Merge branch 'master' of https://github.com/Azure/cosmos-explorer 2024-07-30 08:23:50 -07:00
Senthamil Sindhu
e51c28c634 Merge branch 'master' of https://github.com/Azure/cosmos-explorer 2024-07-24 12:32:54 -07:00
Senthamil Sindhu
2b84af60f4 Merge branch 'master' of https://github.com/Azure/cosmos-explorer 2024-07-19 07:31:11 -07:00
Senthamil Sindhu
29a1a819c3 Merge branch 'master' of https://github.com/Azure/cosmos-explorer 2024-07-16 14:28:50 -07:00
Senthamil Sindhu
5a16eec29d Add more logs for RBAC features 2024-07-10 07:38:37 -07:00
Senthamil Sindhu
2b11e0e52b Add more logs for RBAC feature 2024-07-10 07:30:34 -07:00
Senthamil Sindhu
89374a16ba Merge branch 'master' of https://github.com/Azure/cosmos-explorer 2024-07-09 21:13:41 -07:00
Senthamil Sindhu
dc289ece75 Address feedback 2024-07-09 07:31:09 -07:00
Senthamil Sindhu
1ddd372c6d Add code to fix VCoreMongo/PG bug 2024-07-08 14:46:11 -07:00
Senthamil Sindhu
2740657b4a Remove unnecessary code 2024-07-08 14:39:49 -07:00
Senthamil Sindhu
8c888a751c Remove unnecessary code 2024-07-08 14:26:04 -07:00
Senthamil Sindhu
8140f0edb1 Merge branch 'master' of https://github.com/Azure/cosmos-explorer 2024-07-08 13:45:54 -07:00
Senthamil Sindhu
ab5239df09 Merge branch 'master' of https://github.com/Azure/cosmos-explorer 2024-07-08 13:27:24 -07:00
Senthamil Sindhu
3e48393fbb Merge branch 'master' of https://github.com/Azure/cosmos-explorer 2024-07-03 00:07:17 -07:00
Senthamil Sindhu
0079a9147f Resolved merge conflict 2024-07-01 16:22:04 -07:00
Senthamil Sindhu
912688dc14 Merge branch 'master' of https://github.com/Azure/cosmos-explorer 2024-06-27 11:00:31 -07:00
Senthamil Sindhu
8849526fab Merge branch 'add-dp-rbac' of https://github.com/Azure/cosmos-explorer 2024-06-19 15:20:20 -07:00
Senthamil Sindhu
24af64a66d Add additional changes for Portal RBAC functionality 2024-06-19 15:05:14 -07:00
Senthamil Sindhu
be871737ad Support data plane RBAC 2024-06-14 12:45:21 -07:00
Senthamil Sindhu
4d8bb5c3ea Merge branch 'master' of https://github.com/Azure/cosmos-explorer 2024-06-14 12:18:14 -07:00
Senthamil Sindhu
10a8505b9a Support data plane RBAC 2024-06-14 12:12:30 -07:00
Senthamil Sindhu
ef7c2fe2f7 Remove dev endpoint 2024-04-10 11:59:57 -07:00
Senthamil Sindhu
4c7aca95e1 Merge branch 'users/aisayas/mp-cp-activate-prod' of https://github.com/Azure/cosmos-explorer into users/sindhuba/activate-prod 2024-04-09 12:27:51 -07:00
Senthamil Sindhu
2243ad895a Remove prod endpoint 2024-04-09 12:16:13 -07:00
Senthamil Sindhu
b2d5f91fe1 Remove prod 2024-04-09 11:22:17 -07:00
Asier Isayas
a712193477 fix pr check tests 2024-04-09 11:43:24 -04:00
Senthamil Sindhu
5ee411693c Add prod endpoint 2024-04-09 08:41:47 -07:00
Asier Isayas
16c7b2567b fix bug that blocked local mongo proxy and cassandra proxy development 2024-04-09 11:39:11 -04:00
Senthamil Sindhu
78d9a0cd8d Revert code 2024-04-08 16:20:40 -07:00
Senthamil Sindhu
c6ad538559 Run npm format and tests 2024-04-08 15:58:10 -07:00
Senthamil Sindhu
2bc09a6efe Add CP Prod endpoint 2024-04-08 15:37:19 -07:00
Senthamil Sindhu
d3a3033b25 Merge branch 'master' of https://github.com/Azure/cosmos-explorer 2024-04-08 15:32:50 -07:00
Asier Isayas
6bdc714e11 activate Mongo Proxy and Cassandra Proxy in Prod 2024-04-08 16:52:09 -04:00
Senthamil Sindhu
5042f28229 Merge branch 'master' of https://github.com/Azure/cosmos-explorer 2024-03-25 15:11:53 -07:00
Senthamil Sindhu
e1430fd06f Fix API endpoint for CassandraProxy query API 2024-03-18 10:25:17 -07:00
55 changed files with 1569 additions and 1887 deletions

View File

@@ -1 +1 @@
[Preview this branch](https://dataexplorer-preview.azurewebsites.net/pull/EDIT_THIS_NUMBER_IN_THE_PR_DESCRIPTION?feature.someFeatureFlagYouMightNeed=true) [Preview this branch](https://cosmos-explorer-preview.azurewebsites.net/pull/EDIT_THIS_NUMBER_IN_THE_PR_DESCRIPTION?feature.someFeatureFlagYouMightNeed=true)

View File

@@ -96,16 +96,14 @@ 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 ${{ secrets.PREVIEW_STORAGE_ACCOUNT_NAME }} --destination-path "${{github.event.pull_request.head.sha || github.sha}}" --auth-mode login --overwrite true 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
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 ${{ secrets.PREVIEW_STORAGE_ACCOUNT_NAME }} --name "${{github.event.pull_request.head.sha || github.sha}}/config.json" --auth-mode login --overwrite true run: az storage blob upload -c '$web' -f ./preview/config.json --account-name cosmosexplorerpreview --name "${{github.event.pull_request.head.sha || github.sha}}/config.json" --account-key="${PREVIEW_STORAGE_KEY}" --overwrite true
env:
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/')
@@ -115,21 +113,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@v1
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: dotnet nuget add source "$NUGET_SOURCE" --name "ADO" --username "jawelton@microsoft.com" --password "$AZURE_DEVOPS_PAT" --store-password-in-clear-text - run: nuget sources add -Name "ADO" -Source "$NUGET_SOURCE" -UserName "jawelton@microsoft.com" -Password "$AZURE_DEVOPS_PAT"
- run: dotnet pack DataExplorer.proj /p:PackageVersion="2.0.0-github-${GITHUB_SHA}" - run: nuget pack -Version "2.0.0-github-${GITHUB_SHA}"
- run: dotnet nuget push "bin/Release/*.nupkg" --skip-duplicate --api-key Az --source="$NUGET_SOURCE" - run: nuget push -SkipDuplicate -Source "$NUGET_SOURCE" -ApiKey Az *.nupkg
- run: dotnet nuget remove source "ADO"
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v4
name: Upload package to Artifacts name: packages
with: with:
name: prod-package path: "*.nupkg"
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/')
@@ -139,21 +137,22 @@ 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@v1
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: dotnet nuget add source "$NUGET_SOURCE" --name "ADO" --username "jawelton@microsoft.com" --password "$AZURE_DEVOPS_PAT" --store-password-in-clear-text - run: nuget sources add -Name "ADO" -Source "$NUGET_SOURCE" -UserName "jawelton@microsoft.com" -Password "$AZURE_DEVOPS_PAT"
- run: dotnet pack DataExplorer.proj /p:PackageVersion="2.0.0-github-${GITHUB_SHA}" - run: nuget pack -Version "2.0.0-github-${GITHUB_SHA}"
- run: dotnet nuget push "bin/Release/*.nupkg" --skip-duplicate --api-key Az --source="$NUGET_SOURCE" - run: nuget push -SkipDuplicate -Source "$NUGET_SOURCE" -ApiKey Az *.nupkg
- run: dotnet nuget remove source "ADO"
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v4
name: Upload package to Artifacts name: packages
with: with:
name: mpac-package path: "*.nupkg"
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 }})"
@@ -186,9 +185,9 @@ jobs:
if: ${{ !cancelled() }} if: ${{ !cancelled() }}
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: blob-report-${{ matrix.shardIndex }} name: blob-report-${{ matrix.shardIndex }}
path: blob-report path: blob-report
retention-days: 1 retention-days: 1
merge-playwright-reports: merge-playwright-reports:
name: "Merge Playwright Reports" name: "Merge Playwright Reports"
@@ -198,26 +197,26 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: with:
node-version: 18 node-version: 18
- name: Install dependencies - name: Install dependencies
run: npm ci run: npm ci
- name: Download blob reports from GitHub Actions Artifacts - name: Download blob reports from GitHub Actions Artifacts
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
with: with:
path: all-blob-reports path: all-blob-reports
pattern: blob-report-* pattern: blob-report-*
merge-multiple: true merge-multiple: true
- name: Merge into HTML Report - name: Merge into HTML Report
run: npx playwright merge-reports --reporter html ./all-blob-reports run: npx playwright merge-reports --reporter html ./all-blob-reports
- name: Upload HTML report - name: Upload HTML report
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: html-report--attempt-${{ github.run_attempt }} name: html-report--attempt-${{ github.run_attempt }}
path: playwright-report path: playwright-report
retention-days: 14 retention-days: 14

View File

@@ -1,9 +0,0 @@
<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>

View File

@@ -1830,14 +1830,6 @@ 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;

615
package-lock.json generated
View File

@@ -12,7 +12,8 @@
"@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": "4.5.0", "@azure/identity": "1.5.2",
"@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",
@@ -121,7 +122,7 @@
"@babel/preset-env": "7.24.7", "@babel/preset-env": "7.24.7",
"@babel/preset-react": "7.24.7", "@babel/preset-react": "7.24.7",
"@babel/preset-typescript": "7.24.7", "@babel/preset-typescript": "7.24.7",
"@playwright/test": "1.49.1", "@playwright/test": "1.44.0",
"@testing-library/react": "11.2.3", "@testing-library/react": "11.2.3",
"@types/applicationinsights-js": "1.0.7", "@types/applicationinsights-js": "1.0.7",
"@types/codemirror": "0.0.56", "@types/codemirror": "0.0.56",
@@ -427,68 +428,47 @@
"license": "0BSD" "license": "0BSD"
}, },
"node_modules/@azure/identity": { "node_modules/@azure/identity": {
"version": "4.5.0", "version": "1.5.2",
"resolved": "https://registry.npmjs.org/@azure/identity/-/identity-4.5.0.tgz", "license": "MIT",
"integrity": "sha512-EknvVmtBuSIic47xkOqyNabAme0RYTw52BTMz8eBgU1ysTyMrD1uOoM+JdS0J/4Yfp98IBT3osqq3BfwSaNaGQ==",
"dependencies": { "dependencies": {
"@azure/abort-controller": "^2.0.0", "@azure/core-auth": "^1.3.0",
"@azure/core-auth": "^1.9.0", "@azure/core-client": "^1.0.0",
"@azure/core-client": "^1.9.2", "@azure/core-rest-pipeline": "^1.1.0",
"@azure/core-rest-pipeline": "^1.17.0", "@azure/core-tracing": "1.0.0-preview.12",
"@azure/core-tracing": "^1.0.0",
"@azure/core-util": "^1.11.0",
"@azure/logger": "^1.0.0", "@azure/logger": "^1.0.0",
"@azure/msal-browser": "^3.26.1", "@azure/msal-node": "1.0.0-beta.6",
"@azure/msal-node": "^2.15.0", "@types/stoppable": "^1.1.0",
"axios": "^0.21.1",
"events": "^3.0.0", "events": "^3.0.0",
"jws": "^4.0.0", "jws": "^4.0.0",
"open": "^8.0.0", "msal": "^1.0.2",
"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": ">=18.0.0" "node": ">=12.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.8.1", "version": "2.6.2",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "license": "0BSD"
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
}, },
"node_modules/@azure/logger": { "node_modules/@azure/logger": {
"version": "1.0.4", "version": "1.0.4",
@@ -504,6 +484,10 @@
"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",
@@ -575,6 +559,15 @@
"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",
@@ -596,24 +589,13 @@
} }
}, },
"node_modules/@azure/msal-node": { "node_modules/@azure/msal-node": {
"version": "2.16.2", "version": "1.0.0-beta.6",
"resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-2.16.2.tgz", "license": "MIT",
"integrity": "sha512-An7l1hEr0w1HMMh1LU+rtDtqL7/jw74ORlc9Wnh06v7TU/xpG39/Zdr1ZJu3QpjUfKJ+E0/OXMW8DRSWTlh7qQ==",
"dependencies": { "dependencies": {
"@azure/msal-common": "14.16.0", "@azure/msal-common": "^4.0.0",
"jsonwebtoken": "^9.0.0", "axios": "^0.21.1",
"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": {
@@ -10091,6 +10073,13 @@
"@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"
@@ -10186,18 +10175,34 @@
} }
}, },
"node_modules/@playwright/test": { "node_modules/@playwright/test": {
"version": "1.49.1", "version": "1.44.0",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.49.1.tgz",
"integrity": "sha512-Ky+BVzPz8pL6PQxHqNRW1k3mIyv933LML7HktS8uik0bUXNCdPhoS/kLihiO1tMf/egaJb4IutXd7UywvXEW+g==",
"dev": true, "dev": true,
"license": "Apache-2.0",
"dependencies": { "dependencies": {
"playwright": "1.49.1" "playwright": "1.44.0"
}, },
"bin": { "bin": {
"playwright": "cli.js" "playwright": "cli.js"
}, },
"engines": { "engines": {
"node": ">=18" "node": ">=16"
}
},
"node_modules/@playwright/test/node_modules/playwright": {
"version": "1.44.0",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.44.0"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=16"
},
"optionalDependencies": {
"fsevents": "2.3.2"
} }
}, },
"node_modules/@polka/url": { "node_modules/@polka/url": {
@@ -12736,6 +12741,13 @@
"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,
@@ -13338,6 +13350,61 @@
"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,
@@ -13970,6 +14037,13 @@
"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,
@@ -14673,7 +14747,7 @@
}, },
"node_modules/base64-js": { "node_modules/base64-js": {
"version": "1.5.1", "version": "1.5.1",
"dev": true, "devOptional": true,
"funding": [ "funding": [
{ {
"type": "github", "type": "github",
@@ -14730,24 +14804,14 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/bindings": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
"optional": true,
"dependencies": {
"file-uri-to-path": "1.0.0"
}
},
"node_modules/bintrees": { "node_modules/bintrees": {
"version": "1.0.2", "version": "1.0.2",
"license": "MIT" "license": "MIT"
}, },
"node_modules/bl": { "node_modules/bl": {
"version": "4.1.0", "version": "4.1.0",
"dev": true, "devOptional": 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",
@@ -14756,7 +14820,7 @@
}, },
"node_modules/bl/node_modules/buffer": { "node_modules/bl/node_modules/buffer": {
"version": "5.7.1", "version": "5.7.1",
"dev": true, "devOptional": true,
"funding": [ "funding": [
{ {
"type": "github", "type": "github",
@@ -14772,7 +14836,6 @@
} }
], ],
"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"
@@ -14780,9 +14843,8 @@
}, },
"node_modules/bl/node_modules/readable-stream": { "node_modules/bl/node_modules/readable-stream": {
"version": "3.6.2", "version": "3.6.2",
"dev": true, "devOptional": 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",
@@ -15352,6 +15414,11 @@
"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",
@@ -17115,6 +17182,13 @@
"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"
@@ -17209,6 +17283,14 @@
"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"
@@ -17269,6 +17351,7 @@
}, },
"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"
@@ -18900,6 +18983,14 @@
"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",
@@ -19375,12 +19466,6 @@
"version": "2.0.5", "version": "2.0.5",
"license": "MIT" "license": "MIT"
}, },
"node_modules/file-uri-to-path": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
"optional": true
},
"node_modules/filesize": { "node_modules/filesize": {
"version": "8.0.7", "version": "8.0.7",
"dev": true, "dev": true,
@@ -19587,7 +19672,6 @@
}, },
"node_modules/follow-redirects": { "node_modules/follow-redirects": {
"version": "1.15.3", "version": "1.15.3",
"dev": true,
"funding": [ "funding": [
{ {
"type": "individual", "type": "individual",
@@ -19895,6 +19979,11 @@
"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,
@@ -19965,19 +20054,6 @@
"version": "1.0.0", "version": "1.0.0",
"license": "ISC" "license": "ISC"
}, },
"node_modules/fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"hasInstallScript": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/function-bind": { "node_modules/function-bind": {
"version": "1.1.2", "version": "1.1.2",
"license": "MIT", "license": "MIT",
@@ -20109,6 +20185,11 @@
"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",
@@ -21240,7 +21321,7 @@
}, },
"node_modules/ieee754": { "node_modules/ieee754": {
"version": "1.2.1", "version": "1.2.1",
"dev": true, "devOptional": true,
"funding": [ "funding": [
{ {
"type": "github", "type": "github",
@@ -21429,7 +21510,7 @@
}, },
"node_modules/ini": { "node_modules/ini": {
"version": "1.3.8", "version": "1.3.8",
"dev": true, "devOptional": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/internal-slot": { "node_modules/internal-slot": {
@@ -24079,24 +24160,6 @@
"fsevents": "^1.2.7" "fsevents": "^1.2.7"
} }
}, },
"node_modules/jest-haste-map/node_modules/fsevents": {
"version": "1.2.13",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz",
"integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==",
"deprecated": "Upgrade to fsevents v2 to mitigate potential security issues",
"hasInstallScript": true,
"optional": true,
"os": [
"darwin"
],
"dependencies": {
"bindings": "^1.5.0",
"nan": "^2.12.1"
},
"engines": {
"node": ">= 4.0"
}
},
"node_modules/jest-haste-map/node_modules/jest-worker": { "node_modules/jest-haste-map/node_modules/jest-worker": {
"version": "24.9.0", "version": "24.9.0",
"license": "MIT", "license": "MIT",
@@ -27465,9 +27528,8 @@
} }
}, },
"node_modules/jsonwebtoken": { "node_modules/jsonwebtoken": {
"version": "9.0.2", "version": "8.5.1",
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", "license": "MIT",
"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",
@@ -27478,17 +27540,16 @@
"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": "^7.5.4" "semver": "^5.6.0"
}, },
"engines": { "engines": {
"node": ">=12", "node": ">=4",
"npm": ">=6" "npm": ">=1.4.28"
} }
}, },
"node_modules/jsonwebtoken/node_modules/jwa": { "node_modules/jsonwebtoken/node_modules/jwa": {
"version": "1.4.1", "version": "1.4.1",
"resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", "license": "MIT",
"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",
@@ -27497,24 +27558,12 @@
}, },
"node_modules/jsonwebtoken/node_modules/jws": { "node_modules/jsonwebtoken/node_modules/jws": {
"version": "3.2.2", "version": "3.2.2",
"resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", "license": "MIT",
"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",
@@ -27567,6 +27616,16 @@
"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",
@@ -27883,8 +27942,7 @@
}, },
"node_modules/lodash.includes": { "node_modules/lodash.includes": {
"version": "4.3.0", "version": "4.3.0",
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", "license": "MIT"
"integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="
}, },
"node_modules/lodash.invokemap": { "node_modules/lodash.invokemap": {
"version": "4.6.0", "version": "4.6.0",
@@ -27893,8 +27951,7 @@
}, },
"node_modules/lodash.isboolean": { "node_modules/lodash.isboolean": {
"version": "3.0.3", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", "license": "MIT"
"integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="
}, },
"node_modules/lodash.isequal": { "node_modules/lodash.isequal": {
"version": "4.5.0", "version": "4.5.0",
@@ -27902,23 +27959,19 @@
}, },
"node_modules/lodash.isinteger": { "node_modules/lodash.isinteger": {
"version": "4.0.4", "version": "4.0.4",
"resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", "license": "MIT"
"integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA=="
}, },
"node_modules/lodash.isnumber": { "node_modules/lodash.isnumber": {
"version": "3.0.3", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", "license": "MIT"
"integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw=="
}, },
"node_modules/lodash.isplainobject": { "node_modules/lodash.isplainobject": {
"version": "4.0.6", "version": "4.0.6",
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", "license": "MIT"
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="
}, },
"node_modules/lodash.isstring": { "node_modules/lodash.isstring": {
"version": "4.0.1", "version": "4.0.1",
"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", "license": "MIT"
"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",
@@ -27930,8 +27983,7 @@
}, },
"node_modules/lodash.once": { "node_modules/lodash.once": {
"version": "4.1.1", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", "license": "MIT"
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="
}, },
"node_modules/lodash.pullall": { "node_modules/lodash.pullall": {
"version": "4.2.0", "version": "4.2.0",
@@ -29444,6 +29496,11 @@
"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",
@@ -29510,6 +29567,16 @@
"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,
@@ -29570,6 +29637,11 @@
"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,
@@ -29654,12 +29726,58 @@
"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,
@@ -30680,34 +30798,15 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/playwright": {
"version": "1.49.1",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.49.1.tgz",
"integrity": "sha512-VYL8zLoNTBxVOrJBbDuRgDWa3i+mfQgDTrL8Ah9QXZ7ax4Dsj0MSq5bYgytRnDVVe+njoKnfsYkH3HzqVj5UZA==",
"dev": true,
"dependencies": {
"playwright-core": "1.49.1"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"fsevents": "2.3.2"
}
},
"node_modules/playwright-core": { "node_modules/playwright-core": {
"version": "1.49.1", "version": "1.44.0",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.49.1.tgz",
"integrity": "sha512-BzmpVcs4kE2CH15rWfzpjzVGhWERJfmnXmniSyKeRZUs9Ws65m+RGIi7mjJK/euCegfn3i7jvqWeWyHe9y3Vgg==",
"dev": true, "dev": true,
"license": "Apache-2.0",
"bin": { "bin": {
"playwright-core": "cli.js" "playwright-core": "cli.js"
}, },
"engines": { "engines": {
"node": ">=18" "node": ">=16"
} }
}, },
"node_modules/plotly.js-cartesian-dist-min": { "node_modules/plotly.js-cartesian-dist-min": {
@@ -30858,6 +30957,80 @@
"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",
@@ -31287,6 +31460,28 @@
"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",
@@ -34195,6 +34390,45 @@
"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",
@@ -36532,6 +36766,13 @@
"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",

View File

@@ -7,7 +7,8 @@
"@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": "4.5.0", "@azure/identity": "1.5.2",
"@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",
@@ -116,7 +117,7 @@
"@babel/preset-env": "7.24.7", "@babel/preset-env": "7.24.7",
"@babel/preset-react": "7.24.7", "@babel/preset-react": "7.24.7",
"@babel/preset-typescript": "7.24.7", "@babel/preset-typescript": "7.24.7",
"@playwright/test": "1.49.1", "@playwright/test": "1.44.0",
"@testing-library/react": "11.2.3", "@testing-library/react": "11.2.3",
"@types/applicationinsights-js": "1.0.7", "@types/applicationinsights-js": "1.0.7",
"@types/codemirror": "0.0.56", "@types/codemirror": "0.0.56",
@@ -169,10 +170,10 @@
"jest": "29.7.0", "jest": "29.7.0",
"jest-canvas-mock": "2.5.2", "jest-canvas-mock": "2.5.2",
"jest-circus": "29.7.0", "jest-circus": "29.7.0",
"jest-environment-jsdom": "29.7.0",
"jest-html-loader": "1.0.0", "jest-html-loader": "1.0.0",
"jest-react-hooks-shallow": "1.5.1", "jest-react-hooks-shallow": "1.5.1",
"jest-trx-results-processor": "3.0.2", "jest-trx-results-processor": "3.0.2",
"jest-environment-jsdom": "29.7.0",
"less": "3.8.1", "less": "3.8.1",
"less-loader": "11.1.3", "less-loader": "11.1.3",
"less-vars-loader": "1.1.0", "less-vars-loader": "1.1.0",

View File

@@ -1,7 +1,7 @@
[defaults] [defaults]
group = dataexplorer-preview group = stfaul
sku = P1V2 sku = P1v2
appserviceplan = dataexplorer-preview appserviceplan = stfaul_asp_Linux_centralus_0
location = westus2 location = centralus
web = dataexplorer-preview web = cosmos-explorer-preview

View File

@@ -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://dataexplorer-preview.azurewebsites.net/commit/COMMIT_SHA/hostedExplorer.html Connection string URLs: https://cosmos-explorer-preview.azurewebsites.net/commit/COMMIT_SHA/hostedExplorer.html
Portal URLs: https://ms.portal.azure.com/?dataExplorerSource=https://dataexplorer-preview.azurewebsites.net/commit/COMMIT_SHA/explorer.html#home Portal URLs: https://ms.portal.azure.com/?dataExplorerSource=https://cosmos-explorer-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.

View File

@@ -1,4 +1,4 @@
{ {
"PROXY_PATH": "/proxy", "PROXY_PATH": "/proxy",
"msalRedirectURI": "https://dataexplorer-preview.azurewebsites.net/" "msalRedirectURI": "https://cosmos-explorer-preview.azurewebsites.net/"
} }

View File

@@ -3,15 +3,8 @@ 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 backendEndpoint = "https://cdb-ms-mpac-pbe.cosmos.azure.com"; const api = createProxyMiddleware("/api", {
const previewSiteEndpoint = "https://dataexplorer-preview.azurewebsites.net"; target: "https://cdb-ms-mpac-pbe.cosmos.azure.com",
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) => {
@@ -22,8 +15,8 @@ const api = createProxyMiddleware({
}, },
}); });
const proxy = createProxyMiddleware({ const proxy = createProxyMiddleware("/proxy", {
target: backendEndpoint, target: "https://cdb-ms-mpac-pbe.cosmos.azure.com",
changeOrigin: true, changeOrigin: true,
secure: false, secure: false,
logLevel: "debug", logLevel: "debug",
@@ -34,38 +27,35 @@ const proxy = createProxyMiddleware({
}, },
}); });
const commit = createProxyMiddleware({ const commit = createProxyMiddleware("/commit", {
target: previewStorageWebsiteEndpoint, target: "https://cosmosexplorerpreview.blob.core.windows.net",
changeOrigin: true, changeOrigin: true,
secure: false, secure: false,
logLevel: "debug", logLevel: "debug",
pathRewrite: { "^/commit": "/" }, pathRewrite: { "^/commit": "$web/" },
}); });
const app = express(); const app = express();
app.use("/api", api); app.use(api);
app.use("/proxy", proxy); app.use(proxy);
app.use("/commit", commit); app.use(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(`${githubApiUrl}/pulls/${pr}`) fetch("https://api.github.com/repos/Azure/cosmos-explorer/pulls/" + pr)
.then((response) => response.json()) .then((response) => response.json())
.then(({ head: { ref, sha } }) => { .then(({ head: { ref, sha } }) => {
const prUrl = new URL(`${githubPullRequestUrl}/${pr}`); const prUrl = new URL("https://github.com/Azure/cosmos-explorer/pull/" + pr);
prUrl.hash = ref; prUrl.hash = ref;
search.set("feature.pr", prUrl.href); search.set("feature.pr", prUrl.href);
const explorer = new URL(`${previewSiteEndpoint}/commit/${sha}/explorer.html`); const explorer = new URL("https://cosmos-explorer-preview.azurewebsites.net/commit/" + sha + "/explorer.html");
explorer.search = search.toString(); explorer.search = search.toString();
const portal = new URL(azurePortalMpacEndpoint); const portal = new URL("https://ms.portal.azure.com/");
portal.searchParams.set("dataExplorerSource", explorer.href); portal.searchParams.set("dataExplorerSource", explorer.href);
return res.redirect(portal.href); return res.redirect(portal.href);
@@ -73,10 +63,12 @@ app.get("/pull/:pr(\\d+)", (req, res) => {
.catch(() => res.sendStatus(500)); .catch(() => res.sendStatus(500));
}); });
app.get("/", (req, res) => { app.get("/", (req, res) => {
fetch(`${githubApiUrl}/branches/master`) fetch("https://api.github.com/repos/Azure/cosmos-explorer/branches/master")
.then((response) => response.json()) .then((response) => response.json())
.then(({ commit: { sha } }) => { .then(({ commit: { sha } }) => {
const explorer = new URL(`${previewSiteEndpoint}/commit/${sha}/hostedExplorer.html`); const explorer = new URL(
"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));

1358
preview/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -4,7 +4,7 @@
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"deploy": "az webapp up --name \"dataexplorer-preview\" --subscription \"cosmosdb-portalteam-runners\" --resource-group \"dataexplorer-preview\" --runtime \"NODE:18-lts\" --sku P1V2", "deploy": "az webapp up --name \"cosmos-explorer-preview\" --subscription \"cosmosdb-portalteam-generaltest-msft\" --resource-group \"stfaul\"",
"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,8 +12,7 @@
"author": "Microsoft Corporation", "author": "Microsoft Corporation",
"dependencies": { "dependencies": {
"express": "^4.17.1", "express": "^4.17.1",
"http-proxy-middleware": "^3.0.3", "http-proxy-middleware": "^1.1.0",
"node": "^18.20.6",
"node-fetch": "^2.6.1" "node-fetch": "^2.6.1"
} }
} }

View File

@@ -149,7 +149,7 @@ export class PortalBackendEndpoints {
} }
export class MongoProxyEndpoints { export class MongoProxyEndpoints {
public static readonly Development: string = "https://localhost:7238"; public static readonly Local: string = "https://localhost:7238";
public static readonly Mpac: string = "https://cdb-ms-mpac-mp.cosmos.azure.com"; public static readonly Mpac: string = "https://cdb-ms-mpac-mp.cosmos.azure.com";
public static readonly Prod: string = "https://cdb-ms-prod-mp.cosmos.azure.com"; public static readonly Prod: string = "https://cdb-ms-prod-mp.cosmos.azure.com";
public static readonly Fairfax: string = "https://cdb-ff-prod-mp.cosmos.azure.us"; public static readonly Fairfax: string = "https://cdb-ff-prod-mp.cosmos.azure.us";
@@ -518,11 +518,6 @@ 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 const QueryCopilotSampleDatabaseId = "CopilotSampleDB"; export const QueryCopilotSampleDatabaseId = "CopilotSampleDB";
export const QueryCopilotSampleContainerId = "SampleContainer"; export const QueryCopilotSampleContainerId = "SampleContainer";

View File

@@ -13,6 +13,7 @@ import { logConsoleError } from "../Utils/NotificationConsoleUtils";
import * as PriorityBasedExecutionUtils from "../Utils/PriorityBasedExecutionUtils"; import * as PriorityBasedExecutionUtils from "../Utils/PriorityBasedExecutionUtils";
import { EmulatorMasterKey, HttpHeaders } from "./Constants"; import { EmulatorMasterKey, HttpHeaders } from "./Constants";
import { getErrorMessage } from "./ErrorHandlingUtils"; import { getErrorMessage } from "./ErrorHandlingUtils";
import { runCommand } from "hooks/useDatabaseAccounts";
const _global = typeof self === "undefined" ? window : self; const _global = typeof self === "undefined" ? window : self;
@@ -32,6 +33,7 @@ export const tokenProvider = async (requestInfo: Cosmos.RequestInfo) => {
return null; return null;
} }
const AUTH_PREFIX = `type=aad&ver=1.0&sig=`; const AUTH_PREFIX = `type=aad&ver=1.0&sig=`;
console.log("AAD Token ", userContext.aadToken);
const authorizationToken = `${AUTH_PREFIX}${userContext.aadToken}`; const authorizationToken = `${AUTH_PREFIX}${userContext.aadToken}`;
return authorizationToken; return authorizationToken;
} }
@@ -189,19 +191,10 @@ let _client: Cosmos.CosmosClient;
export function client(): Cosmos.CosmosClient { export function client(): Cosmos.CosmosClient {
if (_client) { if (_client) {
if (!userContext.refreshCosmosClient) { if (!userContext.hasDataPlaneRbacSettingChanged) {
return _client; return _client;
} }
_client.dispose();
_client = null;
} }
if (userContext.refreshCosmosClient) {
updateUserContext({
refreshCosmosClient: false,
});
}
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;
@@ -218,10 +211,14 @@ export function client(): Cosmos.CosmosClient {
_defaultHeaders["x-ms-cosmos-priority-level"] = PriorityLevel.Default; _defaultHeaders["x-ms-cosmos-priority-level"] = PriorityLevel.Default;
} }
const wrappedTokenProvider = async (requestInfo: Cosmos.RequestInfo) => {
return await runCommand(tokenProvider, requestInfo);
};
const options: Cosmos.CosmosClientOptions = { const options: Cosmos.CosmosClientOptions = {
endpoint: endpoint() || "https://cosmos.azure.com", // CosmosClient gets upset if we pass a bad URL. This should never actually get called endpoint: endpoint() || "https://cosmos.azure.com", // CosmosClient gets upset if we pass a bad URL. This should never actually get called
key: userContext.dataPlaneRbacEnabled ? "" : userContext.masterKey, key: userContext.dataPlaneRbacEnabled ? "" : userContext.masterKey,
tokenProvider, tokenProvider: wrappedTokenProvider,
userAgentSuffix: "Azure Portal", userAgentSuffix: "Azure Portal",
defaultHeaders: _defaultHeaders, defaultHeaders: _defaultHeaders,
connectionPolicy: { connectionPolicy: {

View File

@@ -722,63 +722,63 @@ export function getEndpoint(endpoint: string): string {
export function useMongoProxyEndpoint(mongoProxyApi: string): boolean { export function useMongoProxyEndpoint(mongoProxyApi: string): boolean {
const mongoProxyEnvironmentMap: { [key: string]: string[] } = { const mongoProxyEnvironmentMap: { [key: string]: string[] } = {
[MongoProxyApi.ResourceList]: [ [MongoProxyApi.ResourceList]: [
MongoProxyEndpoints.Development, MongoProxyEndpoints.Local,
MongoProxyEndpoints.Mpac, MongoProxyEndpoints.Mpac,
MongoProxyEndpoints.Prod, MongoProxyEndpoints.Prod,
MongoProxyEndpoints.Fairfax, MongoProxyEndpoints.Fairfax,
MongoProxyEndpoints.Mooncake, MongoProxyEndpoints.Mooncake,
], ],
[MongoProxyApi.QueryDocuments]: [ [MongoProxyApi.QueryDocuments]: [
MongoProxyEndpoints.Development, MongoProxyEndpoints.Local,
MongoProxyEndpoints.Mpac, MongoProxyEndpoints.Mpac,
MongoProxyEndpoints.Prod, MongoProxyEndpoints.Prod,
MongoProxyEndpoints.Fairfax, MongoProxyEndpoints.Fairfax,
MongoProxyEndpoints.Mooncake, MongoProxyEndpoints.Mooncake,
], ],
[MongoProxyApi.CreateDocument]: [ [MongoProxyApi.CreateDocument]: [
MongoProxyEndpoints.Development, MongoProxyEndpoints.Local,
MongoProxyEndpoints.Mpac, MongoProxyEndpoints.Mpac,
MongoProxyEndpoints.Prod, MongoProxyEndpoints.Prod,
MongoProxyEndpoints.Fairfax, MongoProxyEndpoints.Fairfax,
MongoProxyEndpoints.Mooncake, MongoProxyEndpoints.Mooncake,
], ],
[MongoProxyApi.ReadDocument]: [ [MongoProxyApi.ReadDocument]: [
MongoProxyEndpoints.Development, MongoProxyEndpoints.Local,
MongoProxyEndpoints.Mpac, MongoProxyEndpoints.Mpac,
MongoProxyEndpoints.Prod, MongoProxyEndpoints.Prod,
MongoProxyEndpoints.Fairfax, MongoProxyEndpoints.Fairfax,
MongoProxyEndpoints.Mooncake, MongoProxyEndpoints.Mooncake,
], ],
[MongoProxyApi.UpdateDocument]: [ [MongoProxyApi.UpdateDocument]: [
MongoProxyEndpoints.Development, MongoProxyEndpoints.Local,
MongoProxyEndpoints.Mpac, MongoProxyEndpoints.Mpac,
MongoProxyEndpoints.Prod, MongoProxyEndpoints.Prod,
MongoProxyEndpoints.Fairfax, MongoProxyEndpoints.Fairfax,
MongoProxyEndpoints.Mooncake, MongoProxyEndpoints.Mooncake,
], ],
[MongoProxyApi.DeleteDocument]: [ [MongoProxyApi.DeleteDocument]: [
MongoProxyEndpoints.Development, MongoProxyEndpoints.Local,
MongoProxyEndpoints.Mpac, MongoProxyEndpoints.Mpac,
MongoProxyEndpoints.Prod, MongoProxyEndpoints.Prod,
MongoProxyEndpoints.Fairfax, MongoProxyEndpoints.Fairfax,
MongoProxyEndpoints.Mooncake, MongoProxyEndpoints.Mooncake,
], ],
[MongoProxyApi.CreateCollectionWithProxy]: [ [MongoProxyApi.CreateCollectionWithProxy]: [
MongoProxyEndpoints.Development, MongoProxyEndpoints.Local,
MongoProxyEndpoints.Mpac, MongoProxyEndpoints.Mpac,
MongoProxyEndpoints.Prod, MongoProxyEndpoints.Prod,
MongoProxyEndpoints.Fairfax, MongoProxyEndpoints.Fairfax,
MongoProxyEndpoints.Mooncake, MongoProxyEndpoints.Mooncake,
], ],
[MongoProxyApi.LegacyMongoShell]: [ [MongoProxyApi.LegacyMongoShell]: [
MongoProxyEndpoints.Development, MongoProxyEndpoints.Local,
MongoProxyEndpoints.Mpac, MongoProxyEndpoints.Mpac,
MongoProxyEndpoints.Prod, MongoProxyEndpoints.Prod,
MongoProxyEndpoints.Fairfax, MongoProxyEndpoints.Fairfax,
MongoProxyEndpoints.Mooncake, MongoProxyEndpoints.Mooncake,
], ],
[MongoProxyApi.BulkDelete]: [ [MongoProxyApi.BulkDelete]: [
MongoProxyEndpoints.Development, MongoProxyEndpoints.Local,
MongoProxyEndpoints.Mpac, MongoProxyEndpoints.Mpac,
MongoProxyEndpoints.Prod, MongoProxyEndpoints.Prod,
MongoProxyEndpoints.Fairfax, MongoProxyEndpoints.Fairfax,

View File

@@ -91,7 +91,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:\\/\\/dataexplorer-preview\\.azurewebsites\\.net$`, `^https:\\/\\/cosmos-explorer-preview\\.azurewebsites\\.net$`,
], // Webpack injects this at build time ], // Webpack injects this at build time
gitSha: process.env.GIT_SHA, gitSha: process.env.GIT_SHA,
hostedExplorerURL: "https://cosmos.azure.com/", hostedExplorerURL: "https://cosmos.azure.com/",

View File

@@ -9,7 +9,6 @@ export enum TabKind {
Graph, Graph,
SQLQuery, SQLQuery,
ScaleSettings, ScaleSettings,
MongoQuery,
} }
/** /**
@@ -52,8 +51,6 @@ export interface OpenCollectionTab extends OpenTab {
*/ */
export interface OpenQueryTab extends OpenCollectionTab { export interface OpenQueryTab extends OpenCollectionTab {
query: QueryInfo; query: QueryInfo;
splitterDirection?: "vertical" | "horizontal";
queryViewSizePercent?: number;
} }
/** /**

View File

@@ -115,13 +115,7 @@ export interface CollectionBase extends TreeNode {
isSampleCollection?: boolean; isSampleCollection?: boolean;
onDocumentDBDocumentsClick(): void; onDocumentDBDocumentsClick(): void;
onNewQueryClick( onNewQueryClick(source: any, event?: MouseEvent, queryText?: string): void;
source: any,
event?: MouseEvent,
queryText?: string,
splitterDirection?: "horizontal" | "vertical",
queryViewSizePercent?: number,
): void;
expandCollection(): void; expandCollection(): void;
collapseCollection(): void; collapseCollection(): void;
getDatabase(): Database; getDatabase(): Database;
@@ -157,13 +151,7 @@ export interface Collection extends CollectionBase {
onSettingsClick: () => Promise<void>; onSettingsClick: () => Promise<void>;
onNewGraphClick(): void; onNewGraphClick(): void;
onNewMongoQueryClick( onNewMongoQueryClick(source: any, event?: MouseEvent, queryText?: string): void;
source: any,
event?: MouseEvent,
queryText?: string,
splitterDirection?: "horizontal" | "vertical",
queryViewSizePercent?: number,
): void;
onNewMongoShellClick(): void; onNewMongoShellClick(): void;
onNewStoredProcedureClick(source: Collection, event?: MouseEvent): void; onNewStoredProcedureClick(source: Collection, event?: MouseEvent): void;
onNewUserDefinedFunctionClick(source: Collection, event?: MouseEvent): void; onNewUserDefinedFunctionClick(source: Collection, event?: MouseEvent): void;
@@ -323,8 +311,6 @@ export interface QueryTabOptions extends TabOptions {
partitionKey?: DataModels.PartitionKey; partitionKey?: DataModels.PartitionKey;
queryText?: string; queryText?: string;
resourceTokenPartitionKey?: string; resourceTokenPartitionKey?: string;
splitterDirection?: "horizontal" | "vertical";
queryViewSizePercent?: number;
} }
export interface ScriptTabOption extends TabOptions { export interface ScriptTabOption extends TabOptions {

View File

@@ -14,7 +14,6 @@ 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 {
@@ -178,14 +177,12 @@ 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>
{configContext.platform !== Platform.Emulator && ( <PrimaryButton
<PrimaryButton styles={{ root: { width: "fit-content" } }}
styles={{ root: { width: "fit-content" } }} text="Change"
text="Change" onClick={startPartitionkeyChangeWorkflow}
onClick={startPartitionkeyChangeWorkflow} disabled={isCurrentJobInProgress(portalDataTransferJob)}
disabled={isCurrentJobInProgress(portalDataTransferJob)} />
/>
)}
{portalDataTransferJob && ( {portalDataTransferJob && (
<Stack> <Stack>
<Text styles={textHeadingStyle}>{partitionKeyName} change job</Text> <Text styles={textHeadingStyle}>{partitionKeyName} change job</Text>

View File

@@ -17,13 +17,14 @@ 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 } from "../../../../../Utils/PricingUtils"; import { calculateEstimateNumber, usageInGB } from "../../../../../Utils/PricingUtils";
import { Int32 } from "../../../../Panes/Tables/Validators/EntityPropertyValidationCommon"; import { Int32 } from "../../../../Panes/Tables/Validators/EntityPropertyValidationCommon";
import { import {
PriceBreakdown, PriceBreakdown,
@@ -365,6 +366,29 @@ 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 (
@@ -637,6 +661,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
</Link> </Link>
</Text> </Text>
)} )}
{this.minRUperGBSurvey()}
{this.props.spendAckVisible && ( {this.props.spendAckVisible && (
<Checkbox <Checkbox
id="spendAckCheckBox" id="spendAckCheckBox"

View File

@@ -1134,7 +1134,7 @@ export default class Explorer {
if (userContext.apiType !== "Postgres" && userContext.apiType !== "VCoreMongo") { if (userContext.apiType !== "Postgres" && userContext.apiType !== "VCoreMongo") {
userContext.authType === AuthType.ResourceToken userContext.authType === AuthType.ResourceToken
? this.refreshDatabaseForResourceToken() ? this.refreshDatabaseForResourceToken()
: await this.refreshAllDatabases(); // await: we rely on the databases to be loaded before restoring the tabs further in the flow : this.refreshAllDatabases();
} }
await useNotebook.getState().refreshNotebooksEnabledStateForAccount(); await useNotebook.getState().refreshNotebooksEnabledStateForAccount();

View File

@@ -1,5 +1,4 @@
// 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 React from "react"; import React from "react";
import { ActionContracts } from "../../Contracts/ExplorerContracts"; import { ActionContracts } from "../../Contracts/ExplorerContracts";
@@ -57,19 +56,6 @@ function openCollectionTab(
continue; continue;
} }
if (
configContext.platform === Platform.Fabric &&
!(
// whitelist the tab kinds that are allowed to be opened in Fabric
(
action.tabKind === ActionContracts.TabKind.SQLDocuments ||
action.tabKind === ActionContracts.TabKind.SQLQuery
)
)
) {
continue;
}
//expand database first if not expanded to load the collections //expand database first if not expanded to load the collections
if (!database.isDatabaseExpanded?.()) { if (!database.isDatabaseExpanded?.()) {
database.expandDatabase?.(); database.expandDatabase?.();
@@ -135,28 +121,10 @@ function openCollectionTab(
action.tabKind === ActionContracts.TabKind.SQLQuery || action.tabKind === ActionContracts.TabKind.SQLQuery ||
action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.SQLQuery] action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.SQLQuery]
) { ) {
const openQueryTabAction = action as ActionContracts.OpenQueryTab;
collection.onNewQueryClick( collection.onNewQueryClick(
collection, collection,
undefined, undefined,
generateQueryText(openQueryTabAction, collection.partitionKeyProperties), generateQueryText(action as ActionContracts.OpenQueryTab, collection.partitionKeyProperties),
openQueryTabAction.splitterDirection,
openQueryTabAction.queryViewSizePercent,
);
break;
}
if (
action.tabKind === ActionContracts.TabKind.MongoQuery ||
action.tabKind === ActionContracts.TabKind[ActionContracts.TabKind.MongoQuery]
) {
const openQueryTabAction = action as ActionContracts.OpenQueryTab;
collection.onNewMongoQueryClick(
collection,
undefined,
generateQueryText(openQueryTabAction, collection.partitionKeyProperties),
openQueryTabAction.splitterDirection,
openQueryTabAction.queryViewSizePercent,
); );
break; break;
} }

View File

@@ -819,9 +819,22 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
{this.shouldShowAnalyticalStoreOptions() && ( {this.shouldShowAnalyticalStoreOptions() && (
<Stack className="panelGroupSpacing"> <Stack className="panelGroupSpacing">
<Text className="panelTextBold" variant="small"> <Stack horizontal>
{this.getAnalyticalStorageContent()} <Text className="panelTextBold" variant="small">
</Text> Analytical store
</Text>
<TooltipHost
directionalHint={DirectionalHint.bottomLeftEdge}
content={this.getAnalyticalStorageTooltipContent()}
>
<Icon
iconName="Info"
className="panelInfoIcon"
tabIndex={0}
ariaLabel="Enable analytical store capability to perform near real-time analytics on your operational data, without impacting the performance of transactional workloads."
/>
</TooltipHost>
</Stack>
<Stack horizontal verticalAlign="center"> <Stack horizontal verticalAlign="center">
<div role="radiogroup"> <div role="radiogroup">
@@ -865,7 +878,6 @@ 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
@@ -1218,16 +1230,12 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
return ""; return "";
} }
private getAnalyticalStorageContent(): JSX.Element { private getAnalyticalStorageTooltipContent(): JSX.Element {
return ( return (
<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 <Link target="_blank" href="https://aka.ms/analytical-store-overview">
aria-label={Constants.ariaLabelForLearnMoreLink.AnalyticalStore}
target="_blank"
href="https://aka.ms/analytical-store-overview"
>
Learn more Learn more
</Link> </Link>
</Text> </Text>

View File

@@ -193,17 +193,6 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
LocalStorageUtility.setEntryNumber(StorageKey.CustomItemPerPage, customItemPerPage); LocalStorageUtility.setEntryNumber(StorageKey.CustomItemPerPage, customItemPerPage);
if (
enableDataPlaneRBACOption !== LocalStorageUtility.getEntryString(StorageKey.DataPlaneRbacEnabled) ||
retryAttempts !== LocalStorageUtility.getEntryNumber(StorageKey.RetryAttempts) ||
retryInterval !== LocalStorageUtility.getEntryNumber(StorageKey.RetryInterval) ||
MaxWaitTimeInSeconds !== LocalStorageUtility.getEntryNumber(StorageKey.MaxWaitTimeInSeconds)
) {
updateUserContext({
refreshCosmosClient: true,
});
}
if (configContext.platform !== Platform.Fabric) { if (configContext.platform !== Platform.Fabric) {
LocalStorageUtility.setEntryString(StorageKey.DataPlaneRbacEnabled, enableDataPlaneRBACOption); LocalStorageUtility.setEntryString(StorageKey.DataPlaneRbacEnabled, enableDataPlaneRBACOption);
if ( if (
@@ -213,6 +202,7 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
) { ) {
updateUserContext({ updateUserContext({
dataPlaneRbacEnabled: true, dataPlaneRbacEnabled: true,
hasDataPlaneRbacSettingChanged: true,
}); });
useDataPlaneRbac.setState({ dataPlaneRbacEnabled: true }); useDataPlaneRbac.setState({ dataPlaneRbacEnabled: true });
try { try {
@@ -236,6 +226,7 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
} else { } else {
updateUserContext({ updateUserContext({
dataPlaneRbacEnabled: false, dataPlaneRbacEnabled: false,
hasDataPlaneRbacSettingChanged: true,
}); });
const { databaseAccount: account, subscriptionId, resourceGroup } = userContext; const { databaseAccount: account, subscriptionId, resourceGroup } = userContext;
if (!userContext.features.enableAadDataPlane && !userContext.masterKey) { if (!userContext.features.enableAadDataPlane && !userContext.masterKey) {
@@ -491,7 +482,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={`customAccordion ${styles.firstItem}`}> <Accordion className={styles.firstItem}>
{shouldShowQueryPageOptions && ( {shouldShowQueryPageOptions && (
<AccordionItem value="1"> <AccordionItem value="1">
<AccordionHeader> <AccordionHeader>
@@ -573,6 +564,7 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
</AccordionPanel> </AccordionPanel>
</AccordionItem> </AccordionItem>
)} )}
{userContext.apiType === "SQL" && ( {userContext.apiType === "SQL" && (
<> <>
<AccordionItem value="3"> <AccordionItem value="3">
@@ -671,79 +663,78 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
</AccordionItem> </AccordionItem>
</> </>
)} )}
{(userContext.apiType === "SQL" || userContext.apiType === "Tables" || userContext.apiType === "Gremlin") && (
<AccordionItem value="6"> <AccordionItem value="6">
<AccordionHeader> <AccordionHeader>
<div className={styles.header}>Retry Settings</div> <div className={styles.header}>Retry Settings</div>
</AccordionHeader> </AccordionHeader>
<AccordionPanel> <AccordionPanel>
<div className={styles.settingsSectionContainer}> <div className={styles.settingsSectionContainer}>
<div className={styles.settingsSectionDescription}> <div className={styles.settingsSectionDescription}>
Retry policy associated with throttled requests during CosmosDB queries. Retry policy associated with throttled requests during CosmosDB queries.
</div>
<div>
<span className={styles.subHeader}>Max retry attempts</span>
<InfoTooltip className={styles.headerIcon}>
Max number of retries to be performed for a request. Default value 9.
</InfoTooltip>
</div>
<SpinButton
labelPosition={Position.top}
min={1}
step={1}
value={"" + retryAttempts}
onChange={handleOnQueryRetryAttemptsSpinButtonChange}
incrementButtonAriaLabel="Increase value by 1"
decrementButtonAriaLabel="Decrease value by 1"
onIncrement={(newValue) => setRetryAttempts(parseInt(newValue) + 1 || retryAttempts)}
onDecrement={(newValue) => setRetryAttempts(parseInt(newValue) - 1 || retryAttempts)}
onValidate={(newValue) => setRetryAttempts(parseInt(newValue) || retryAttempts)}
styles={spinButtonStyles}
/>
<div>
<span className={styles.subHeader}>Fixed retry interval (ms)</span>
<InfoTooltip className={styles.headerIcon}>
Fixed retry interval in milliseconds to wait between each retry ignoring the retryAfter returned
as part of the response. Default value is 0 milliseconds.
</InfoTooltip>
</div>
<SpinButton
labelPosition={Position.top}
min={1000}
step={1000}
value={"" + retryInterval}
onChange={handleOnRetryIntervalSpinButtonChange}
incrementButtonAriaLabel="Increase value by 1000"
decrementButtonAriaLabel="Decrease value by 1000"
onIncrement={(newValue) => setRetryInterval(parseInt(newValue) + 1000 || retryInterval)}
onDecrement={(newValue) => setRetryInterval(parseInt(newValue) - 1000 || retryInterval)}
onValidate={(newValue) => setRetryInterval(parseInt(newValue) || retryInterval)}
styles={spinButtonStyles}
/>
<div>
<span className={styles.subHeader}>Max wait time (s)</span>
<InfoTooltip className={styles.headerIcon}>
Max wait time in seconds to wait for a request while the retries are happening. Default value 30
seconds.
</InfoTooltip>
</div>
<SpinButton
labelPosition={Position.top}
min={1}
step={1}
value={"" + MaxWaitTimeInSeconds}
onChange={handleOnMaxWaitTimeSpinButtonChange}
incrementButtonAriaLabel="Increase value by 1"
decrementButtonAriaLabel="Decrease value by 1"
onIncrement={(newValue) => setMaxWaitTimeInSeconds(parseInt(newValue) + 1 || MaxWaitTimeInSeconds)}
onDecrement={(newValue) => setMaxWaitTimeInSeconds(parseInt(newValue) - 1 || MaxWaitTimeInSeconds)}
onValidate={(newValue) => setMaxWaitTimeInSeconds(parseInt(newValue) || MaxWaitTimeInSeconds)}
styles={spinButtonStyles}
/>
</div> </div>
</AccordionPanel> <div>
</AccordionItem> <span className={styles.subHeader}>Max retry attempts</span>
)} <InfoTooltip className={styles.headerIcon}>
Max number of retries to be performed for a request. Default value 9.
</InfoTooltip>
</div>
<SpinButton
labelPosition={Position.top}
min={1}
step={1}
value={"" + retryAttempts}
onChange={handleOnQueryRetryAttemptsSpinButtonChange}
incrementButtonAriaLabel="Increase value by 1"
decrementButtonAriaLabel="Decrease value by 1"
onIncrement={(newValue) => setRetryAttempts(parseInt(newValue) + 1 || retryAttempts)}
onDecrement={(newValue) => setRetryAttempts(parseInt(newValue) - 1 || retryAttempts)}
onValidate={(newValue) => setRetryAttempts(parseInt(newValue) || retryAttempts)}
styles={spinButtonStyles}
/>
<div>
<span className={styles.subHeader}>Fixed retry interval (ms)</span>
<InfoTooltip className={styles.headerIcon}>
Fixed retry interval in milliseconds to wait between each retry ignoring the retryAfter returned as
part of the response. Default value is 0 milliseconds.
</InfoTooltip>
</div>
<SpinButton
labelPosition={Position.top}
min={1000}
step={1000}
value={"" + retryInterval}
onChange={handleOnRetryIntervalSpinButtonChange}
incrementButtonAriaLabel="Increase value by 1000"
decrementButtonAriaLabel="Decrease value by 1000"
onIncrement={(newValue) => setRetryInterval(parseInt(newValue) + 1000 || retryInterval)}
onDecrement={(newValue) => setRetryInterval(parseInt(newValue) - 1000 || retryInterval)}
onValidate={(newValue) => setRetryInterval(parseInt(newValue) || retryInterval)}
styles={spinButtonStyles}
/>
<div>
<span className={styles.subHeader}>Max wait time (s)</span>
<InfoTooltip className={styles.headerIcon}>
Max wait time in seconds to wait for a request while the retries are happening. Default value 30
seconds.
</InfoTooltip>
</div>
<SpinButton
labelPosition={Position.top}
min={1}
step={1}
value={"" + MaxWaitTimeInSeconds}
onChange={handleOnMaxWaitTimeSpinButtonChange}
incrementButtonAriaLabel="Increase value by 1"
decrementButtonAriaLabel="Decrease value by 1"
onIncrement={(newValue) => setMaxWaitTimeInSeconds(parseInt(newValue) + 1 || MaxWaitTimeInSeconds)}
onDecrement={(newValue) => setMaxWaitTimeInSeconds(parseInt(newValue) - 1 || MaxWaitTimeInSeconds)}
onValidate={(newValue) => setMaxWaitTimeInSeconds(parseInt(newValue) || MaxWaitTimeInSeconds)}
styles={spinButtonStyles}
/>
</div>
</AccordionPanel>
</AccordionItem>
<AccordionItem value="7"> <AccordionItem value="7">
<AccordionHeader> <AccordionHeader>
@@ -767,6 +758,7 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
</div> </div>
</AccordionPanel> </AccordionPanel>
</AccordionItem> </AccordionItem>
{shouldShowCrossPartitionOption && ( {shouldShowCrossPartitionOption && (
<AccordionItem value="8"> <AccordionItem value="8">
<AccordionHeader> <AccordionHeader>
@@ -792,6 +784,7 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
</AccordionPanel> </AccordionPanel>
</AccordionItem> </AccordionItem>
)} )}
{shouldShowParallelismOption && ( {shouldShowParallelismOption && (
<AccordionItem value="9"> <AccordionItem value="9">
<AccordionHeader> <AccordionHeader>
@@ -825,6 +818,7 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
</AccordionPanel> </AccordionPanel>
</AccordionItem> </AccordionItem>
)} )}
{shouldShowPriorityLevelOption && ( {shouldShowPriorityLevelOption && (
<AccordionItem value="10"> <AccordionItem value="10">
<AccordionHeader> <AccordionHeader>
@@ -848,6 +842,7 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
</AccordionPanel> </AccordionPanel>
</AccordionItem> </AccordionItem>
)} )}
{shouldShowGraphAutoVizOption && ( {shouldShowGraphAutoVizOption && (
<AccordionItem value="11"> <AccordionItem value="11">
<AccordionHeader> <AccordionHeader>
@@ -869,6 +864,7 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({
</AccordionPanel> </AccordionPanel>
</AccordionItem> </AccordionItem>
)} )}
{shouldShowCopilotSampleDBOption && ( {shouldShowCopilotSampleDBOption && (
<AccordionItem value="12"> <AccordionItem value="12">
<AccordionHeader> <AccordionHeader>

View File

@@ -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="customAccordion ___1uf6361_0000000 fz7g6wx" className="___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="customAccordion ___1uf6361_0000000 fz7g6wx" className="___1uf6361_0000000 fz7g6wx"
> >
<AccordionItem <AccordionItem
value="6" value="6"

View File

@@ -309,24 +309,40 @@ exports[`AddCollectionPanel should render Default properly 1`] = `
<Stack <Stack
className="panelGroupSpacing" className="panelGroupSpacing"
> >
<Text <Stack
className="panelTextBold" horizontal={true}
variant="small"
> >
<Text <Text
className="panelTextBold"
variant="small" variant="small"
> >
Enable analytical store capability to perform near real-time analytics on your operational data, without impacting the performance of transactional workloads. Analytical store
<StyledLinkBase
aria-label="Learn more about analytical store."
href="https://aka.ms/analytical-store-overview"
target="_blank"
>
Learn more
</StyledLinkBase>
</Text> </Text>
</Text> <StyledTooltipHostBase
content={
<Text
variant="small"
>
Enable analytical store capability to perform near real-time analytics on your operational data, without impacting the performance of transactional workloads.
<StyledLinkBase
href="https://aka.ms/analytical-store-overview"
target="_blank"
>
Learn more
</StyledLinkBase>
</Text>
}
directionalHint={4}
>
<Icon
ariaLabel="Enable analytical store capability to perform near real-time analytics on your operational data, without impacting the performance of transactional workloads."
className="panelInfoIcon"
iconName="Info"
tabIndex={0}
/>
</StyledTooltipHostBase>
</Stack>
<Stack <Stack
horizontal={true} horizontal={true}
verticalAlign="center" verticalAlign="center"
@@ -384,7 +400,6 @@ 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"

View File

@@ -75,7 +75,6 @@ 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,
@@ -133,7 +132,6 @@ 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);
@@ -307,7 +305,7 @@ export const QueryCopilotPromptbar: React.FC<QueryCopilotPromptProps> = ({
if (isGeneratingQuery === null) { if (isGeneratingQuery === null) {
return " "; return " ";
} else if (isGeneratingQuery) { } else if (isGeneratingQuery) {
return "Thinking"; return "Content is loading";
} else { } else {
return "Content is updated"; return "Content is updated";
} }
@@ -402,7 +400,6 @@ 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"
@@ -679,7 +676,6 @@ 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%" }}
@@ -710,9 +706,6 @@ 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..."

View File

@@ -39,7 +39,7 @@ export const SplashScreenButton: React.FC<SplashScreenButtonProps> = ({
role="button" role="button"
> >
<div> <div>
<img src={imgSrc} alt={title} aria-hidden="true" /> <img src={imgSrc} />
</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>

View File

@@ -3,11 +3,17 @@
import { ColumnDefinition } from "Explorer/Tabs/DocumentsTabV2/DocumentsTableComponent"; import { ColumnDefinition } from "Explorer/Tabs/DocumentsTabV2/DocumentsTableComponent";
import { import {
AppStateComponentNames, AppStateComponentNames,
deleteSubComponentState, deleteState,
readSubComponentState, loadState,
saveSubComponentState, saveState,
saveStateDebounced,
} from "Shared/AppStatePersistenceUtility"; } from "Shared/AppStatePersistenceUtility";
import { userContext } from "UserContext";
import * as ViewModels from "../../../Contracts/ViewModels"; import * as ViewModels from "../../../Contracts/ViewModels";
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
const componentName = AppStateComponentNames.DocumentsTab;
export enum SubComponentName { export enum SubComponentName {
ColumnSizes = "ColumnSizes", ColumnSizes = "ColumnSizes",
@@ -15,7 +21,6 @@ export enum SubComponentName {
MainTabDivider = "MainTabDivider", MainTabDivider = "MainTabDivider",
ColumnsSelection = "ColumnsSelection", ColumnsSelection = "ColumnsSelection",
ColumnSort = "ColumnSort", ColumnSort = "ColumnSort",
CurrentFilter = "CurrentFilter",
} }
export type ColumnSizesMap = { [columnId: string]: WidthDefinition }; export type ColumnSizesMap = { [columnId: string]: WidthDefinition };
@@ -25,22 +30,84 @@ export type TabDivider = { leftPaneWidthPercent: number };
export type ColumnsSelection = { selectedColumnIds: string[]; columnDefinitions: ColumnDefinition[] }; export type ColumnsSelection = { selectedColumnIds: string[]; columnDefinitions: ColumnDefinition[] };
export type ColumnSort = { columnId: string; direction: "ascending" | "descending" }; export type ColumnSort = { columnId: string; direction: "ascending" | "descending" };
// Wrap the ...SubComponentState functions for type safety /**
*
export const readDocumentsTabSubComponentState = <T>( * @param subComponentName
* @param collection
* @param defaultValue Will be returned if persisted state is not found
* @returns
*/
export const readSubComponentState = <T>(
subComponentName: SubComponentName, subComponentName: SubComponentName,
collection: ViewModels.CollectionBase, collection: ViewModels.CollectionBase,
defaultValue: T, defaultValue: T,
): T => readSubComponentState<T>(AppStateComponentNames.DocumentsTab, subComponentName, collection, defaultValue); ): T => {
const globalAccountName = userContext.databaseAccount?.name;
if (!globalAccountName) {
const message = "Database account name not found in userContext";
console.error(message);
TelemetryProcessor.traceFailure(Action.ReadPersistedTabState, { message, componentName });
return defaultValue;
}
export const saveDocumentsTabSubComponentState = <T>( const state = loadState({
componentName: componentName,
subComponentName,
globalAccountName,
databaseName: collection.databaseId,
containerName: collection.id(),
}) as T;
return state || defaultValue;
};
/**
*
* @param subComponentName
* @param collection
* @param state State to save
* @param debounce true for high-frequency calls (e.g mouse drag events)
*/
export const saveSubComponentState = <T>(
subComponentName: SubComponentName, subComponentName: SubComponentName,
collection: ViewModels.CollectionBase, collection: ViewModels.CollectionBase,
state: T, state: T,
debounce?: boolean, debounce?: boolean,
): void => saveSubComponentState<T>(AppStateComponentNames.DocumentsTab, subComponentName, collection, state, debounce); ): void => {
const globalAccountName = userContext.databaseAccount?.name;
if (!globalAccountName) {
const message = "Database account name not found in userContext";
console.error(message);
TelemetryProcessor.traceFailure(Action.SavePersistedTabState, { message, componentName });
return;
}
export const deleteDocumentsTabSubComponentState = ( (debounce ? saveStateDebounced : saveState)(
subComponentName: SubComponentName, {
collection: ViewModels.CollectionBase, componentName: componentName,
) => deleteSubComponentState(AppStateComponentNames.DocumentsTab, subComponentName, collection); subComponentName,
globalAccountName,
databaseName: collection.databaseId,
containerName: collection.id(),
},
state,
);
};
export const deleteSubComponentState = (subComponentName: SubComponentName, collection: ViewModels.CollectionBase) => {
const globalAccountName = userContext.databaseAccount?.name;
if (!globalAccountName) {
const message = "Database account name not found in userContext";
console.error(message);
TelemetryProcessor.traceFailure(Action.DeletePersistedTabState, { message, componentName });
return;
}
deleteState({
componentName: componentName,
subComponentName,
globalAccountName,
databaseName: collection.databaseId,
containerName: collection.id(),
});
};

View File

@@ -21,7 +21,6 @@ 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 { Platform, configContext } from "ConfigContext";
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";
import { EditorReact } from "Explorer/Controls/Editor/EditorReact"; import { EditorReact } from "Explorer/Controls/Editor/EditorReact";
@@ -35,9 +34,8 @@ import {
FilterHistory, FilterHistory,
SubComponentName, SubComponentName,
TabDivider, TabDivider,
deleteDocumentsTabSubComponentState, readSubComponentState,
readDocumentsTabSubComponentState, saveSubComponentState,
saveDocumentsTabSubComponentState,
} from "Explorer/Tabs/DocumentsTabV2/DocumentsTabStateUtil"; } from "Explorer/Tabs/DocumentsTabV2/DocumentsTabStateUtil";
import { usePrevious } from "Explorer/Tabs/DocumentsTabV2/SelectionHelper"; 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";
@@ -142,8 +140,6 @@ export class DocumentsTabV2 extends TabsBase {
private title: string; private title: string;
private resourceTokenPartitionKey: string; private resourceTokenPartitionKey: string;
protected persistedState: OpenCollectionTab;
constructor(options: ViewModels.DocumentsTabOptions) { constructor(options: ViewModels.DocumentsTabOptions) {
super(options); super(options);
@@ -151,13 +147,6 @@ export class DocumentsTabV2 extends TabsBase {
this.title = options.title; this.title = options.title;
this.partitionKey = options.partitionKey; this.partitionKey = options.partitionKey;
this.resourceTokenPartitionKey = options.resourceTokenPartitionKey; this.resourceTokenPartitionKey = options.resourceTokenPartitionKey;
this.persistedState = {
actionType: ActionType.OpenCollectionTab,
tabKind: options.isPreferredApiMongoDB ? TabKind.MongoDocuments : TabKind.SQLDocuments,
databaseResourceId: options.collection.databaseId,
collectionResourceId: options.collection.id(),
};
} }
public render(): JSX.Element { public render(): JSX.Element {
@@ -586,10 +575,7 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
onIsExecutingChange, onIsExecutingChange,
isTabActive, isTabActive,
}): JSX.Element => { }): JSX.Element => {
const [filterContent, setFilterContent] = useState<string>(() => const [filterContent, setFilterContent] = useState<string>("");
readDocumentsTabSubComponentState<string>(SubComponentName.CurrentFilter, _collection, ""),
);
const [documentIds, setDocumentIds] = useState<ExtendedDocumentId[]>([]); const [documentIds, setDocumentIds] = useState<ExtendedDocumentId[]>([]);
const [isExecuting, setIsExecuting] = useState<boolean>(false); const [isExecuting, setIsExecuting] = useState<boolean>(false);
const styles = useDocumentsTabStyles(); const styles = useDocumentsTabStyles();
@@ -620,7 +606,7 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
// State // State
const [tabStateData, setTabStateData] = useState<TabDivider>(() => const [tabStateData, setTabStateData] = useState<TabDivider>(() =>
readDocumentsTabSubComponentState<TabDivider>(SubComponentName.MainTabDivider, _collection, { readSubComponentState<TabDivider>(SubComponentName.MainTabDivider, _collection, {
leftPaneWidthPercent: 35, leftPaneWidthPercent: 35,
}), }),
); );
@@ -635,7 +621,7 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
// User's filter history // User's filter history
const [lastFilterContents, setLastFilterContents] = useState<FilterHistory>(() => const [lastFilterContents, setLastFilterContents] = useState<FilterHistory>(() =>
readDocumentsTabSubComponentState<FilterHistory>(SubComponentName.FilterHistory, _collection, [] as FilterHistory), readSubComponentState<FilterHistory>(SubComponentName.FilterHistory, _collection, [] as FilterHistory),
); );
// For progress bar for bulk delete (noSql) // For progress bar for bulk delete (noSql)
@@ -777,7 +763,7 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
}; };
const [selectedColumnIds, setSelectedColumnIds] = useState<string[]>(() => { const [selectedColumnIds, setSelectedColumnIds] = useState<string[]>(() => {
const persistedColumnsSelection = readDocumentsTabSubComponentState<ColumnsSelection>( const persistedColumnsSelection = readSubComponentState<ColumnsSelection>(
SubComponentName.ColumnsSelection, SubComponentName.ColumnsSelection,
_collection, _collection,
undefined, undefined,
@@ -822,7 +808,7 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
useEffect(() => { useEffect(() => {
setKeyboardActions({ setKeyboardActions({
[KeyboardAction.CLEAR_SEARCH]: () => { [KeyboardAction.CLEAR_SEARCH]: () => {
updateFilterContent(""); setFilterContent("");
refreshDocumentsGrid(true); refreshDocumentsGrid(true);
return true; return true;
}, },
@@ -1659,7 +1645,7 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
// Column definition is a map<id, ColumnDefinition> to garantee uniqueness // Column definition is a map<id, ColumnDefinition> to garantee uniqueness
const [columnDefinitions, setColumnDefinitions] = useState<ColumnDefinition[]>(() => { const [columnDefinitions, setColumnDefinitions] = useState<ColumnDefinition[]>(() => {
const persistedColumnsSelection = readDocumentsTabSubComponentState<ColumnsSelection>( const persistedColumnsSelection = readSubComponentState<ColumnsSelection>(
SubComponentName.ColumnsSelection, SubComponentName.ColumnsSelection,
_collection, _collection,
undefined, undefined,
@@ -1970,7 +1956,7 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
const limitedLastFilterContents = lastFilterContents.slice(0, MAX_FILTER_HISTORY_COUNT); const limitedLastFilterContents = lastFilterContents.slice(0, MAX_FILTER_HISTORY_COUNT);
setLastFilterContents(limitedLastFilterContents); setLastFilterContents(limitedLastFilterContents);
saveDocumentsTabSubComponentState<FilterHistory>(SubComponentName.FilterHistory, _collection, lastFilterContents); saveSubComponentState<FilterHistory>(SubComponentName.FilterHistory, _collection, lastFilterContents);
}; };
const refreshDocumentsGrid = useCallback( const refreshDocumentsGrid = useCallback(
@@ -2027,7 +2013,7 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
setSelectedColumnIds(newSelectedColumnIds); setSelectedColumnIds(newSelectedColumnIds);
saveDocumentsTabSubComponentState<ColumnsSelection>(SubComponentName.ColumnsSelection, _collection, { saveSubComponentState<ColumnsSelection>(SubComponentName.ColumnsSelection, _collection, {
selectedColumnIds: newSelectedColumnIds, selectedColumnIds: newSelectedColumnIds,
columnDefinitions, columnDefinitions,
}); });
@@ -2077,15 +2063,6 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
return options; return options;
}; };
const updateFilterContent = (filter: string): void => {
if (filter === "" || filter === undefined) {
deleteDocumentsTabSubComponentState(SubComponentName.CurrentFilter, _collection);
} else {
saveDocumentsTabSubComponentState<string>(SubComponentName.CurrentFilter, _collection, filter, true);
}
setFilterContent(filter);
};
return ( return (
<CosmosFluentProvider className={styles.container}> <CosmosFluentProvider className={styles.container}>
<div className="tab-pane active" role="tabpanel" style={{ display: "flex" }}> <div className="tab-pane active" role="tabpanel" style={{ display: "flex" }}>
@@ -2100,7 +2077,7 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
} }
title="Type a query predicate or choose one from the list." title="Type a query predicate or choose one from the list."
value={filterContent} value={filterContent}
onChange={updateFilterContent} onChange={(value) => setFilterContent(value)}
onKeyDown={onFilterKeyDown} onKeyDown={onFilterKeyDown}
bottomLink={{ text: "Learn more", url: DATA_EXPLORER_DOC_URL }} bottomLink={{ text: "Learn more", url: DATA_EXPLORER_DOC_URL }}
/> />
@@ -2126,7 +2103,7 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
<Allotment <Allotment
onDragEnd={(sizes: number[]) => { onDragEnd={(sizes: number[]) => {
tabStateData.leftPaneWidthPercent = (100 * sizes[0]) / (sizes[0] + sizes[1]); tabStateData.leftPaneWidthPercent = (100 * sizes[0]) / (sizes[0] + sizes[1]);
saveDocumentsTabSubComponentState<TabDivider>(SubComponentName.MainTabDivider, _collection, tabStateData); saveSubComponentState<TabDivider>(SubComponentName.MainTabDivider, _collection, tabStateData);
setTabStateData(tabStateData); setTabStateData(tabStateData);
}} }}
> >

View File

@@ -42,9 +42,9 @@ import { TableColumnSelectionPane } from "Explorer/Panes/TableColumnSelectionPan
import { import {
ColumnSizesMap, ColumnSizesMap,
ColumnSort, ColumnSort,
deleteDocumentsTabSubComponentState, deleteSubComponentState,
readDocumentsTabSubComponentState, readSubComponentState,
saveDocumentsTabSubComponentState, saveSubComponentState,
SubComponentName, SubComponentName,
} from "Explorer/Tabs/DocumentsTabV2/DocumentsTabStateUtil"; } from "Explorer/Tabs/DocumentsTabV2/DocumentsTabStateUtil";
import { INITIAL_SELECTED_ROW_INDEX, useDocumentsTabStyles } from "Explorer/Tabs/DocumentsTabV2/DocumentsTabV2"; import { INITIAL_SELECTED_ROW_INDEX, useDocumentsTabStyles } from "Explorer/Tabs/DocumentsTabV2/DocumentsTabV2";
@@ -118,11 +118,7 @@ export const DocumentsTableComponent: React.FC<IDocumentsTableComponentProps> =
const sortedRowsRef = React.useRef(null); const sortedRowsRef = React.useRef(null);
const [columnSizingOptions, setColumnSizingOptions] = React.useState<TableColumnSizingOptions>(() => { const [columnSizingOptions, setColumnSizingOptions] = React.useState<TableColumnSizingOptions>(() => {
const columnSizesMap: ColumnSizesMap = readDocumentsTabSubComponentState( const columnSizesMap: ColumnSizesMap = readSubComponentState(SubComponentName.ColumnSizes, collection, {});
SubComponentName.ColumnSizes,
collection,
{},
);
const columnSizesPx: TableColumnSizingOptions = {}; const columnSizesPx: TableColumnSizingOptions = {};
selectedColumnIds.forEach((columnId) => { selectedColumnIds.forEach((columnId) => {
if ( if (
@@ -146,7 +142,7 @@ export const DocumentsTableComponent: React.FC<IDocumentsTableComponentProps> =
sortDirection: "ascending" | "descending"; sortDirection: "ascending" | "descending";
sortColumn: TableColumnId | undefined; sortColumn: TableColumnId | undefined;
}>(() => { }>(() => {
const sort = readDocumentsTabSubComponentState<ColumnSort>(SubComponentName.ColumnSort, collection, undefined); const sort = readSubComponentState<ColumnSort>(SubComponentName.ColumnSort, collection, undefined);
if (!sort) { if (!sort) {
return { return {
@@ -178,12 +174,7 @@ export const DocumentsTableComponent: React.FC<IDocumentsTableComponentProps> =
return acc; return acc;
}, {} as ColumnSizesMap); }, {} as ColumnSizesMap);
saveDocumentsTabSubComponentState<ColumnSizesMap>( saveSubComponentState<ColumnSizesMap>(SubComponentName.ColumnSizes, collection, persistentSizes, true);
SubComponentName.ColumnSizes,
collection,
persistentSizes,
true,
);
return newSizingOptions; return newSizingOptions;
}); });
@@ -195,14 +186,11 @@ export const DocumentsTableComponent: React.FC<IDocumentsTableComponentProps> =
setColumnSort(event, columnId, direction); setColumnSort(event, columnId, direction);
if (columnId === undefined || direction === undefined) { if (columnId === undefined || direction === undefined) {
deleteDocumentsTabSubComponentState(SubComponentName.ColumnSort, collection); deleteSubComponentState(SubComponentName.ColumnSort, collection);
return; return;
} }
saveDocumentsTabSubComponentState<ColumnSort>(SubComponentName.ColumnSort, collection, { saveSubComponentState<ColumnSort>(SubComponentName.ColumnSort, collection, { columnId, direction });
columnId,
direction,
});
}; };
// Columns must be a static object and cannot change on re-renders otherwise React will complain about too many refreshes // Columns must be a static object and cannot change on re-renders otherwise React will complain about too many refreshes

View File

@@ -1,4 +1,3 @@
import { ActionType, TabKind } from "Contracts/ActionContracts";
import React from "react"; import React from "react";
import MongoUtility from "../../../Common/MongoUtility"; import MongoUtility from "../../../Common/MongoUtility";
import * as ViewModels from "../../../Contracts/ViewModels"; import * as ViewModels from "../../../Contracts/ViewModels";
@@ -21,7 +20,7 @@ export class NewMongoQueryTab extends NewQueryTab {
private mongoQueryTabProps: IMongoQueryTabProps, private mongoQueryTabProps: IMongoQueryTabProps,
) { ) {
super(options, mongoQueryTabProps); super(options, mongoQueryTabProps);
this.queryText = options.queryText ?? ""; this.queryText = "";
this.iMongoQueryTabComponentProps = { this.iMongoQueryTabComponentProps = {
collection: options.collection, collection: options.collection,
isExecutionError: this.isExecutionError(), isExecutionError: this.isExecutionError(),
@@ -29,8 +28,6 @@ export class NewMongoQueryTab extends NewQueryTab {
tabsBaseInstance: this, tabsBaseInstance: this,
queryText: this.queryText, queryText: this.queryText,
partitionKey: this.partitionKey, partitionKey: this.partitionKey,
splitterDirection: options.splitterDirection,
queryViewSizePercent: options.queryViewSizePercent,
container: this.mongoQueryTabProps.container, container: this.mongoQueryTabProps.container,
onTabAccessor: (instance: ITabAccessor): void => { onTabAccessor: (instance: ITabAccessor): void => {
this.iTabAccessor = instance; this.iTabAccessor = instance;
@@ -38,26 +35,6 @@ export class NewMongoQueryTab extends NewQueryTab {
isPreferredApiMongoDB: true, isPreferredApiMongoDB: true,
monacoEditorSetting: "plaintext", monacoEditorSetting: "plaintext",
viewModelcollection: this.mongoQueryTabProps.viewModelcollection, viewModelcollection: this.mongoQueryTabProps.viewModelcollection,
onUpdatePersistedState: (state: {
queryText: string;
splitterDirection: string;
queryViewSizePercent: number;
}): void => {
this.persistedState = {
actionType: ActionType.OpenCollectionTab,
tabKind: TabKind.SQLQuery,
databaseResourceId: options.collection.databaseId,
collectionResourceId: options.collection.id(),
query: {
text: state.queryText,
},
splitterDirection: state.splitterDirection as "vertical" | "horizontal",
queryViewSizePercent: state.queryViewSizePercent,
};
if (this.triggerPersistState) {
this.triggerPersistState();
}
},
}; };
} }

View File

@@ -1,5 +1,4 @@
import { sendMessage } from "Common/MessageHandler"; import { sendMessage } from "Common/MessageHandler";
import { ActionType, OpenQueryTab, TabKind } from "Contracts/ActionContracts";
import { MessageTypes } from "Contracts/MessageTypes"; import { MessageTypes } from "Contracts/MessageTypes";
import { CopilotProvider } from "Explorer/QueryCopilot/QueryCopilotContext"; import { CopilotProvider } from "Explorer/QueryCopilot/QueryCopilotContext";
import { userContext } from "UserContext"; import { userContext } from "UserContext";
@@ -27,8 +26,6 @@ export class NewQueryTab extends TabsBase {
public iQueryTabComponentProps: IQueryTabComponentProps; public iQueryTabComponentProps: IQueryTabComponentProps;
public iTabAccessor: ITabAccessor; public iTabAccessor: ITabAccessor;
protected persistedState: OpenQueryTab;
constructor( constructor(
options: QueryTabOptions, options: QueryTabOptions,
private props: IQueryTabProps, private props: IQueryTabProps,
@@ -42,41 +39,12 @@ export class NewQueryTab extends TabsBase {
tabsBaseInstance: this, tabsBaseInstance: this,
queryText: options.queryText, queryText: options.queryText,
partitionKey: this.partitionKey, partitionKey: this.partitionKey,
splitterDirection: options.splitterDirection,
queryViewSizePercent: options.queryViewSizePercent,
container: this.props.container, container: this.props.container,
onTabAccessor: (instance: ITabAccessor): void => { onTabAccessor: (instance: ITabAccessor): void => {
this.iTabAccessor = instance; this.iTabAccessor = instance;
}, },
isPreferredApiMongoDB: false, isPreferredApiMongoDB: false,
onUpdatePersistedState: (state: {
queryText: string;
splitterDirection: string;
queryViewSizePercent: number;
}): void => {
this.persistedState = {
actionType: ActionType.OpenCollectionTab,
tabKind: TabKind.SQLQuery,
databaseResourceId: options.collection.databaseId,
collectionResourceId: options.collection.id(),
query: {
text: state.queryText,
},
splitterDirection: state.splitterDirection as "vertical" | "horizontal",
queryViewSizePercent: state.queryViewSizePercent,
};
if (this.triggerPersistState) {
this.triggerPersistState();
}
},
}; };
// set initial state
this.iQueryTabComponentProps.onUpdatePersistedState({
queryText: options.queryText,
splitterDirection: options.splitterDirection,
queryViewSizePercent: options.queryViewSizePercent,
});
} }
public render(): JSX.Element { public render(): JSX.Element {

View File

@@ -34,7 +34,6 @@ jest.mock("Shared/AppStatePersistenceUtility", () => ({
AppStateComponentNames: { AppStateComponentNames: {
QueryCopilot: "QueryCopilot", QueryCopilot: "QueryCopilot",
}, },
readSubComponentState: jest.fn(),
})); }));
describe("QueryTabComponent", () => { describe("QueryTabComponent", () => {

View File

@@ -18,7 +18,13 @@ import { CosmosFluentProvider } from "Explorer/Theme/ThemeUtil";
import { useSelectedNode } from "Explorer/useSelectedNode"; import { useSelectedNode } from "Explorer/useSelectedNode";
import { KeyboardAction } from "KeyboardShortcuts"; import { KeyboardAction } from "KeyboardShortcuts";
import { QueryConstants } from "Shared/Constants"; import { QueryConstants } from "Shared/Constants";
import { LocalStorageUtility, StorageKey, getRUThreshold, ruThresholdEnabled } from "Shared/StorageUtility"; import {
LocalStorageUtility,
StorageKey,
getDefaultQueryResultsView,
getRUThreshold,
ruThresholdEnabled,
} from "Shared/StorageUtility";
import { Action } from "Shared/Telemetry/TelemetryConstants"; import { Action } from "Shared/Telemetry/TelemetryConstants";
import { Allotment } from "allotment"; import { Allotment } from "allotment";
import { QueryCopilotState, useQueryCopilot } from "hooks/useQueryCopilot"; import { QueryCopilotState, useQueryCopilot } from "hooks/useQueryCopilot";
@@ -93,13 +99,6 @@ export interface IQueryTabComponentProps {
copilotEnabled?: boolean; copilotEnabled?: boolean;
isSampleCopilotActive?: boolean; isSampleCopilotActive?: boolean;
copilotStore?: Partial<QueryCopilotState>; copilotStore?: Partial<QueryCopilotState>;
splitterDirection?: "horizontal" | "vertical";
queryViewSizePercent?: number;
onUpdatePersistedState: (state: {
queryText: string;
splitterDirection: "vertical" | "horizontal";
queryViewSizePercent: number;
}) => void;
} }
interface IQueryTabStates { interface IQueryTabStates {
@@ -119,13 +118,11 @@ interface IQueryTabStates {
queryResultsView: SplitterDirection; queryResultsView: SplitterDirection;
errors?: QueryError[]; errors?: QueryError[];
modelMarkers?: monaco.editor.IMarkerData[]; modelMarkers?: monaco.editor.IMarkerData[];
queryViewSizePercent: number;
} }
export const QueryTabCopilotComponent = (props: IQueryTabComponentProps): any => { export const QueryTabCopilotComponent = (props: IQueryTabComponentProps): any => {
const styles = useQueryTabStyles(); const styles = useQueryTabStyles();
const copilotStore = useCopilotStore(); const copilotStore = useCopilotStore();
const isSampleCopilotActive = useSelectedNode.getState().isQueryCopilotCollectionSelected(); const isSampleCopilotActive = useSelectedNode.getState().isQueryCopilotCollectionSelected();
const queryTabProps = { const queryTabProps = {
...props, ...props,
@@ -135,12 +132,12 @@ export const QueryTabCopilotComponent = (props: IQueryTabComponentProps): any =>
isSampleCopilotActive: isSampleCopilotActive, isSampleCopilotActive: isSampleCopilotActive,
copilotStore: copilotStore, copilotStore: copilotStore,
}; };
return <QueryTabComponentImpl styles={styles} {...queryTabProps} />; return <QueryTabComponentImpl styles={styles} {...queryTabProps}></QueryTabComponentImpl>;
}; };
export const QueryTabComponent = (props: IQueryTabComponentProps): any => { export const QueryTabComponent = (props: IQueryTabComponentProps): any => {
const styles = useQueryTabStyles(); const styles = useQueryTabStyles();
return <QueryTabComponentImpl styles={styles} {...{ ...props }} />; return <QueryTabComponentImpl styles={styles} {...props}></QueryTabComponentImpl>;
}; };
type QueryTabComponentImplProps = IQueryTabComponentProps & { type QueryTabComponentImplProps = IQueryTabComponentProps & {
@@ -149,8 +146,6 @@ type QueryTabComponentImplProps = IQueryTabComponentProps & {
// Inner (legacy) class component. We only use this component via one of the two functional components above (since we need to use the `useQueryTabStyles` hook). // Inner (legacy) class component. We only use this component via one of the two functional components above (since we need to use the `useQueryTabStyles` hook).
class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps, IQueryTabStates> { class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps, IQueryTabStates> {
private static readonly DEBOUNCE_DELAY_MS = 1000;
public queryEditorId: string; public queryEditorId: string;
public executeQueryButton: Button; public executeQueryButton: Button;
public saveQueryButton: Button; public saveQueryButton: Button;
@@ -162,10 +157,10 @@ class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps,
private _iterator: MinimalQueryIterator; private _iterator: MinimalQueryIterator;
private queryAbortController: AbortController; private queryAbortController: AbortController;
queryEditor: React.RefObject<EditorReact>; queryEditor: React.RefObject<EditorReact>;
private timeoutId: NodeJS.Timeout | undefined;
constructor(props: QueryTabComponentImplProps) { constructor(props: QueryTabComponentImplProps) {
super(props); super(props);
this.queryEditor = createRef<EditorReact>(); this.queryEditor = createRef<EditorReact>();
this.state = { this.state = {
@@ -181,9 +176,7 @@ class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps,
cancelQueryTimeoutID: undefined, cancelQueryTimeoutID: undefined,
copilotActive: this._queryCopilotActive(), copilotActive: this._queryCopilotActive(),
currentTabActive: true, currentTabActive: true,
queryResultsView: queryResultsView: getDefaultQueryResultsView(),
props.splitterDirection === "vertical" ? SplitterDirection.Vertical : SplitterDirection.Horizontal,
queryViewSizePercent: props.queryViewSizePercent,
}; };
this.isCloseClicked = false; this.isCloseClicked = false;
this.splitterId = this.props.tabId + "_splitter"; this.splitterId = this.props.tabId + "_splitter";
@@ -214,23 +207,6 @@ class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps,
}); });
} }
/**
* Helper function to save the query text in the query tab state
* Since it reads and writes to the same state, it is debounced
*/
private saveQueryTabStateDebounced = () => {
if (this.timeoutId) {
clearTimeout(this.timeoutId);
}
this.timeoutId = setTimeout(async () => {
this.props.onUpdatePersistedState({
queryText: this.state.sqlQueryEditorContent,
splitterDirection: this.state.queryResultsView,
queryViewSizePercent: this.state.queryViewSizePercent,
});
}, QueryTabComponentImpl.DEBOUNCE_DELAY_MS);
};
private _queryCopilotActive(): boolean { private _queryCopilotActive(): boolean {
if (this.props.copilotEnabled) { if (this.props.copilotEnabled) {
return readCopilotToggleStatus(userContext.databaseAccount); return readCopilotToggleStatus(userContext.databaseAccount);
@@ -591,7 +567,7 @@ class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps,
}; };
} }
private _setViewLayout(direction: SplitterDirection): void { private _setViewLayout(direction: SplitterDirection): void {
this.setState({ queryResultsView: direction }, () => this.saveQueryTabStateDebounced()); this.setState({ queryResultsView: direction });
// We'll need to refresh the context buttons to update the selected state of the view buttons // We'll need to refresh the context buttons to update the selected state of the view buttons
setTimeout(() => { setTimeout(() => {
@@ -623,16 +599,13 @@ class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps,
if (this.state.copilotActive) { if (this.state.copilotActive) {
this.props.copilotStore?.setQuery(newContent); this.props.copilotStore?.setQuery(newContent);
} }
this.setState( this.setState({
{ sqlQueryEditorContent: newContent,
sqlQueryEditorContent: newContent, queryCopilotGeneratedQuery: "",
queryCopilotGeneratedQuery: "",
// Clear the markers when the user edits the document. // Clear the markers when the user edits the document.
modelMarkers: [], modelMarkers: [],
}, });
() => this.saveQueryTabStateDebounced(),
);
if (this.isPreferredApiMongoDB) { if (this.isPreferredApiMongoDB) {
if (newContent.length > 0) { if (newContent.length > 0) {
this.executeQueryButton = { this.executeQueryButton = {
@@ -731,20 +704,8 @@ class QueryTabComponentImpl extends React.Component<QueryTabComponentImplProps,
></QueryCopilotPromptbar> ></QueryCopilotPromptbar>
)} )}
{/* Set 'key' to the value of vertical to force re-rendering when vertical changes, to work around https://github.com/johnwalley/allotment/issues/457 */} {/* Set 'key' to the value of vertical to force re-rendering when vertical changes, to work around https://github.com/johnwalley/allotment/issues/457 */}
<Allotment <Allotment key={vertical.toString()} vertical={vertical}>
key={vertical.toString()} <Allotment.Pane data-test="QueryTab/EditorPane">
vertical={vertical}
onDragEnd={(sizes: number[]) => {
const queryViewSizePercent = (100 * sizes[0]) / (sizes[0] + sizes[1]);
this.setState({ queryViewSizePercent }, () => this.saveQueryTabStateDebounced());
}}
>
<Allotment.Pane
data-test="QueryTab/EditorPane"
preferredSize={
this.state.queryViewSizePercent !== undefined ? `${this.state.queryViewSizePercent}%` : undefined
}
>
<EditorReact <EditorReact
ref={this.queryEditor} ref={this.queryEditor}
className={this.props.styles.queryEditor} className={this.props.styles.queryEditor}

View File

@@ -1,4 +1,3 @@
import { ActionType, OpenCollectionTab, TabKind } from "Contracts/ActionContracts";
import React from "react"; import React from "react";
import * as ViewModels from "../../Contracts/ViewModels"; import * as ViewModels from "../../Contracts/ViewModels";
import { SettingsComponent } from "../Controls/Settings/SettingsComponent"; import { SettingsComponent } from "../Controls/Settings/SettingsComponent";
@@ -11,18 +10,6 @@ export class SettingsTabV2 extends TabsBase {
} }
export class CollectionSettingsTabV2 extends SettingsTabV2 { export class CollectionSettingsTabV2 extends SettingsTabV2 {
protected persistedState: OpenCollectionTab;
constructor(options: ViewModels.TabOptions) {
super(options);
this.persistedState = {
actionType: ActionType.OpenCollectionTab,
tabKind: TabKind.ScaleSettings,
databaseResourceId: options.collection.databaseId,
collectionResourceId: options.collection.id(),
};
}
public onActivate(): void { public onActivate(): void {
super.onActivate(); super.onActivate();
this.collection.selectedSubnodeKind(ViewModels.CollectionTabKind.CollectionSettingsV2); this.collection.selectedSubnodeKind(ViewModels.CollectionTabKind.CollectionSettingsV2);

View File

@@ -1,7 +1,9 @@
import { IMessageBarStyles, MessageBar, MessageBarType } from "@fluentui/react"; import { IMessageBarStyles, MessageBar, MessageBarButton, MessageBarType } from "@fluentui/react";
import { CassandraProxyEndpoints, MongoProxyEndpoints } from "Common/Constants"; import { CassandraProxyEndpoints, MongoProxyEndpoints } from "Common/Constants";
import { sendMessage } from "Common/MessageHandler";
import { configContext } from "ConfigContext"; import { configContext } from "ConfigContext";
import { IpRule } from "Contracts/DataModels"; import { IpRule } from "Contracts/DataModels";
import { MessageTypes } from "Contracts/ExplorerContracts";
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";
@@ -33,7 +35,7 @@ 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, networkSettingsWarning } = useTabs();
const [ const [
showMongoAndCassandraProxiesNetworkSettingsWarningState, showMongoAndCassandraProxiesNetworkSettingsWarningState,
setShowMongoAndCassandraProxiesNetworkSettingsWarningState, setShowMongoAndCassandraProxiesNetworkSettingsWarningState,
@@ -58,6 +60,29 @@ export const Tabs = ({ explorer }: TabsProps): JSX.Element => {
return ( return (
<div className="tabsManagerContainer"> <div className="tabsManagerContainer">
{networkSettingsWarning && (
<MessageBar
messageBarType={MessageBarType.warning}
styles={defaultMessageBarStyles}
actions={
<MessageBarButton
onClick={() =>
sendMessage({
type:
userContext.apiType === "VCoreMongo"
? MessageTypes.OpenVCoreMongoNetworkingBlade
: MessageTypes.OpenPostgresNetworkingBlade,
})
}
>
Change network settings
</MessageBarButton>
}
messageBarIconProps={{ iconName: "WarningSolid", className: "messageBarWarningIcon" }}
>
{networkSettingsWarning}
</MessageBar>
)}
{showMongoAndCassandraProxiesNetworkSettingsWarningState && ( {showMongoAndCassandraProxiesNetworkSettingsWarningState && (
<MessageBar <MessageBar
messageBarType={MessageBarType.warning} messageBarType={MessageBarType.warning}
@@ -159,7 +184,7 @@ function TabNav({ tab, active, tabKind }: { tab?: Tab; active: boolean; tabKind?
</span> </span>
<span className="tabNavText">{tabTitle}</span> <span className="tabNavText">{tabTitle}</span>
</span> </span>
<span className="tabIconSection" aria-hidden="true"> <span className="tabIconSection">
<CloseButton tab={tab} active={active} hovering={hovering} tabKind={tabKind} ariaLabel={tabTitle} /> <CloseButton tab={tab} active={active} hovering={hovering} tabKind={tabKind} ariaLabel={tabTitle} />
</span> </span>
</div> </div>
@@ -203,7 +228,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" aria-label="hidden" /> <img src={errorIcon} title="Close" alt="Close" role="none" />
</span> </span>
</span> </span>
); );
@@ -247,7 +272,7 @@ function TabPane({ tab, active }: { tab: Tab; active: boolean }) {
if (tab) { if (tab) {
if ("render" in tab) { if ("render" in tab) {
return ( return (
<div id={tab.tabId} data-test={`Tab:${tab.tabId}`} {...attrs}> <div data-test={`Tab:${tab.tabId}`} {...attrs}>
{tab.render()} {tab.render()}
</div> </div>
); );
@@ -318,7 +343,7 @@ const getReactTabContent = (activeReactTab: ReactTabKind, explorer: Explorer): J
const showMongoAndCassandraProxiesNetworkSettingsWarning = (): boolean => { const showMongoAndCassandraProxiesNetworkSettingsWarning = (): boolean => {
const ipRules: IpRule[] = userContext.databaseAccount?.properties?.ipRules; const ipRules: IpRule[] = userContext.databaseAccount?.properties?.ipRules;
if ( if (
((userContext.apiType === "Mongo" && configContext.MONGO_PROXY_ENDPOINT !== MongoProxyEndpoints.Development) || ((userContext.apiType === "Mongo" && configContext.MONGO_PROXY_ENDPOINT !== MongoProxyEndpoints.Local) ||
(userContext.apiType === "Cassandra" && (userContext.apiType === "Cassandra" &&
configContext.CASSANDRA_PROXY_ENDPOINT !== CassandraProxyEndpoints.Development)) && configContext.CASSANDRA_PROXY_ENDPOINT !== CassandraProxyEndpoints.Development)) &&
ipRules?.length ipRules?.length

View File

@@ -1,4 +1,3 @@
import { OpenTab } from "Contracts/ActionContracts";
import { KeyboardActionGroup, clearKeyboardActionGroup } from "KeyboardShortcuts"; import { KeyboardActionGroup, clearKeyboardActionGroup } from "KeyboardShortcuts";
import * as ko from "knockout"; import * as ko from "knockout";
import * as Constants from "../../Common/Constants"; import * as Constants from "../../Common/Constants";
@@ -31,8 +30,6 @@ export default class TabsBase extends WaitsForTemplateViewModel {
protected _theme: string; protected _theme: string;
public onLoadStartKey: number; public onLoadStartKey: number;
protected persistedState: OpenTab | undefined = undefined; // Used to store state of tab for persistence
constructor(options: ViewModels.TabOptions) { constructor(options: ViewModels.TabOptions) {
super(); super();
this.index = options.index; this.index = options.index;
@@ -58,10 +55,6 @@ export default class TabsBase extends WaitsForTemplateViewModel {
}; };
} }
// Called by useTabs to persist
public getPersistedState = (): OpenTab | null => this.persistedState;
public triggerPersistState: () => void = undefined;
public onCloseTabButtonClick(): void { public onCloseTabButtonClick(): void {
useTabs.getState().closeTab(this); useTabs.getState().closeTab(this);
TelemetryProcessor.trace(Action.Tab, ActionModifiers.Close, { TelemetryProcessor.trace(Action.Tab, ActionModifiers.Close, {

View File

@@ -630,13 +630,7 @@ export default class Collection implements ViewModels.Collection {
} }
}; };
public onNewQueryClick( public onNewQueryClick(source: any, event: MouseEvent, queryText?: string) {
source: any,
event: MouseEvent,
queryText?: string,
splitterDirection?: "horizontal" | "vertical",
queryViewSizePercent?: number,
) {
const collection: ViewModels.Collection = source.collection || source; const collection: ViewModels.Collection = source.collection || source;
const id = useTabs.getState().getTabs(ViewModels.CollectionTabKind.Query).length + 1; const id = useTabs.getState().getTabs(ViewModels.CollectionTabKind.Query).length + 1;
const title = "Query " + id; const title = "Query " + id;
@@ -659,21 +653,13 @@ export default class Collection implements ViewModels.Collection {
queryText: queryText, queryText: queryText,
partitionKey: collection.partitionKey, partitionKey: collection.partitionKey,
onLoadStartKey: startKey, onLoadStartKey: startKey,
splitterDirection,
queryViewSizePercent,
}, },
{ container: this.container }, { container: this.container },
), ),
); );
} }
public onNewMongoQueryClick( public onNewMongoQueryClick(source: any, event: MouseEvent, queryText?: string) {
source: any,
event: MouseEvent,
queryText?: string,
splitterDirection?: "horizontal" | "vertical",
queryViewSizePercent?: number,
) {
const collection: ViewModels.Collection = source.collection || source; const collection: ViewModels.Collection = source.collection || source;
const id = useTabs.getState().getTabs(ViewModels.CollectionTabKind.Query).length + 1; const id = useTabs.getState().getTabs(ViewModels.CollectionTabKind.Query).length + 1;
@@ -695,9 +681,6 @@ export default class Collection implements ViewModels.Collection {
node: this, node: this,
partitionKey: collection.partitionKey, partitionKey: collection.partitionKey,
onLoadStartKey: startKey, onLoadStartKey: startKey,
queryText,
splitterDirection,
queryViewSizePercent,
}, },
{ {
container: this.container, container: this.container,

View File

@@ -266,10 +266,7 @@ export const getOfferingIds = async (regions: Array<RegionItem>): Promise<Offeri
method: "GET", method: "GET",
apiVersion: "2023-05-01-preview", apiVersion: "2023-05-01-preview",
queryParams: { queryParams: {
filter: filter: "armRegionNameeq '" + regionShortName + "'",
"armRegionNameeq '" +
regionShortName +
"' and productDisplayName eq 'Azure Cosmos DB Dedicated Gateway - General Purpose'",
}, },
}); });

View File

@@ -1,20 +1,12 @@
import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility"; import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
import { Action } from "Shared/Telemetry/TelemetryConstants";
import { userContext } from "UserContext";
import * as ViewModels from "../Contracts/ViewModels";
import * as TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor";
// The component name whose state is being saved. Component name must not include special characters. // The component name whose state is being saved. Component name must not include special characters.
export enum AppStateComponentNames { export enum AppStateComponentNames {
DocumentsTab = "DocumentsTab", DocumentsTab = "DocumentsTab",
MostRecentActivity = "MostRecentActivity", MostRecentActivity = "MostRecentActivity",
QueryCopilot = "QueryCopilot", QueryCopilot = "QueryCopilot",
DataExplorerAction = "DataExplorerAction",
} }
// Subcomponent for DataExplorerAction
export const OPEN_TABS_SUBCOMPONENT_NAME = "OpenTabs";
export const PATH_SEPARATOR = "/"; // export for testing purposes export const PATH_SEPARATOR = "/"; // export for testing purposes
const SCHEMA_VERSION = 1; const SCHEMA_VERSION = 1;
@@ -80,18 +72,12 @@ export const hasState = (path: StorePath): boolean => {
}; };
// This is for high-frequency state changes // This is for high-frequency state changes
// Keep track of timeouts per path let timeoutId: NodeJS.Timeout | undefined;
const pathToTimeoutIdMap = new Map<string, NodeJS.Timeout>();
export const saveStateDebounced = (path: StorePath, state: unknown, debounceDelayMs = 1000): void => { export const saveStateDebounced = (path: StorePath, state: unknown, debounceDelayMs = 1000): void => {
const key = createKeyFromPath(path);
const timeoutId = pathToTimeoutIdMap.get(key);
if (timeoutId) { if (timeoutId) {
clearTimeout(timeoutId); clearTimeout(timeoutId);
} }
pathToTimeoutIdMap.set( timeoutId = setTimeout(() => saveState(path, state), debounceDelayMs);
key,
setTimeout(() => saveState(path, state), debounceDelayMs),
);
}; };
interface ApplicationState { interface ApplicationState {
@@ -126,93 +112,3 @@ export const createKeyFromPath = (path: StorePath): string => {
export const deleteAllStates = (): void => { export const deleteAllStates = (): void => {
LocalStorageUtility.removeEntry(StorageKey.AppState); LocalStorageUtility.removeEntry(StorageKey.AppState);
}; };
// Convenience functions
/**
*
* @param subComponentName
* @param collection
* @param defaultValue Will be returned if persisted state is not found
* @returns
*/
export const readSubComponentState = <T>(
componentName: AppStateComponentNames,
subComponentName: string,
collection: ViewModels.CollectionBase | undefined,
defaultValue: T,
): T => {
const globalAccountName = userContext.databaseAccount?.name;
if (!globalAccountName) {
const message = "Database account name not found in userContext";
console.error(message);
TelemetryProcessor.traceFailure(Action.ReadPersistedTabState, { message, componentName });
return defaultValue;
}
const state = loadState({
componentName: componentName,
subComponentName,
globalAccountName,
databaseName: collection ? collection.databaseId : "",
containerName: collection ? collection.id() : "",
}) as T;
return state || defaultValue;
};
/**
*
* @param subComponentName
* @param collection
* @param state State to save
* @param debounce true for high-frequency calls (e.g mouse drag events)
*/
export const saveSubComponentState = <T>(
componentName: AppStateComponentNames,
subComponentName: string,
collection: ViewModels.CollectionBase | undefined,
state: T,
debounce?: boolean,
): void => {
const globalAccountName = userContext.databaseAccount?.name;
if (!globalAccountName) {
const message = "Database account name not found in userContext";
console.error(message);
TelemetryProcessor.traceFailure(Action.SavePersistedTabState, { message, componentName });
return;
}
(debounce ? saveStateDebounced : saveState)(
{
componentName: componentName,
subComponentName,
globalAccountName,
databaseName: collection ? collection.databaseId : "",
containerName: collection ? collection.id() : "",
},
state,
);
};
export const deleteSubComponentState = (
componentName: AppStateComponentNames,
subComponentName: string,
collection: ViewModels.CollectionBase,
) => {
const globalAccountName = userContext.databaseAccount?.name;
if (!globalAccountName) {
const message = "Database account name not found in userContext";
console.error(message);
TelemetryProcessor.traceFailure(Action.DeletePersistedTabState, { message, componentName });
return;
}
deleteState({
componentName: componentName,
subComponentName,
globalAccountName,
databaseName: collection.databaseId,
containerName: collection.id(),
});
};

View File

@@ -81,6 +81,7 @@ export interface UserContext {
readonly endpoint?: string; readonly endpoint?: string;
readonly aadToken?: string; readonly aadToken?: string;
readonly accessToken?: string; readonly accessToken?: string;
readonly armToken?: string;
readonly authorizationToken?: string; readonly authorizationToken?: string;
readonly resourceToken?: string; readonly resourceToken?: string;
readonly subscriptionType?: SubscriptionType; readonly subscriptionType?: SubscriptionType;
@@ -104,7 +105,7 @@ export interface UserContext {
readonly vcoreMongoConnectionParams?: VCoreMongoConnectionParams; readonly vcoreMongoConnectionParams?: VCoreMongoConnectionParams;
readonly feedbackPolicies?: AdminFeedbackPolicySettings; readonly feedbackPolicies?: AdminFeedbackPolicySettings;
readonly dataPlaneRbacEnabled?: boolean; readonly dataPlaneRbacEnabled?: boolean;
readonly refreshCosmosClient?: boolean; readonly hasDataPlaneRbacSettingChanged?: boolean;
} }
export type ApiType = "SQL" | "Mongo" | "Gremlin" | "Tables" | "Cassandra" | "Postgres" | "VCoreMongo"; export type ApiType = "SQL" | "Mongo" | "Gremlin" | "Tables" | "Cassandra" | "Postgres" | "VCoreMongo";

View File

@@ -93,7 +93,7 @@ export const MongoProxyOutboundIPs: { [key: string]: string[] } = {
}; };
export const defaultAllowedMongoProxyEndpoints: ReadonlyArray<string> = [ export const defaultAllowedMongoProxyEndpoints: ReadonlyArray<string> = [
MongoProxyEndpoints.Development, MongoProxyEndpoints.Local,
MongoProxyEndpoints.Mpac, MongoProxyEndpoints.Mpac,
MongoProxyEndpoints.Prod, MongoProxyEndpoints.Prod,
MongoProxyEndpoints.Fairfax, MongoProxyEndpoints.Fairfax,
@@ -141,7 +141,9 @@ 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> = ["https://dataexplorer-preview.azurewebsites.net/"]; export const allowedMsalRedirectEndpoints: ReadonlyArray<string> = [
"https://cosmos-explorer-preview.azurewebsites.net/",
];
export const allowedJunoOrigins: ReadonlyArray<string> = [ export const allowedJunoOrigins: ReadonlyArray<string> = [
JunoEndpoints.Test, JunoEndpoints.Test,

View File

@@ -0,0 +1,104 @@
import { MongoProxyEndpoints, PortalBackendEndpoints } from "Common/Constants";
import { resetConfigContext, updateConfigContext } from "ConfigContext";
import { DatabaseAccount, IpRule } from "Contracts/DataModels";
import { updateUserContext } from "UserContext";
import { MongoProxyOutboundIPs, PortalBackendOutboundIPs } from "Utils/EndpointUtils";
import { getNetworkSettingsWarningMessage } from "./NetworkUtility";
describe("NetworkUtility tests", () => {
describe("getNetworkSettingsWarningMessage", () => {
const publicAccessMessagePart = "Please enable public access to proceed";
const accessMessagePart = "Please allow access from Azure Portal to proceed";
let warningMessageResult: string;
const warningMessageFunc = (msg: string) => (warningMessageResult = msg);
beforeEach(() => {
warningMessageResult = undefined;
});
afterEach(() => {
resetConfigContext();
});
it("should return no message when publicNetworkAccess is enabled", async () => {
updateUserContext({
databaseAccount: {
properties: {
publicNetworkAccess: "Enabled",
},
} as DatabaseAccount,
});
await getNetworkSettingsWarningMessage(warningMessageFunc);
expect(warningMessageResult).toBeUndefined();
});
it("should return publicAccessMessage when publicNetworkAccess is disabled", async () => {
updateUserContext({
databaseAccount: {
properties: {
publicNetworkAccess: "Disabled",
},
} as DatabaseAccount,
});
await getNetworkSettingsWarningMessage(warningMessageFunc);
expect(warningMessageResult).toContain(publicAccessMessagePart);
});
it(`should return no message when the appropriate ip rules are added to mongo/cassandra account per endpoint`, async () => {
const portalBackendOutboundIPs: string[] = [
...PortalBackendOutboundIPs[PortalBackendEndpoints.Mpac],
...PortalBackendOutboundIPs[PortalBackendEndpoints.Prod],
...MongoProxyOutboundIPs[MongoProxyEndpoints.Mpac],
...MongoProxyOutboundIPs[MongoProxyEndpoints.Prod],
];
updateUserContext({
databaseAccount: {
kind: "MongoDB",
properties: {
ipRules: portalBackendOutboundIPs.map((ip: string) => ({ ipAddressOrRange: ip }) as IpRule),
publicNetworkAccess: "Enabled",
},
} as DatabaseAccount,
});
updateConfigContext({
PORTAL_BACKEND_ENDPOINT: PortalBackendEndpoints.Mpac,
MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Mpac,
});
let asyncWarningMessageResult: string;
const asyncWarningMessageFunc = (msg: string) => (asyncWarningMessageResult = msg);
await getNetworkSettingsWarningMessage(asyncWarningMessageFunc);
expect(asyncWarningMessageResult).toBeUndefined();
});
it("should return accessMessage when incorrent ip rule is added to mongo/cassandra account per endpoint", async () => {
updateUserContext({
databaseAccount: {
kind: "MongoDB",
properties: {
ipRules: [{ ipAddressOrRange: "1.1.1.1" }],
publicNetworkAccess: "Enabled",
},
} as DatabaseAccount,
});
updateConfigContext({
PORTAL_BACKEND_ENDPOINT: PortalBackendEndpoints.Mpac,
MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Mpac,
});
let asyncWarningMessageResult: string;
const asyncWarningMessageFunc = (msg: string) => (asyncWarningMessageResult = msg);
await getNetworkSettingsWarningMessage(asyncWarningMessageFunc);
expect(asyncWarningMessageResult).toContain(accessMessagePart);
});
// Postgres and vcore mongo account checks basically pass through to CheckFirewallRules so those
// tests are omitted here and included in CheckFirewallRules.test.ts
});
});

View File

@@ -0,0 +1,99 @@
import { CassandraProxyEndpoints, MongoProxyEndpoints, PortalBackendEndpoints } from "Common/Constants";
import { configContext } from "ConfigContext";
import { checkFirewallRules } from "Explorer/Tabs/Shared/CheckFirewallRules";
import { userContext } from "UserContext";
import { CassandraProxyOutboundIPs, MongoProxyOutboundIPs, PortalBackendOutboundIPs } from "Utils/EndpointUtils";
export const getNetworkSettingsWarningMessage = async (
setStateFunc: (warningMessage: string) => void,
): Promise<void> => {
const accountProperties = userContext.databaseAccount?.properties;
const accessMessage =
"The Network settings for this account are preventing access from Data Explorer. Please allow access from Azure Portal to proceed.";
const publicAccessMessage =
"The Network settings for this account are preventing access from Data Explorer. Please enable public access to proceed.";
if (userContext.apiType === "Postgres") {
checkFirewallRules(
"2022-11-08",
(rule) => rule.properties.startIpAddress === "0.0.0.0" && rule.properties.endIpAddress === "255.255.255.255",
undefined,
setStateFunc,
accessMessage,
);
return;
} else if (userContext.apiType === "VCoreMongo") {
checkFirewallRules(
"2023-03-01-preview",
(rule) =>
rule.name.startsWith("AllowAllAzureServicesAndResourcesWithinAzureIps") ||
(rule.properties.startIpAddress === "0.0.0.0" && rule.properties.endIpAddress === "255.255.255.255"),
undefined,
setStateFunc,
accessMessage,
);
return;
} else if (accountProperties) {
// public network access is disabled
if (
accountProperties.publicNetworkAccess !== "Enabled" &&
accountProperties.publicNetworkAccess !== "SecuredByPerimeter"
) {
setStateFunc(publicAccessMessage);
return;
}
const ipRules = accountProperties.ipRules;
// public network access is NOT set to "All networks"
if (ipRules?.length > 0) {
const isProdOrMpacPortalBackendEndpoint: boolean = [
PortalBackendEndpoints.Mpac,
PortalBackendEndpoints.Prod,
].includes(configContext.PORTAL_BACKEND_ENDPOINT);
const portalBackendOutboundIPs: string[] = isProdOrMpacPortalBackendEndpoint
? [
...PortalBackendOutboundIPs[PortalBackendEndpoints.Mpac],
...PortalBackendOutboundIPs[PortalBackendEndpoints.Prod],
]
: PortalBackendOutboundIPs[configContext.PORTAL_BACKEND_ENDPOINT];
let portalIPs: string[] = [...portalBackendOutboundIPs];
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];
portalIPs = [...portalIPs, ...mongoProxyOutboundIPs];
} 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];
portalIPs = [...portalIPs, ...cassandraProxyOutboundIPs];
}
let numberOfMatches = 0;
ipRules.forEach((ipRule) => {
if (portalIPs.indexOf(ipRule.ipAddressOrRange) !== -1) {
numberOfMatches++;
}
});
if (numberOfMatches !== portalIPs.length) {
setStateFunc(accessMessage);
}
}
}
};

View File

@@ -3,6 +3,7 @@ import { useBoolean } from "@fluentui/react-hooks";
import * as React from "react"; import * as React from "react";
import { configContext } from "../ConfigContext"; import { configContext } from "../ConfigContext";
import { acquireTokenWithMsal, getMsalInstance } from "../Utils/AuthorizationUtils"; import { acquireTokenWithMsal, getMsalInstance } from "../Utils/AuthorizationUtils";
import { updateUserContext } from "UserContext";
const msalInstance = await getMsalInstance(); const msalInstance = await getMsalInstance();
@@ -79,7 +80,7 @@ export function useAADAuth(): ReturnType {
authority: `${configContext.AAD_ENDPOINT}${tenantId}`, authority: `${configContext.AAD_ENDPOINT}${tenantId}`,
scopes: [`${configContext.ARM_ENDPOINT}/.default`], scopes: [`${configContext.ARM_ENDPOINT}/.default`],
}); });
updateUserContext({ armToken: armToken });
setArmToken(armToken); setArmToken(armToken);
setAuthFailure(null); setAuthFailure(null);
} catch (error) { } catch (error) {

View File

@@ -1,6 +1,8 @@
import { HttpHeaders } from "Common/Constants"; import { HttpHeaders } from "Common/Constants";
import { QueryRequestOptions, QueryResponse } from "Contracts/AzureResourceGraph"; import { QueryRequestOptions, QueryResponse } from "Contracts/AzureResourceGraph";
import useSWR from "swr"; import useSWR from "swr";
import { updateUserContext, userContext } from "UserContext";
import { acquireTokenWithMsal, getMsalInstance } from "Utils/AuthorizationUtils";
import { configContext } from "../ConfigContext"; import { configContext } from "../ConfigContext";
import { DatabaseAccount } from "../Contracts/DataModels"; import { DatabaseAccount } from "../Contracts/DataModels";
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
@@ -33,12 +35,9 @@ export async function fetchDatabaseAccounts(subscriptionId: string, accessToken:
return accounts.sort((a, b) => a.name.localeCompare(b.name)); return accounts.sort((a, b) => a.name.localeCompare(b.name));
} }
export async function fetchDatabaseAccountsFromGraph( export async function fetchDatabaseAccountsFromGraph(subscriptionId: string): Promise<DatabaseAccount[]> {
subscriptionId: string,
accessToken: string,
): Promise<DatabaseAccount[]> {
const headers = new Headers(); const headers = new Headers();
const bearer = `Bearer ${accessToken}`; const bearer = `Bearer ${userContext.armToken}`;
headers.append("Authorization", bearer); headers.append("Authorization", bearer);
headers.append(HttpHeaders.contentType, "application/json"); headers.append(HttpHeaders.contentType, "application/json");
@@ -46,8 +45,9 @@ export async function fetchDatabaseAccountsFromGraph(
const apiVersion = "2021-03-01"; const apiVersion = "2021-03-01";
const managementResourceGraphAPIURL = `${configContext.ARM_ENDPOINT}providers/Microsoft.ResourceGraph/resources?api-version=${apiVersion}`; const managementResourceGraphAPIURL = `${configContext.ARM_ENDPOINT}providers/Microsoft.ResourceGraph/resources?api-version=${apiVersion}`;
const databaseAccounts: DatabaseAccount[] = []; let databaseAccounts: DatabaseAccount[] = [];
let skipToken: string; let skipToken: string;
console.log("Old ARM Token - fetchDatabaseAccountsFromGraph function", userContext.armToken);
do { do {
const body = { const body = {
query: databaseAccountsQuery, query: databaseAccountsQuery,
@@ -85,10 +85,81 @@ export async function fetchDatabaseAccountsFromGraph(
return databaseAccounts.sort((a, b) => a.name.localeCompare(b.name)); return databaseAccounts.sort((a, b) => a.name.localeCompare(b.name));
} }
export function useDatabaseAccounts(subscriptionId: string, armToken: string): DatabaseAccount[] | undefined { export function useDatabaseAccounts(subscriptionId: string): DatabaseAccount[] | undefined {
const { data } = useSWR( const { data } = useSWR(
() => (armToken && subscriptionId ? ["databaseAccounts", subscriptionId, armToken] : undefined), () => (subscriptionId ? ["databaseAccounts", subscriptionId] : undefined),
(_, subscriptionId, armToken) => fetchDatabaseAccountsFromGraph(subscriptionId, armToken), (_, subscriptionId) => runCommand(fetchDatabaseAccountsFromGraph, subscriptionId),
); );
return data; return data;
} }
// Define the types for your responses
interface DatabaseAccount {
name: string;
id: string;
// Add other relevant fields as per your use case
}
interface QueryRequestOptions {
$top?: number;
$skipToken?: string;
$allowPartialScopes?: boolean;
}
// Define the configuration context and headers if not already defined
const configContext = {
ARM_ENDPOINT: "https://management.azure.com/",
AAD_ENDPOINT: "https://login.microsoftonline.com/",
};
interface QueryResponse {
data?: any[];
$skipToken?: string;
}
export async function runCommand<T>(fn: (...args: any[]) => Promise<T>, ...args: any[]): Promise<T> {
try {
// Attempt to execute the function passed as an argument
const result = await fn(...args);
console.log("Successfully executed function:", fn.name, result);
return result;
} catch (error) {
// Handle any error that is thrown during the execution of the function
if (error) {
console.log("Creating new token");
const msalInstance = await getMsalInstance();
const cachedAccount = msalInstance.getAllAccounts()?.[0];
const cachedTenantId = localStorage.getItem("cachedTenantId");
msalInstance.setActiveAccount(cachedAccount);
// TODO: Add condition to check if the ARM token needs to be renewed, then we need to run the code below for creating the ARM token
console.log("Creating new ARM token");
const newAccessToken = await acquireTokenWithMsal(msalInstance, {
authority: `${configContext.AAD_ENDPOINT}${cachedTenantId}`,
scopes: [`${configContext.ARM_ENDPOINT}/.default`],
});
updateUserContext({ armToken: newAccessToken });
// TODO: add condition to check if AAD token needs to be renewed (i.e) Token provider has failed with expired AAD token and create a new AAD Token using the below code
// const hrefEndpoint = new URL(userContext.databaseAccount.properties.documentEndpoint).href.replace(/\/$/, "/.default");
// console.log('Creating new AAD token');
// let aadToken = await acquireTokenWithMsal(msalInstance, {
// forceRefresh: true,
// authority: `${configContext.AAD_ENDPOINT}${cachedTenantId}`,
// scopes: [hrefEndpoint],
// });
// updateUserContext({aadToken: aadToken});
//console.log('Latest AAD Token', fn.name, userContext.aadToken);
const result = await fn(...args);
return result;
} else {
console.error("An error occurred:", error.message);
throw new error();
}
}
}

View File

@@ -7,13 +7,9 @@ import Explorer from "Explorer/Explorer";
import { useDataPlaneRbac } from "Explorer/Panes/SettingsPane/SettingsPane"; import { useDataPlaneRbac } from "Explorer/Panes/SettingsPane/SettingsPane";
import { useSelectedNode } from "Explorer/useSelectedNode"; import { useSelectedNode } from "Explorer/useSelectedNode";
import { scheduleRefreshDatabaseResourceToken } from "Platform/Fabric/FabricUtil"; import { scheduleRefreshDatabaseResourceToken } from "Platform/Fabric/FabricUtil";
import {
AppStateComponentNames,
OPEN_TABS_SUBCOMPONENT_NAME,
readSubComponentState,
} from "Shared/AppStatePersistenceUtility";
import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility"; import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
import { useNewPortalBackendEndpoint } from "Utils/EndpointUtils"; import { useNewPortalBackendEndpoint } from "Utils/EndpointUtils";
import { getNetworkSettingsWarningMessage } from "Utils/NetworkUtility";
import { logConsoleError } from "Utils/NotificationConsoleUtils"; import { logConsoleError } from "Utils/NotificationConsoleUtils";
import { useQueryCopilot } from "hooks/useQueryCopilot"; import { useQueryCopilot } from "hooks/useQueryCopilot";
import { ReactTabKind, useTabs } from "hooks/useTabs"; import { ReactTabKind, useTabs } from "hooks/useTabs";
@@ -84,9 +80,6 @@ export function useKnockoutExplorer(platform: Platform): Explorer {
await updateContextForCopilot(explorer); await updateContextForCopilot(explorer);
await updateContextForSampleData(explorer); await updateContextForSampleData(explorer);
} }
restoreOpenTabs();
setExplorer(explorer); setExplorer(explorer);
} }
}; };
@@ -139,7 +132,7 @@ async function configureFabric(): Promise<Explorer> {
await scheduleRefreshDatabaseResourceToken(true); await scheduleRefreshDatabaseResourceToken(true);
resolve(explorer); resolve(explorer);
await explorer.refreshAllDatabases(); await explorer.refreshAllDatabases();
if (userContext.fabricContext.isVisible) { if (userContext.fabricContext.isVisible && !firstContainerOpened) {
firstContainerOpened = true; firstContainerOpened = true;
openFirstContainer(explorer, userContext.fabricContext.databaseConnectionInfo.databaseId); openFirstContainer(explorer, userContext.fabricContext.databaseConnectionInfo.databaseId);
} }
@@ -436,7 +429,6 @@ function createExplorerFabric(params: { connectionId: string; isVisible: boolean
}, },
}, },
}); });
useTabs.getState().closeAllTabs();
const explorer = new Explorer(); const explorer = new Explorer();
return explorer; return explorer;
} }
@@ -739,6 +731,8 @@ function updateContextsFromPortalMessage(inputs: DataExplorerInputsFrame) {
} }
} }
getNetworkSettingsWarningMessage(useTabs.getState().setNetworkSettingsWarning);
if (inputs.features) { if (inputs.features) {
Object.assign(userContext.features, extractFeatures(new URLSearchParams(inputs.features))); Object.assign(userContext.features, extractFeatures(new URLSearchParams(inputs.features)));
} }
@@ -822,17 +816,3 @@ async function updateContextForSampleData(explorer: Explorer): Promise<void> {
interface SampledataconnectionResponse { interface SampledataconnectionResponse {
connectionString: string; connectionString: string;
} }
const restoreOpenTabs = () => {
const openTabsState = readSubComponentState<(DataExplorerAction | undefined)[]>(
AppStateComponentNames.DataExplorerAction,
OPEN_TABS_SUBCOMPONENT_NAME,
undefined,
[],
);
openTabsState.forEach((openTabState) => {
if (openTabState) {
handleOpenAction(openTabState, useDatabases.getState().databases, this);
}
});
};

View File

@@ -3,6 +3,8 @@ import { QueryRequestOptions, QueryResponse } from "Contracts/AzureResourceGraph
import useSWR from "swr"; import useSWR from "swr";
import { configContext } from "../ConfigContext"; import { configContext } from "../ConfigContext";
import { Subscription } from "../Contracts/DataModels"; import { Subscription } from "../Contracts/DataModels";
import { runCommand } from "hooks/useDatabaseAccounts";
import { userContext } from "UserContext";
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
interface SubscriptionListResult { interface SubscriptionListResult {
@@ -35,9 +37,9 @@ export async function fetchSubscriptions(accessToken: string): Promise<Subscript
return subscriptions.sort((a, b) => a.displayName.localeCompare(b.displayName)); return subscriptions.sort((a, b) => a.displayName.localeCompare(b.displayName));
} }
export async function fetchSubscriptionsFromGraph(accessToken: string): Promise<Subscription[]> { export async function fetchSubscriptionsFromGraph(): Promise<Subscription[]> {
const headers = new Headers(); const headers = new Headers();
const bearer = `Bearer ${accessToken}`; const bearer = `Bearer ${userContext.armToken}`;
headers.append("Authorization", bearer); headers.append("Authorization", bearer);
headers.append(HttpHeaders.contentType, "application/json"); headers.append(HttpHeaders.contentType, "application/json");
@@ -48,6 +50,7 @@ export async function fetchSubscriptionsFromGraph(accessToken: string): Promise<
const subscriptions: Subscription[] = []; const subscriptions: Subscription[] = [];
let skipToken: string; let skipToken: string;
console.log("Old ARM Token fetchSubscriptionsFromGraph fn", userContext.armToken);
do { do {
const body = { const body = {
query: subscriptionsQuery, query: subscriptionsQuery,
@@ -86,9 +89,14 @@ export async function fetchSubscriptionsFromGraph(accessToken: string): Promise<
} }
export function useSubscriptions(armToken: string): Subscription[] | undefined { export function useSubscriptions(armToken: string): Subscription[] | undefined {
const { data } = useSWR( const { data, error } = useSWR(
() => (armToken ? ["subscriptions", armToken] : undefined), () => (armToken ? ["subscriptions", armToken] : undefined),
(_, armToken) => fetchSubscriptionsFromGraph(armToken), (_) => runCommand(fetchSubscriptionsFromGraph),
); );
if (error) {
console.error("Error fetching subscriptions:", error);
}
return data; return data;
} }

View File

@@ -1,11 +1,5 @@
import { clamp } from "@fluentui/react"; import { clamp } from "@fluentui/react";
import { OpenTab } from "Contracts/ActionContracts";
import { useSelectedNode } from "Explorer/useSelectedNode"; import { useSelectedNode } from "Explorer/useSelectedNode";
import {
AppStateComponentNames,
OPEN_TABS_SUBCOMPONENT_NAME,
saveSubComponentState,
} from "Shared/AppStatePersistenceUtility";
import create, { UseStore } from "zustand"; import create, { UseStore } from "zustand";
import * as ViewModels from "../Contracts/ViewModels"; import * as ViewModels from "../Contracts/ViewModels";
import { CollectionTabKind } from "../Contracts/ViewModels"; import { CollectionTabKind } from "../Contracts/ViewModels";
@@ -18,6 +12,7 @@ export interface TabsState {
openedReactTabs: ReactTabKind[]; openedReactTabs: ReactTabKind[];
activeTab: TabsBase | undefined; activeTab: TabsBase | undefined;
activeReactTab: ReactTabKind | undefined; activeReactTab: ReactTabKind | undefined;
networkSettingsWarning: string;
queryCopilotTabInitialInput: string; queryCopilotTabInitialInput: string;
isTabExecuting: boolean; isTabExecuting: boolean;
isQueryErrorThrown: boolean; isQueryErrorThrown: boolean;
@@ -32,6 +27,7 @@ export interface TabsState {
closeAllNotebookTabs: (hardClose: boolean) => void; closeAllNotebookTabs: (hardClose: boolean) => void;
openAndActivateReactTab: (tabKind: ReactTabKind) => void; openAndActivateReactTab: (tabKind: ReactTabKind) => void;
closeReactTab: (tabKind: ReactTabKind) => void; closeReactTab: (tabKind: ReactTabKind) => void;
setNetworkSettingsWarning: (warningMessage: string) => void;
setQueryCopilotTabInitialInput: (input: string) => void; setQueryCopilotTabInitialInput: (input: string) => void;
setIsTabExecuting: (state: boolean) => void; setIsTabExecuting: (state: boolean) => void;
setIsQueryErrorThrown: (state: boolean) => void; setIsQueryErrorThrown: (state: boolean) => void;
@@ -40,8 +36,6 @@ export interface TabsState {
selectLeftTab: () => void; selectLeftTab: () => void;
selectRightTab: () => void; selectRightTab: () => void;
closeActiveTab: () => void; closeActiveTab: () => void;
closeAllTabs: () => void;
persistTabsState: () => void;
} }
export enum ReactTabKind { export enum ReactTabKind {
@@ -67,6 +61,7 @@ export const useTabs: UseStore<TabsState> = create((set, get) => ({
openedReactTabs: !isPlatformFabric ? [ReactTabKind.Home] : [], openedReactTabs: !isPlatformFabric ? [ReactTabKind.Home] : [],
activeTab: undefined, activeTab: undefined,
activeReactTab: !isPlatformFabric ? ReactTabKind.Home : undefined, activeReactTab: !isPlatformFabric ? ReactTabKind.Home : undefined,
networkSettingsWarning: "",
queryCopilotTabInitialInput: "", queryCopilotTabInitialInput: "",
isTabExecuting: false, isTabExecuting: false,
isQueryErrorThrown: false, isQueryErrorThrown: false,
@@ -78,9 +73,7 @@ export const useTabs: UseStore<TabsState> = create((set, get) => ({
}, },
activateNewTab: (tab: TabsBase): void => { activateNewTab: (tab: TabsBase): void => {
set((state) => ({ openedTabs: [...state.openedTabs, tab], activeTab: tab, activeReactTab: undefined })); set((state) => ({ openedTabs: [...state.openedTabs, tab], activeTab: tab, activeReactTab: undefined }));
tab.triggerPersistState = get().persistTabsState;
tab.onActivate(); tab.onActivate();
get().persistTabsState();
}, },
activateReactTab: (tabKind: ReactTabKind): void => { activateReactTab: (tabKind: ReactTabKind): void => {
// Clear the selected node when switching to a react tab. // Clear the selected node when switching to a react tab.
@@ -137,8 +130,6 @@ export const useTabs: UseStore<TabsState> = create((set, get) => ({
} }
set({ openedTabs: updatedTabs }); set({ openedTabs: updatedTabs });
get().persistTabsState();
}, },
closeAllNotebookTabs: (hardClose): void => { closeAllNotebookTabs: (hardClose): void => {
const isNotebook = (tabKind: CollectionTabKind): boolean => { const isNotebook = (tabKind: CollectionTabKind): boolean => {
@@ -187,6 +178,7 @@ export const useTabs: UseStore<TabsState> = create((set, get) => ({
set({ openedReactTabs: updatedOpenedReactTabs }); set({ openedReactTabs: updatedOpenedReactTabs });
}, },
setNetworkSettingsWarning: (warningMessage: string) => set({ networkSettingsWarning: warningMessage }),
setQueryCopilotTabInitialInput: (input: string) => set({ queryCopilotTabInitialInput: input }), setQueryCopilotTabInitialInput: (input: string) => set({ queryCopilotTabInitialInput: input }),
setIsTabExecuting: (state: boolean) => { setIsTabExecuting: (state: boolean) => {
set({ isTabExecuting: state }); set({ isTabExecuting: state });
@@ -234,18 +226,4 @@ export const useTabs: UseStore<TabsState> = create((set, get) => ({
state.closeTab(state.activeTab); state.closeTab(state.activeTab);
} }
}, },
closeAllTabs: () => {
set({ openedTabs: [], openedReactTabs: [], activeTab: undefined, activeReactTab: undefined });
},
persistTabsState: () => {
const state = get();
const openTabsStates = state.openedTabs.map((tab) => tab.getPersistedState());
saveSubComponentState<OpenTab[]>(
AppStateComponentNames.DataExplorerAction,
OPEN_TABS_SUBCOMPONENT_NAME,
undefined,
openTabsStates,
);
},
})); }));

View File

@@ -1,4 +1,4 @@
import { AzureCliCredential } from "@azure/identity"; import { AzureCliCredentials } from "@azure/ms-rest-nodeauth";
import { expect, Frame, Locator, Page } from "@playwright/test"; import { expect, Frame, Locator, Page } from "@playwright/test";
import crypto from "crypto"; import crypto from "crypto";
@@ -20,13 +20,13 @@ export function generateUniqueName(baseName, options?: TestNameOptions): string
return `${prefix}${baseName}${crypto.randomBytes(length).toString("hex")}${suffix}`; return `${prefix}${baseName}${crypto.randomBytes(length).toString("hex")}${suffix}`;
} }
export function getAzureCLICredentials(): AzureCliCredential { export async function getAzureCLICredentials(): Promise<AzureCliCredentials> {
return new AzureCliCredential(); return await AzureCliCredentials.create();
} }
export async function getAzureCLICredentialsToken(): Promise<string> { export async function getAzureCLICredentialsToken(): Promise<string> {
const credentials = getAzureCLICredentials(); const credentials = await getAzureCLICredentials();
const token = (await credentials.getToken("https://management.core.windows.net//.default")).token; const token = (await credentials.getToken()).accessToken;
return token; return token;
} }

View File

@@ -13,7 +13,7 @@ import {
} from "../fx"; } from "../fx";
test("SQL account using Resource token", async ({ page }) => { test("SQL account using Resource token", async ({ page }) => {
const credentials = getAzureCLICredentials(); const credentials = await getAzureCLICredentials();
const armClient = new CosmosDBManagementClient(credentials, subscriptionId); const armClient = new CosmosDBManagementClient(credentials, subscriptionId);
const accountName = getAccountName(TestAccount.SQL); const accountName = getAccountName(TestAccount.SQL);
const account = await armClient.databaseAccounts.get(resourceGroupName, accountName); const account = await armClient.databaseAccounts.get(resourceGroupName, accountName);

View File

@@ -56,7 +56,7 @@ export class TestContainerContext {
export async function createTestSQLContainer(includeTestData?: boolean) { export async function createTestSQLContainer(includeTestData?: boolean) {
const databaseId = generateUniqueName("db"); const databaseId = generateUniqueName("db");
const containerId = "testcontainer"; // A unique container name isn't needed because the database is unique const containerId = "testcontainer"; // A unique container name isn't needed because the database is unique
const credentials = getAzureCLICredentials(); const credentials = await getAzureCLICredentials();
const armClient = new CosmosDBManagementClient(credentials, subscriptionId); const armClient = new CosmosDBManagementClient(credentials, subscriptionId);
const accountName = getAccountName(TestAccount.SQL); const accountName = getAccountName(TestAccount.SQL);
const account = await armClient.databaseAccounts.get(resourceGroupName, accountName); const account = await armClient.databaseAccounts.get(resourceGroupName, accountName);

View File

@@ -30,7 +30,7 @@
<clear /> <clear />
<add name="X-Xss-Protection" value="1; mode=block" /> <add name="X-Xss-Protection" value="1; mode=block" />
<add name="X-Content-Type-Options" value="nosniff" /> <add name="X-Content-Type-Options" value="nosniff" />
<add name="Content-Security-Policy" value="frame-ancestors 'self' portal.azure.com *.portal.azure.com portal.azure.us portal.azure.cn portal.microsoftazure.de df.onecloud.azure-test.net *.fabric.microsoft.com *.powerbi.com *.analysis-df.windows.net dataexplorer-preview.azurewebsites.net" /> <add name="Content-Security-Policy" value="frame-ancestors 'self' portal.azure.com *.portal.azure.com portal.azure.us portal.azure.cn portal.microsoftazure.de df.onecloud.azure-test.net *.fabric.microsoft.com *.powerbi.com *.analysis-df.windows.net cosmos-explorer-preview.azurewebsites.net" />
</customHeaders> </customHeaders>
<redirectHeaders> <redirectHeaders>
<clear /> <clear />

View File

@@ -20,7 +20,9 @@ const isCI = require("is-ci");
const gitSha = childProcess.execSync("git rev-parse HEAD").toString("utf8"); const gitSha = childProcess.execSync("git rev-parse HEAD").toString("utf8");
const AZURE_CLIENT_ID = "fd8753b0-0707-4e32-84e9-2532af865fb4";
const AZURE_TENANT_ID = "72f988bf-86f1-41af-91ab-2d7cd011db47"; const AZURE_TENANT_ID = "72f988bf-86f1-41af-91ab-2d7cd011db47";
const SUBSCRIPTION_ID = "69e02f2d-f059-4409-9eac-97e8a276ae2c";
const RESOURCE_GROUP = "de-e2e-tests"; const RESOURCE_GROUP = "de-e2e-tests";
const AZURE_CLIENT_SECRET = process.env.AZURE_CLIENT_SECRET || process.env.NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET; // TODO Remove. Exists for backwards compat with old .env files. Prefer AZURE_CLIENT_SECRET const AZURE_CLIENT_SECRET = process.env.AZURE_CLIENT_SECRET || process.env.NOTEBOOKS_TEST_RUNNER_CLIENT_SECRET; // TODO Remove. Exists for backwards compat with old .env files. Prefer AZURE_CLIENT_SECRET
@@ -94,7 +96,10 @@ module.exports = function (_env = {}, argv = {}) {
if (mode === "development") { if (mode === "development") {
envVars.NODE_ENV = "development"; envVars.NODE_ENV = "development";
envVars.AZURE_CLIENT_ID = AZURE_CLIENT_ID;
envVars.AZURE_TENANT_ID = AZURE_TENANT_ID;
envVars.AZURE_CLIENT_SECRET = AZURE_CLIENT_SECRET || null; envVars.AZURE_CLIENT_SECRET = AZURE_CLIENT_SECRET || null;
envVars.SUBSCRIPTION_ID = SUBSCRIPTION_ID;
envVars.RESOURCE_GROUP = RESOURCE_GROUP; envVars.RESOURCE_GROUP = RESOURCE_GROUP;
typescriptRule.use[0].options.compilerOptions = { target: "ES2018" }; typescriptRule.use[0].options.compilerOptions = { target: "ES2018" };
} }
@@ -182,12 +187,7 @@ module.exports = function (_env = {}, argv = {}) {
}), }),
new MonacoWebpackPlugin(), new MonacoWebpackPlugin(),
new CopyWebpackPlugin({ new CopyWebpackPlugin({
patterns: [ patterns: [{ from: "DataExplorer.nuspec" }, { from: "web.config" }, { from: "quickstart/*.zip" }],
{ from: "DataExplorer.nuspec" },
{ from: "DataExplorer.proj" },
{ from: "web.config" },
{ from: "quickstart/*.zip" },
],
}), }),
new EnvironmentPlugin(envVars), new EnvironmentPlugin(envVars),
]; ];