mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-03-12 12:56:09 +00:00
Merge branch 'master' of https://github.com/Azure/cosmos-explorer
This commit is contained in:
commit
d12f10afd8
10
.github/workflows/ci.yml
vendored
10
.github/workflows/ci.yml
vendored
@ -92,7 +92,7 @@ jobs:
|
|||||||
NODE_OPTIONS: "--max-old-space-size=4096"
|
NODE_OPTIONS: "--max-old-space-size=4096"
|
||||||
- run: cp -r ./Contracts ./dist/contracts
|
- run: cp -r ./Contracts ./dist/contracts
|
||||||
- run: cp -r ./configs ./dist/configs
|
- run: cp -r ./configs ./dist/configs
|
||||||
- uses: actions/upload-artifact@v3
|
- uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: dist
|
name: dist
|
||||||
path: dist/
|
path: dist/
|
||||||
@ -117,14 +117,14 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
nuget-api-key: ${{ secrets.NUGET_API_KEY }}
|
nuget-api-key: ${{ secrets.NUGET_API_KEY }}
|
||||||
- name: Download Dist Folder
|
- name: Download Dist Folder
|
||||||
uses: actions/download-artifact@v3
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: dist
|
name: dist
|
||||||
- run: cp ./configs/prod.json config.json
|
- run: cp ./configs/prod.json config.json
|
||||||
- run: nuget sources add -Name "ADO" -Source "$NUGET_SOURCE" -UserName "jawelton@microsoft.com" -Password "$AZURE_DEVOPS_PAT"
|
- run: nuget sources add -Name "ADO" -Source "$NUGET_SOURCE" -UserName "jawelton@microsoft.com" -Password "$AZURE_DEVOPS_PAT"
|
||||||
- run: nuget pack -Version "2.0.0-github-${GITHUB_SHA}"
|
- run: nuget pack -Version "2.0.0-github-${GITHUB_SHA}"
|
||||||
- run: nuget push -SkipDuplicate -Source "$NUGET_SOURCE" -ApiKey Az *.nupkg
|
- run: nuget push -SkipDuplicate -Source "$NUGET_SOURCE" -ApiKey Az *.nupkg
|
||||||
- uses: actions/upload-artifact@v3
|
- uses: actions/upload-artifact@v4
|
||||||
name: packages
|
name: packages
|
||||||
with:
|
with:
|
||||||
path: "*.nupkg"
|
path: "*.nupkg"
|
||||||
@ -141,7 +141,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
nuget-api-key: ${{ secrets.NUGET_API_KEY }}
|
nuget-api-key: ${{ secrets.NUGET_API_KEY }}
|
||||||
- name: Download Dist Folder
|
- name: Download Dist Folder
|
||||||
uses: actions/download-artifact@v3
|
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
|
||||||
@ -149,7 +149,7 @@ jobs:
|
|||||||
- run: nuget sources add -Name "ADO" -Source "$NUGET_SOURCE" -UserName "jawelton@microsoft.com" -Password "$AZURE_DEVOPS_PAT"
|
- run: nuget sources add -Name "ADO" -Source "$NUGET_SOURCE" -UserName "jawelton@microsoft.com" -Password "$AZURE_DEVOPS_PAT"
|
||||||
- run: nuget pack -Version "2.0.0-github-${GITHUB_SHA}"
|
- run: nuget pack -Version "2.0.0-github-${GITHUB_SHA}"
|
||||||
- run: nuget push -SkipDuplicate -Source "$NUGET_SOURCE" -ApiKey Az *.nupkg
|
- run: nuget push -SkipDuplicate -Source "$NUGET_SOURCE" -ApiKey Az *.nupkg
|
||||||
- uses: actions/upload-artifact@v3
|
- uses: actions/upload-artifact@v4
|
||||||
name: packages
|
name: packages
|
||||||
with:
|
with:
|
||||||
path: "*.nupkg"
|
path: "*.nupkg"
|
||||||
|
@ -174,7 +174,11 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
|
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
|
||||||
transformIgnorePatterns: ["/node_modules/(?!@fluentui/react-icons)", "/externals/"],
|
transformIgnorePatterns: [
|
||||||
|
"/node_modules/(?!@fluentui/react-icons|(.*)/dist/browser)/",
|
||||||
|
"/node_modules/plotly.js-cartesian-dist-min",
|
||||||
|
"/externals/",
|
||||||
|
],
|
||||||
|
|
||||||
// An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
|
// An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
|
||||||
// unmockedModulePathPatterns: undefined,
|
// unmockedModulePathPatterns: undefined,
|
||||||
|
@ -3117,3 +3117,7 @@ a:link {
|
|||||||
background: white;
|
background: white;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sidebarContainer {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
@ -20,6 +20,10 @@ a:focus {
|
|||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.splashLoaderContainer {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
#divExplorer {
|
#divExplorer {
|
||||||
background-color: #f5f5f5;
|
background-color: #f5f5f5;
|
||||||
}
|
}
|
||||||
@ -27,26 +31,24 @@ a:focus {
|
|||||||
.resourceTreeAndTabs {
|
.resourceTreeAndTabs {
|
||||||
border-radius: 0px;
|
border-radius: 0px;
|
||||||
box-shadow: @FabricBoxBorderShadow;
|
box-shadow: @FabricBoxBorderShadow;
|
||||||
margin: @FabricBoxMargin;
|
|
||||||
margin-top: 0px;
|
margin-top: 0px;
|
||||||
margin-bottom: 0px;
|
margin-bottom: 0px;
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tabsManagerContainer {
|
.tabsManagerContainer {
|
||||||
background-color: #ffffff
|
background-color: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-tabs-margin {
|
.nav-tabs-margin {
|
||||||
padding-top: 5px;
|
padding-top: 5px;
|
||||||
background-color: #ffffff
|
background-color: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.commandBarContainer {
|
.commandBarContainer {
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
border-radius: @FabricBoxBorderRadius @FabricBoxBorderRadius 0px 0px;
|
border-radius: @FabricBoxBorderRadius @FabricBoxBorderRadius 0px 0px;
|
||||||
box-shadow: @FabricBoxBorderShadow;
|
box-shadow: @FabricBoxBorderShadow;
|
||||||
margin: @FabricBoxMargin;
|
|
||||||
margin-top: 0px;
|
margin-top: 0px;
|
||||||
margin-bottom: 0px;
|
margin-bottom: 0px;
|
||||||
padding-top: 2px;
|
padding-top: 2px;
|
||||||
@ -65,7 +67,6 @@ a:focus {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.nav-tabs > li > .tabNavContentContainer > .tab_Content:hover {
|
.nav-tabs > li > .tabNavContentContainer > .tab_Content:hover {
|
||||||
border-bottom: 2px solid #e0e0e0;
|
border-bottom: 2px solid #e0e0e0;
|
||||||
}
|
}
|
||||||
@ -119,7 +120,6 @@ a:focus {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.resourceTree {
|
.resourceTree {
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
}
|
}
|
||||||
@ -163,18 +163,14 @@ a:focus {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.dataExplorerErrorConsoleContainer {
|
.dataExplorerErrorConsoleContainer {
|
||||||
border-radius: 0px 0px @FabricBoxBorderRadius @FabricBoxBorderRadius;
|
border-radius: 0px 0px @FabricBoxBorderRadius @FabricBoxBorderRadius;
|
||||||
box-shadow: @FabricBoxBorderShadow;
|
box-shadow: @FabricBoxBorderShadow;
|
||||||
margin: @FabricBoxMargin;
|
|
||||||
margin-top: 0px;
|
margin-top: 0px;
|
||||||
width: auto;
|
width: auto;
|
||||||
align-self: auto;
|
align-self: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.filterbtnstyle {
|
.filterbtnstyle {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
color: #000;
|
color: #000;
|
||||||
@ -200,12 +196,10 @@ a:focus {
|
|||||||
border: solid 1px #d1d1d1;
|
border: solid 1px #d1d1d1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.gridRowSelected .tabdocumentsGridElement:hover {
|
.gridRowSelected .tabdocumentsGridElement:hover {
|
||||||
background-color: @FabricAccentLight !important;
|
background-color: @FabricAccentLight !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.refreshcol {
|
.refreshcol {
|
||||||
filter: brightness(0) saturate(100%);
|
filter: brightness(0) saturate(100%);
|
||||||
}
|
}
|
||||||
|
150
package-lock.json
generated
150
package-lock.json
generated
@ -10,7 +10,7 @@
|
|||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@azure/arm-cosmosdb": "9.1.0",
|
"@azure/arm-cosmosdb": "9.1.0",
|
||||||
"@azure/cosmos": "4.0.1-beta.3",
|
"@azure/cosmos": "4.2.0-beta.1",
|
||||||
"@azure/cosmos-language-service": "0.0.5",
|
"@azure/cosmos-language-service": "0.0.5",
|
||||||
"@azure/identity": "1.5.2",
|
"@azure/identity": "1.5.2",
|
||||||
"@azure/ms-rest-nodeauth": "3.1.1",
|
"@azure/ms-rest-nodeauth": "3.1.1",
|
||||||
@ -228,18 +228,20 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@azure/abort-controller": {
|
"node_modules/@azure/abort-controller": {
|
||||||
"version": "1.1.0",
|
"version": "2.1.2",
|
||||||
"license": "MIT",
|
"resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "^2.2.0"
|
"tslib": "^2.6.2"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12.0.0"
|
"node": ">=18.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@azure/abort-controller/node_modules/tslib": {
|
"node_modules/@azure/abort-controller/node_modules/tslib": {
|
||||||
"version": "2.6.2",
|
"version": "2.8.1",
|
||||||
"license": "0BSD"
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||||
|
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
|
||||||
},
|
},
|
||||||
"node_modules/@azure/arm-cosmosdb": {
|
"node_modules/@azure/arm-cosmosdb": {
|
||||||
"version": "9.1.0",
|
"version": "9.1.0",
|
||||||
@ -251,15 +253,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@azure/core-auth": {
|
"node_modules/@azure/core-auth": {
|
||||||
"version": "1.5.0",
|
"version": "1.9.0",
|
||||||
"license": "MIT",
|
"resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.9.0.tgz",
|
||||||
|
"integrity": "sha512-FPwHpZywuyasDSLMqJ6fhbOK3TqUdviZNF8OqRGA4W5Ewib2lEEZ+pBsYcBa88B2NGO/SEnYPGhyBqNlE8ilSw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@azure/abort-controller": "^1.0.0",
|
"@azure/abort-controller": "^2.0.0",
|
||||||
"@azure/core-util": "^1.1.0",
|
"@azure/core-util": "^1.11.0",
|
||||||
"tslib": "^2.2.0"
|
"tslib": "^2.6.2"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14.0.0"
|
"node": ">=18.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@azure/core-auth/node_modules/tslib": {
|
"node_modules/@azure/core-auth/node_modules/tslib": {
|
||||||
@ -282,36 +285,61 @@
|
|||||||
"node": ">=18.0.0"
|
"node": ">=18.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@azure/core-client/node_modules/@azure/abort-controller": {
|
"node_modules/@azure/core-client/node_modules/tslib": {
|
||||||
"version": "2.1.2",
|
"version": "2.6.2",
|
||||||
"license": "MIT",
|
"license": "0BSD"
|
||||||
|
},
|
||||||
|
"node_modules/@azure/core-rest-pipeline": {
|
||||||
|
"version": "1.18.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.18.0.tgz",
|
||||||
|
"integrity": "sha512-QSoGUp4Eq/gohEFNJaUOwTN7BCc2nHTjjbm75JT0aD7W65PWM1H/tItz0GsABn22uaKyGxiMhWQLt2r+FGU89Q==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@azure/abort-controller": "^2.0.0",
|
||||||
|
"@azure/core-auth": "^1.8.0",
|
||||||
|
"@azure/core-tracing": "^1.0.1",
|
||||||
|
"@azure/core-util": "^1.11.0",
|
||||||
|
"@azure/logger": "^1.0.0",
|
||||||
|
"http-proxy-agent": "^7.0.0",
|
||||||
|
"https-proxy-agent": "^7.0.0",
|
||||||
"tslib": "^2.6.2"
|
"tslib": "^2.6.2"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0"
|
"node": ">=18.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@azure/core-client/node_modules/tslib": {
|
"node_modules/@azure/core-rest-pipeline/node_modules/agent-base": {
|
||||||
"version": "2.6.2",
|
"version": "7.1.1",
|
||||||
"license": "0BSD"
|
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz",
|
||||||
},
|
"integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==",
|
||||||
"node_modules/@azure/core-rest-pipeline": {
|
|
||||||
"version": "1.12.2",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@azure/abort-controller": "^1.0.0",
|
"debug": "^4.3.4"
|
||||||
"@azure/core-auth": "^1.4.0",
|
|
||||||
"@azure/core-tracing": "^1.0.1",
|
|
||||||
"@azure/core-util": "^1.3.0",
|
|
||||||
"@azure/logger": "^1.0.0",
|
|
||||||
"form-data": "^4.0.0",
|
|
||||||
"http-proxy-agent": "^5.0.0",
|
|
||||||
"https-proxy-agent": "^5.0.0",
|
|
||||||
"tslib": "^2.2.0"
|
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16.0.0"
|
"node": ">= 14"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@azure/core-rest-pipeline/node_modules/http-proxy-agent": {
|
||||||
|
"version": "7.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
|
||||||
|
"integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
|
||||||
|
"dependencies": {
|
||||||
|
"agent-base": "^7.1.0",
|
||||||
|
"debug": "^4.3.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 14"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@azure/core-rest-pipeline/node_modules/https-proxy-agent": {
|
||||||
|
"version": "7.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz",
|
||||||
|
"integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==",
|
||||||
|
"dependencies": {
|
||||||
|
"agent-base": "^7.0.2",
|
||||||
|
"debug": "4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@azure/core-rest-pipeline/node_modules/tslib": {
|
"node_modules/@azure/core-rest-pipeline/node_modules/tslib": {
|
||||||
@ -319,13 +347,14 @@
|
|||||||
"license": "0BSD"
|
"license": "0BSD"
|
||||||
},
|
},
|
||||||
"node_modules/@azure/core-tracing": {
|
"node_modules/@azure/core-tracing": {
|
||||||
"version": "1.0.1",
|
"version": "1.2.0",
|
||||||
"license": "MIT",
|
"resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-UKTiEJPkWcESPYJz3X5uKRYyOcJD+4nYph+KpfdPRnQJVrZfk0KJgdnaAWKfhsBBtAf/D58Az4AvCJEmWgIBAg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "^2.2.0"
|
"tslib": "^2.6.2"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12.0.0"
|
"node": ">=18.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@azure/core-tracing/node_modules/tslib": {
|
"node_modules/@azure/core-tracing/node_modules/tslib": {
|
||||||
@ -333,14 +362,15 @@
|
|||||||
"license": "0BSD"
|
"license": "0BSD"
|
||||||
},
|
},
|
||||||
"node_modules/@azure/core-util": {
|
"node_modules/@azure/core-util": {
|
||||||
"version": "1.6.1",
|
"version": "1.11.0",
|
||||||
"license": "MIT",
|
"resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.11.0.tgz",
|
||||||
|
"integrity": "sha512-DxOSLua+NdpWoSqULhjDyAZTXFdP/LKkqtYuxxz1SCN289zk3OG8UOpnCQAz/tygyACBtWp/BoO72ptK7msY8g==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@azure/abort-controller": "^1.0.0",
|
"@azure/abort-controller": "^2.0.0",
|
||||||
"tslib": "^2.2.0"
|
"tslib": "^2.6.2"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16.0.0"
|
"node": ">=18.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@azure/core-util/node_modules/tslib": {
|
"node_modules/@azure/core-util/node_modules/tslib": {
|
||||||
@ -348,22 +378,20 @@
|
|||||||
"license": "0BSD"
|
"license": "0BSD"
|
||||||
},
|
},
|
||||||
"node_modules/@azure/cosmos": {
|
"node_modules/@azure/cosmos": {
|
||||||
"version": "4.0.1-beta.3",
|
"version": "4.2.0-beta.1",
|
||||||
"license": "MIT",
|
"resolved": "https://registry.npmjs.org/@azure/cosmos/-/cosmos-4.2.0-beta.1.tgz",
|
||||||
|
"integrity": "sha512-mREONehm1DxjEKXGaNU6Wmpf9Ckb9IrhKFXhDFVs45pxmoEb3y2s/Ub0owuFmqlphpcS1zgtYQn5exn+lwnJuQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@azure/abort-controller": "^1.0.0",
|
"@azure/abort-controller": "^2.0.0",
|
||||||
"@azure/core-auth": "^1.3.0",
|
"@azure/core-auth": "^1.7.1",
|
||||||
"@azure/core-rest-pipeline": "^1.2.0",
|
"@azure/core-rest-pipeline": "^1.15.1",
|
||||||
"@azure/core-tracing": "^1.0.0",
|
"@azure/core-tracing": "^1.1.1",
|
||||||
"debug": "^4.1.1",
|
"@azure/core-util": "^1.8.1",
|
||||||
"fast-json-stable-stringify": "^2.1.0",
|
"fast-json-stable-stringify": "^2.1.0",
|
||||||
"jsbi": "^3.1.3",
|
"jsbi": "^4.3.0",
|
||||||
"node-abort-controller": "^3.0.0",
|
|
||||||
"priorityqueuejs": "^2.0.0",
|
"priorityqueuejs": "^2.0.0",
|
||||||
"semaphore": "^1.0.5",
|
"semaphore": "^1.1.0",
|
||||||
"tslib": "^2.2.0",
|
"tslib": "^2.6.2"
|
||||||
"universal-user-agent": "^6.0.0",
|
|
||||||
"uuid": "^8.3.0"
|
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0"
|
"node": ">=18.0.0"
|
||||||
@ -11708,6 +11736,7 @@
|
|||||||
},
|
},
|
||||||
"node_modules/@tootallnate/once": {
|
"node_modules/@tootallnate/once": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 10"
|
"node": ">= 10"
|
||||||
@ -19895,6 +19924,7 @@
|
|||||||
},
|
},
|
||||||
"node_modules/form-data": {
|
"node_modules/form-data": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"asynckit": "^0.4.0",
|
"asynckit": "^0.4.0",
|
||||||
@ -21095,6 +21125,7 @@
|
|||||||
},
|
},
|
||||||
"node_modules/http-proxy-agent": {
|
"node_modules/http-proxy-agent": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tootallnate/once": "2",
|
"@tootallnate/once": "2",
|
||||||
@ -27067,8 +27098,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/jsbi": {
|
"node_modules/jsbi": {
|
||||||
"version": "3.2.5",
|
"version": "4.3.0",
|
||||||
"license": "Apache-2.0"
|
"resolved": "https://registry.npmjs.org/jsbi/-/jsbi-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-SnZNcinB4RIcnEyZqFPdGPVgrg2AcnykiBy0sHVJQKHYeaLUvi3Exj+iaPpLnFVkDPZIV4U0yvgC9/R4uEAZ9g=="
|
||||||
},
|
},
|
||||||
"node_modules/jsbn": {
|
"node_modules/jsbn": {
|
||||||
"version": "0.1.1",
|
"version": "0.1.1",
|
||||||
@ -29737,7 +29769,9 @@
|
|||||||
},
|
},
|
||||||
"node_modules/node-abort-controller": {
|
"node_modules/node-abort-controller": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"license": "MIT"
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/node-addon-api": {
|
"node_modules/node-addon-api": {
|
||||||
"version": "4.3.0",
|
"version": "4.3.0",
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@azure/arm-cosmosdb": "9.1.0",
|
"@azure/arm-cosmosdb": "9.1.0",
|
||||||
"@azure/cosmos": "4.0.1-beta.3",
|
"@azure/cosmos": "4.2.0-beta.1",
|
||||||
"@azure/cosmos-language-service": "0.0.5",
|
"@azure/cosmos-language-service": "0.0.5",
|
||||||
"@azure/identity": "1.5.2",
|
"@azure/identity": "1.5.2",
|
||||||
"@azure/ms-rest-nodeauth": "3.1.1",
|
"@azure/ms-rest-nodeauth": "3.1.1",
|
||||||
|
@ -89,6 +89,7 @@ export class CapabilityNames {
|
|||||||
public static readonly EnableMongo: string = "EnableMongo";
|
public static readonly EnableMongo: string = "EnableMongo";
|
||||||
public static readonly EnableServerless: string = "EnableServerless";
|
public static readonly EnableServerless: string = "EnableServerless";
|
||||||
public static readonly EnableNoSQLVectorSearch: string = "EnableNoSQLVectorSearch";
|
public static readonly EnableNoSQLVectorSearch: string = "EnableNoSQLVectorSearch";
|
||||||
|
public static readonly EnableNoSQLFullTextSearch: string = "EnableNoSQLFullTextSearch";
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum CapacityMode {
|
export enum CapacityMode {
|
||||||
@ -158,7 +159,7 @@ export class MongoProxyEndpoints {
|
|||||||
export class MongoProxyApi {
|
export class MongoProxyApi {
|
||||||
public static readonly ResourceList: string = "ResourceList";
|
public static readonly ResourceList: string = "ResourceList";
|
||||||
public static readonly QueryDocuments: string = "QueryDocuments";
|
public static readonly QueryDocuments: string = "QueryDocuments";
|
||||||
public static readonly CreateDocument: string = "CreateDocumen";
|
public static readonly CreateDocument: string = "CreateDocument";
|
||||||
public static readonly ReadDocument: string = "ReadDocument";
|
public static readonly ReadDocument: string = "ReadDocument";
|
||||||
public static readonly UpdateDocument: string = "UpdateDocument";
|
public static readonly UpdateDocument: string = "UpdateDocument";
|
||||||
public static readonly DeleteDocument: string = "DeleteDocument";
|
public static readonly DeleteDocument: string = "DeleteDocument";
|
||||||
|
@ -73,6 +73,7 @@ describe("MongoProxyClient", () => {
|
|||||||
});
|
});
|
||||||
updateConfigContext({
|
updateConfigContext({
|
||||||
MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Prod,
|
MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Prod,
|
||||||
|
globallyEnabledMongoAPIs: [],
|
||||||
});
|
});
|
||||||
window.fetch = jest.fn().mockImplementation(fetchMock);
|
window.fetch = jest.fn().mockImplementation(fetchMock);
|
||||||
});
|
});
|
||||||
@ -89,7 +90,10 @@ describe("MongoProxyClient", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("builds the correct proxy URL in development", () => {
|
it("builds the correct proxy URL in development", () => {
|
||||||
updateConfigContext({ MONGO_PROXY_ENDPOINT: "https://localhost:1234" });
|
updateConfigContext({
|
||||||
|
MONGO_PROXY_ENDPOINT: "https://localhost:1234",
|
||||||
|
globallyEnabledMongoAPIs: [],
|
||||||
|
});
|
||||||
queryDocuments(databaseId, collection, true, "{}");
|
queryDocuments(databaseId, collection, true, "{}");
|
||||||
expect(window.fetch).toHaveBeenCalledWith(
|
expect(window.fetch).toHaveBeenCalledWith(
|
||||||
`${configContext.MONGO_PROXY_ENDPOINT}/api/mongo/explorer/resourcelist`,
|
`${configContext.MONGO_PROXY_ENDPOINT}/api/mongo/explorer/resourcelist`,
|
||||||
@ -105,6 +109,7 @@ describe("MongoProxyClient", () => {
|
|||||||
});
|
});
|
||||||
updateConfigContext({
|
updateConfigContext({
|
||||||
MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Prod,
|
MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Prod,
|
||||||
|
globallyEnabledMongoAPIs: [],
|
||||||
});
|
});
|
||||||
window.fetch = jest.fn().mockImplementation(fetchMock);
|
window.fetch = jest.fn().mockImplementation(fetchMock);
|
||||||
});
|
});
|
||||||
@ -121,7 +126,10 @@ describe("MongoProxyClient", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("builds the correct proxy URL in development", () => {
|
it("builds the correct proxy URL in development", () => {
|
||||||
updateConfigContext({ MONGO_PROXY_ENDPOINT: "https://localhost:1234" });
|
updateConfigContext({
|
||||||
|
MONGO_PROXY_ENDPOINT: "https://localhost:1234",
|
||||||
|
globallyEnabledMongoAPIs: [],
|
||||||
|
});
|
||||||
readDocument(databaseId, collection, documentId);
|
readDocument(databaseId, collection, documentId);
|
||||||
expect(window.fetch).toHaveBeenCalledWith(
|
expect(window.fetch).toHaveBeenCalledWith(
|
||||||
`${configContext.MONGO_PROXY_ENDPOINT}/api/mongo/explorer`,
|
`${configContext.MONGO_PROXY_ENDPOINT}/api/mongo/explorer`,
|
||||||
@ -137,6 +145,7 @@ describe("MongoProxyClient", () => {
|
|||||||
});
|
});
|
||||||
updateConfigContext({
|
updateConfigContext({
|
||||||
MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Prod,
|
MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Prod,
|
||||||
|
globallyEnabledMongoAPIs: [],
|
||||||
});
|
});
|
||||||
window.fetch = jest.fn().mockImplementation(fetchMock);
|
window.fetch = jest.fn().mockImplementation(fetchMock);
|
||||||
});
|
});
|
||||||
@ -153,7 +162,10 @@ describe("MongoProxyClient", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("builds the correct proxy URL in development", () => {
|
it("builds the correct proxy URL in development", () => {
|
||||||
updateConfigContext({ MONGO_PROXY_ENDPOINT: "https://localhost:1234" });
|
updateConfigContext({
|
||||||
|
MONGO_PROXY_ENDPOINT: "https://localhost:1234",
|
||||||
|
globallyEnabledMongoAPIs: [],
|
||||||
|
});
|
||||||
readDocument(databaseId, collection, documentId);
|
readDocument(databaseId, collection, documentId);
|
||||||
expect(window.fetch).toHaveBeenCalledWith(
|
expect(window.fetch).toHaveBeenCalledWith(
|
||||||
`${configContext.MONGO_PROXY_ENDPOINT}/api/mongo/explorer`,
|
`${configContext.MONGO_PROXY_ENDPOINT}/api/mongo/explorer`,
|
||||||
@ -169,6 +181,7 @@ describe("MongoProxyClient", () => {
|
|||||||
});
|
});
|
||||||
updateConfigContext({
|
updateConfigContext({
|
||||||
MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Prod,
|
MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Prod,
|
||||||
|
globallyEnabledMongoAPIs: [],
|
||||||
});
|
});
|
||||||
window.fetch = jest.fn().mockImplementation(fetchMock);
|
window.fetch = jest.fn().mockImplementation(fetchMock);
|
||||||
});
|
});
|
||||||
@ -185,7 +198,10 @@ describe("MongoProxyClient", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("builds the correct proxy URL in development", () => {
|
it("builds the correct proxy URL in development", () => {
|
||||||
updateConfigContext({ MONGO_BACKEND_ENDPOINT: "https://localhost:1234" });
|
updateConfigContext({
|
||||||
|
MONGO_BACKEND_ENDPOINT: "https://localhost:1234",
|
||||||
|
globallyEnabledMongoAPIs: [],
|
||||||
|
});
|
||||||
updateDocument(databaseId, collection, documentId, "{}");
|
updateDocument(databaseId, collection, documentId, "{}");
|
||||||
expect(window.fetch).toHaveBeenCalledWith(
|
expect(window.fetch).toHaveBeenCalledWith(
|
||||||
`${configContext.MONGO_PROXY_ENDPOINT}/api/mongo/explorer`,
|
`${configContext.MONGO_PROXY_ENDPOINT}/api/mongo/explorer`,
|
||||||
@ -201,6 +217,7 @@ describe("MongoProxyClient", () => {
|
|||||||
});
|
});
|
||||||
updateConfigContext({
|
updateConfigContext({
|
||||||
MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Prod,
|
MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Prod,
|
||||||
|
globallyEnabledMongoAPIs: [],
|
||||||
});
|
});
|
||||||
window.fetch = jest.fn().mockImplementation(fetchMock);
|
window.fetch = jest.fn().mockImplementation(fetchMock);
|
||||||
});
|
});
|
||||||
@ -217,7 +234,10 @@ describe("MongoProxyClient", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("builds the correct proxy URL in development", () => {
|
it("builds the correct proxy URL in development", () => {
|
||||||
updateConfigContext({ MONGO_PROXY_ENDPOINT: "https://localhost:1234" });
|
updateConfigContext({
|
||||||
|
MONGO_PROXY_ENDPOINT: "https://localhost:1234",
|
||||||
|
globallyEnabledMongoAPIs: [],
|
||||||
|
});
|
||||||
deleteDocument(databaseId, collection, documentId);
|
deleteDocument(databaseId, collection, documentId);
|
||||||
expect(window.fetch).toHaveBeenCalledWith(
|
expect(window.fetch).toHaveBeenCalledWith(
|
||||||
`${configContext.MONGO_PROXY_ENDPOINT}/api/mongo/explorer`,
|
`${configContext.MONGO_PROXY_ENDPOINT}/api/mongo/explorer`,
|
||||||
@ -233,6 +253,7 @@ describe("MongoProxyClient", () => {
|
|||||||
});
|
});
|
||||||
updateConfigContext({
|
updateConfigContext({
|
||||||
MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Prod,
|
MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Prod,
|
||||||
|
globallyEnabledMongoAPIs: [],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -260,6 +281,7 @@ describe("MongoProxyClient", () => {
|
|||||||
resetConfigContext();
|
resetConfigContext();
|
||||||
updateConfigContext({
|
updateConfigContext({
|
||||||
MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Prod,
|
MONGO_PROXY_ENDPOINT: MongoProxyEndpoints.Prod,
|
||||||
|
globallyEnabledMongoAPIs: [],
|
||||||
});
|
});
|
||||||
const params = new URLSearchParams({
|
const params = new URLSearchParams({
|
||||||
"feature.mongoProxyEndpoint": MongoProxyEndpoints.Prod,
|
"feature.mongoProxyEndpoint": MongoProxyEndpoints.Prod,
|
||||||
|
@ -689,13 +689,13 @@ export function createMongoCollectionWithProxy_ToBeDeprecated(
|
|||||||
}
|
}
|
||||||
export function getFeatureEndpointOrDefault(feature: string): string {
|
export function getFeatureEndpointOrDefault(feature: string): string {
|
||||||
let endpoint;
|
let endpoint;
|
||||||
|
if (useMongoProxyEndpoint(feature)) {
|
||||||
|
endpoint = configContext.MONGO_PROXY_ENDPOINT;
|
||||||
|
} else {
|
||||||
const allowedMongoProxyEndpoints = configContext.allowedMongoProxyEndpoints || [
|
const allowedMongoProxyEndpoints = configContext.allowedMongoProxyEndpoints || [
|
||||||
...defaultAllowedMongoProxyEndpoints,
|
...defaultAllowedMongoProxyEndpoints,
|
||||||
...allowedMongoProxyEndpoints_ToBeDeprecated,
|
...allowedMongoProxyEndpoints_ToBeDeprecated,
|
||||||
];
|
];
|
||||||
if (useMongoProxyEndpoint(feature)) {
|
|
||||||
endpoint = configContext.MONGO_PROXY_ENDPOINT;
|
|
||||||
} else {
|
|
||||||
endpoint =
|
endpoint =
|
||||||
hasFlag(userContext.features.mongoProxyAPIs, feature) &&
|
hasFlag(userContext.features.mongoProxyAPIs, feature) &&
|
||||||
validateEndpoint(userContext.features.mongoProxyEndpoint, allowedMongoProxyEndpoints)
|
validateEndpoint(userContext.features.mongoProxyEndpoint, allowedMongoProxyEndpoints)
|
||||||
@ -790,6 +790,10 @@ export function useMongoProxyEndpoint(mongoProxyApi: string): boolean {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (configContext.globallyEnabledMongoAPIs.includes(mongoProxyApi)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return mongoProxyEnvironmentMap[mongoProxyApi].includes(configContext.MONGO_PROXY_ENDPOINT);
|
return mongoProxyEnvironmentMap[mongoProxyApi].includes(configContext.MONGO_PROXY_ENDPOINT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ describe("QueryError.tryParse", () => {
|
|||||||
code: "BadRequest",
|
code: "BadRequest",
|
||||||
message: "Your query is bad, and you should feel bad",
|
message: "Your query is bad, and you should feel bad",
|
||||||
};
|
};
|
||||||
const message = JSON.stringify(innerError);
|
const message = `Message: ${JSON.stringify(innerError)}\r\nActivity ID: 42`;
|
||||||
const outerError = {
|
const outerError = {
|
||||||
code: "BadRequest",
|
code: "BadRequest",
|
||||||
message,
|
message,
|
||||||
@ -48,7 +48,7 @@ describe("QueryError.tryParse", () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Imitate the value coming from the backend, which has the syntax errors serialized as JSON in the message.
|
// Imitate the value coming from the backend, which has the syntax errors serialized as JSON in the message, along with a prefix and activity id.
|
||||||
it("handles single-nested error", () => {
|
it("handles single-nested error", () => {
|
||||||
const errors = [
|
const errors = [
|
||||||
{
|
{
|
||||||
@ -69,7 +69,7 @@ describe("QueryError.tryParse", () => {
|
|||||||
message: "Your query is bad, and you should feel bad",
|
message: "Your query is bad, and you should feel bad",
|
||||||
errors,
|
errors,
|
||||||
};
|
};
|
||||||
const message = JSON.stringify(innerError);
|
const message = `Message: ${JSON.stringify(innerError)}\r\nActivity ID: 42`;
|
||||||
const outerError = {
|
const outerError = {
|
||||||
code: "BadRequest",
|
code: "BadRequest",
|
||||||
message,
|
message,
|
||||||
@ -91,4 +91,23 @@ describe("QueryError.tryParse", () => {
|
|||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Imitate another value we've gotten from the backend, which has a doubly-nested JSON payload.
|
||||||
|
it("handles double-nested error", () => {
|
||||||
|
const outerError = {
|
||||||
|
code: "BadRequest",
|
||||||
|
message:
|
||||||
|
'{"code":"BadRequest","message":"{\\"errors\\":[{\\"severity\\":\\"Error\\",\\"location\\":{\\"start\\":7,\\"end\\":18},\\"code\\":\\"SC2005\\",\\"message\\":\\"\'nonexistent\' is not a recognized built-in function name.\\"}]}\\r\\nActivityId: aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa, Windows/10.0.20348 cosmos-netstandard-sdk/3.18.0"}',
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = QueryError.tryParse(outerError, testErrorLocationResolver);
|
||||||
|
expect(result).toEqual([
|
||||||
|
new QueryError(
|
||||||
|
"'nonexistent' is not a recognized built-in function name.",
|
||||||
|
QueryErrorSeverity.Error,
|
||||||
|
"SC2005",
|
||||||
|
new QueryErrorLocation({ offset: 7, lineNumber: 7, column: 7 }, { offset: 18, lineNumber: 18, column: 18 }),
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -214,16 +214,28 @@ export default class QueryError {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assign to a new variable because of a TypeScript flow typing quirk, see below.
|
// Some newer backends produce a message that contains a doubly-nested JSON payload.
|
||||||
if (message.startsWith("Message: ")) {
|
// In this case, the message we get is a fully-complete JSON object we can parse.
|
||||||
// Reassigning this to 'error' restores the original type of 'error', which is 'unknown'.
|
// So let's try that first
|
||||||
// So we use a separate variable to avoid this.
|
if (message.startsWith("{") && message.endsWith("}")) {
|
||||||
message = message.substring("Message: ".length);
|
let outer: unknown = undefined;
|
||||||
|
try {
|
||||||
|
outer = JSON.parse(message);
|
||||||
|
if (typeof outer === "object" && "message" in outer && typeof outer.message === "string") {
|
||||||
|
message = outer.message;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Just continue if the parsing fails. We'll use the fallback logic below.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const lines = message.split("\n");
|
const lines = message.split("\n");
|
||||||
message = lines[0].trim();
|
message = lines[0].trim();
|
||||||
|
|
||||||
|
if (message.startsWith("Message: ")) {
|
||||||
|
message = message.substring("Message: ".length);
|
||||||
|
}
|
||||||
|
|
||||||
let parsed: unknown;
|
let parsed: unknown;
|
||||||
try {
|
try {
|
||||||
parsed = JSON.parse(message);
|
parsed = JSON.parse(message);
|
||||||
|
@ -99,6 +99,9 @@ const createSqlContainer = async (params: DataModels.CreateCollectionParams): Pr
|
|||||||
if (params.vectorEmbeddingPolicy) {
|
if (params.vectorEmbeddingPolicy) {
|
||||||
resource.vectorEmbeddingPolicy = params.vectorEmbeddingPolicy;
|
resource.vectorEmbeddingPolicy = params.vectorEmbeddingPolicy;
|
||||||
}
|
}
|
||||||
|
if (params.fullTextPolicy) {
|
||||||
|
resource.fullTextPolicy = params.fullTextPolicy;
|
||||||
|
}
|
||||||
|
|
||||||
const rpPayload: ARMTypes.SqlDatabaseCreateUpdateParameters = {
|
const rpPayload: ARMTypes.SqlDatabaseCreateUpdateParameters = {
|
||||||
properties: {
|
properties: {
|
||||||
@ -270,6 +273,7 @@ const createCollectionWithSDK = async (params: DataModels.CreateCollectionParams
|
|||||||
uniqueKeyPolicy: params.uniqueKeyPolicy || undefined,
|
uniqueKeyPolicy: params.uniqueKeyPolicy || undefined,
|
||||||
analyticalStorageTtl: params.analyticalStorageTtl,
|
analyticalStorageTtl: params.analyticalStorageTtl,
|
||||||
vectorEmbeddingPolicy: params.vectorEmbeddingPolicy,
|
vectorEmbeddingPolicy: params.vectorEmbeddingPolicy,
|
||||||
|
fullTextPolicy: params.fullTextPolicy,
|
||||||
} as ContainerRequest; // TODO: remove cast when https://github.com/Azure/azure-cosmos-js/issues/423 is fixed
|
} as ContainerRequest; // TODO: remove cast when https://github.com/Azure/azure-cosmos-js/issues/423 is fixed
|
||||||
const collectionOptions: RequestOptions = {};
|
const collectionOptions: RequestOptions = {};
|
||||||
const createDatabaseBody: DatabaseRequest = { id: params.databaseId };
|
const createDatabaseBody: DatabaseRequest = { id: params.databaseId };
|
||||||
|
@ -67,6 +67,8 @@ export interface ConfigContext {
|
|||||||
hostedExplorerURL: string;
|
hostedExplorerURL: string;
|
||||||
armAPIVersion?: string;
|
armAPIVersion?: string;
|
||||||
msalRedirectURI?: string;
|
msalRedirectURI?: string;
|
||||||
|
globallyEnabledCassandraAPIs?: string[];
|
||||||
|
globallyEnabledMongoAPIs?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default configuration
|
// Default configuration
|
||||||
@ -114,6 +116,8 @@ let configContext: Readonly<ConfigContext> = {
|
|||||||
NEW_CASSANDRA_APIS: ["postQuery", "createOrDelete", "getKeys", "getSchema"],
|
NEW_CASSANDRA_APIS: ["postQuery", "createOrDelete", "getKeys", "getSchema"],
|
||||||
isTerminalEnabled: false,
|
isTerminalEnabled: false,
|
||||||
isPhoenixEnabled: false,
|
isPhoenixEnabled: false,
|
||||||
|
globallyEnabledCassandraAPIs: [],
|
||||||
|
globallyEnabledMongoAPIs: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
export function resetConfigContext(): void {
|
export function resetConfigContext(): void {
|
||||||
|
@ -159,6 +159,7 @@ export interface Collection extends Resource {
|
|||||||
analyticalStorageTtl?: number;
|
analyticalStorageTtl?: number;
|
||||||
geospatialConfig?: GeospatialConfig;
|
geospatialConfig?: GeospatialConfig;
|
||||||
vectorEmbeddingPolicy?: VectorEmbeddingPolicy;
|
vectorEmbeddingPolicy?: VectorEmbeddingPolicy;
|
||||||
|
fullTextPolicy?: FullTextPolicy;
|
||||||
schema?: ISchema;
|
schema?: ISchema;
|
||||||
requestSchema?: () => void;
|
requestSchema?: () => void;
|
||||||
computedProperties?: ComputedProperties;
|
computedProperties?: ComputedProperties;
|
||||||
@ -199,11 +200,19 @@ export interface IndexingPolicy {
|
|||||||
compositeIndexes?: any[];
|
compositeIndexes?: any[];
|
||||||
spatialIndexes?: any[];
|
spatialIndexes?: any[];
|
||||||
vectorIndexes?: VectorIndex[];
|
vectorIndexes?: VectorIndex[];
|
||||||
|
fullTextIndexes?: FullTextIndex[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface VectorIndex {
|
export interface VectorIndex {
|
||||||
path: string;
|
path: string;
|
||||||
type: "flat" | "diskANN" | "quantizedFlat";
|
type: "flat" | "diskANN" | "quantizedFlat";
|
||||||
|
diskANNShardKey?: string;
|
||||||
|
indexingSearchListSize?: number;
|
||||||
|
quantizationByteSize?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FullTextIndex {
|
||||||
|
path: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ComputedProperty {
|
export interface ComputedProperty {
|
||||||
@ -342,6 +351,7 @@ export interface CreateCollectionParams {
|
|||||||
uniqueKeyPolicy?: UniqueKeyPolicy;
|
uniqueKeyPolicy?: UniqueKeyPolicy;
|
||||||
createMongoWildcardIndex?: boolean;
|
createMongoWildcardIndex?: boolean;
|
||||||
vectorEmbeddingPolicy?: VectorEmbeddingPolicy;
|
vectorEmbeddingPolicy?: VectorEmbeddingPolicy;
|
||||||
|
fullTextPolicy?: FullTextPolicy;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface VectorEmbeddingPolicy {
|
export interface VectorEmbeddingPolicy {
|
||||||
@ -355,6 +365,16 @@ export interface VectorEmbedding {
|
|||||||
path: string;
|
path: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface FullTextPolicy {
|
||||||
|
defaultLanguage: string;
|
||||||
|
fullTextPaths: FullTextPath[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FullTextPath {
|
||||||
|
path: string;
|
||||||
|
language: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ReadDatabaseOfferParams {
|
export interface ReadDatabaseOfferParams {
|
||||||
databaseId: string;
|
databaseId: string;
|
||||||
databaseResourceId?: string;
|
databaseResourceId?: string;
|
||||||
|
@ -126,6 +126,8 @@ export interface Collection extends CollectionBase {
|
|||||||
analyticalStorageTtl: ko.Observable<number>;
|
analyticalStorageTtl: ko.Observable<number>;
|
||||||
schema?: DataModels.ISchema;
|
schema?: DataModels.ISchema;
|
||||||
requestSchema?: () => void;
|
requestSchema?: () => void;
|
||||||
|
vectorEmbeddingPolicy: ko.Observable<DataModels.VectorEmbeddingPolicy>;
|
||||||
|
fullTextPolicy: ko.Observable<DataModels.FullTextPolicy>;
|
||||||
indexingPolicy: ko.Observable<DataModels.IndexingPolicy>;
|
indexingPolicy: ko.Observable<DataModels.IndexingPolicy>;
|
||||||
uniqueKeyPolicy: DataModels.UniqueKeyPolicy;
|
uniqueKeyPolicy: DataModels.UniqueKeyPolicy;
|
||||||
usageSizeInKB: ko.Observable<number>;
|
usageSizeInKB: ko.Observable<number>;
|
||||||
@ -381,8 +383,9 @@ export enum TerminalKind {
|
|||||||
export interface DataExplorerInputsFrame {
|
export interface DataExplorerInputsFrame {
|
||||||
databaseAccount: any;
|
databaseAccount: any;
|
||||||
subscriptionId?: string;
|
subscriptionId?: string;
|
||||||
tenantId?: string;
|
|
||||||
resourceGroup?: string;
|
resourceGroup?: string;
|
||||||
|
tenantId?: string;
|
||||||
|
userName?: string;
|
||||||
masterKey?: string;
|
masterKey?: string;
|
||||||
hasWriteAccess?: boolean;
|
hasWriteAccess?: boolean;
|
||||||
authorizationToken?: string;
|
authorizationToken?: string;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { DirectionalHint, Icon, Label, Stack, TooltipHost } from "@fluentui/react";
|
import { DirectionalHint, Icon, IconButton, Label, Stack, TooltipHost } from "@fluentui/react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { NormalizedEventKey } from "../../../Common/Constants";
|
import { NormalizedEventKey } from "../../../Common/Constants";
|
||||||
import { accordionStackTokens } from "../Settings/SettingsRenderUtils";
|
import { accordionStackTokens } from "../Settings/SettingsRenderUtils";
|
||||||
@ -9,6 +9,9 @@ export interface CollapsibleSectionProps {
|
|||||||
onExpand?: () => void;
|
onExpand?: () => void;
|
||||||
children: JSX.Element;
|
children: JSX.Element;
|
||||||
tooltipContent?: string | JSX.Element | JSX.Element[];
|
tooltipContent?: string | JSX.Element | JSX.Element[];
|
||||||
|
showDelete?: boolean;
|
||||||
|
onDelete?: () => void;
|
||||||
|
disabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CollapsibleSectionState {
|
export interface CollapsibleSectionState {
|
||||||
@ -69,6 +72,20 @@ export class CollapsibleSectionComponent extends React.Component<CollapsibleSect
|
|||||||
<Icon iconName="Info" className="panelInfoIcon" tabIndex={0} />
|
<Icon iconName="Info" className="panelInfoIcon" tabIndex={0} />
|
||||||
</TooltipHost>
|
</TooltipHost>
|
||||||
)}
|
)}
|
||||||
|
{this.props.showDelete && (
|
||||||
|
<Stack.Item style={{ marginLeft: "auto" }}>
|
||||||
|
<IconButton
|
||||||
|
disabled={this.props.disabled}
|
||||||
|
id={`delete-${this.props.title.split(" ").join("-")}`}
|
||||||
|
iconProps={{ iconName: "Delete" }}
|
||||||
|
style={{ height: 27, marginRight: "20px" }}
|
||||||
|
onClick={(event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
this.props.onDelete();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Stack.Item>
|
||||||
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
{this.state.isExpanded && this.props.children}
|
{this.state.isExpanded && this.props.children}
|
||||||
</>
|
</>
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
import "@testing-library/jest-dom";
|
||||||
|
|
||||||
|
describe("AddFullTextPolicyForm", () => {
|
||||||
|
//CTODO: add tests
|
||||||
|
it.skip("should render correctly", () => {});
|
||||||
|
});
|
@ -0,0 +1,239 @@
|
|||||||
|
import {
|
||||||
|
DefaultButton,
|
||||||
|
Dropdown,
|
||||||
|
IDropdownOption,
|
||||||
|
IStyleFunctionOrObject,
|
||||||
|
ITextFieldStyleProps,
|
||||||
|
ITextFieldStyles,
|
||||||
|
Label,
|
||||||
|
Stack,
|
||||||
|
TextField,
|
||||||
|
} from "@fluentui/react";
|
||||||
|
import { FullTextIndex, FullTextPath, FullTextPolicy } from "Contracts/DataModels";
|
||||||
|
import { CollapsibleSectionComponent } from "Explorer/Controls/CollapsiblePanel/CollapsibleSectionComponent";
|
||||||
|
import * as React from "react";
|
||||||
|
|
||||||
|
export interface FullTextPoliciesComponentProps {
|
||||||
|
fullTextPolicy: FullTextPolicy;
|
||||||
|
onFullTextPathChange: (
|
||||||
|
fullTextPolicy: FullTextPolicy,
|
||||||
|
fullTextIndexes: FullTextIndex[],
|
||||||
|
validationPassed: boolean,
|
||||||
|
) => void;
|
||||||
|
discardChanges?: boolean;
|
||||||
|
onChangesDiscarded?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FullTextPolicyData {
|
||||||
|
path: string;
|
||||||
|
language: string;
|
||||||
|
pathError: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const labelStyles = {
|
||||||
|
root: {
|
||||||
|
fontSize: 12,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const textFieldStyles: IStyleFunctionOrObject<ITextFieldStyleProps, ITextFieldStyles> = {
|
||||||
|
fieldGroup: {
|
||||||
|
height: 27,
|
||||||
|
},
|
||||||
|
field: {
|
||||||
|
fontSize: 12,
|
||||||
|
padding: "0 8px",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const dropdownStyles = {
|
||||||
|
title: {
|
||||||
|
height: 27,
|
||||||
|
lineHeight: "24px",
|
||||||
|
fontSize: 12,
|
||||||
|
},
|
||||||
|
dropdown: {
|
||||||
|
height: 27,
|
||||||
|
lineHeight: "24px",
|
||||||
|
},
|
||||||
|
dropdownItem: {
|
||||||
|
fontSize: 12,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FullTextPoliciesComponent: React.FunctionComponent<FullTextPoliciesComponentProps> = ({
|
||||||
|
fullTextPolicy,
|
||||||
|
onFullTextPathChange,
|
||||||
|
discardChanges,
|
||||||
|
onChangesDiscarded,
|
||||||
|
}): JSX.Element => {
|
||||||
|
const getFullTextPathError = (path: string, index?: number): string => {
|
||||||
|
let error = "";
|
||||||
|
if (!path) {
|
||||||
|
error = "Full text path should not be empty";
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
index >= 0 &&
|
||||||
|
fullTextPathData?.find(
|
||||||
|
(fullTextPath: FullTextPolicyData, dataIndex: number) => dataIndex !== index && fullTextPath.path === path,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
error = "Full text path is already defined";
|
||||||
|
}
|
||||||
|
return error;
|
||||||
|
};
|
||||||
|
|
||||||
|
const initializeData = (fullTextPolicy: FullTextPolicy): FullTextPolicyData[] => {
|
||||||
|
if (!fullTextPolicy) {
|
||||||
|
fullTextPolicy = { defaultLanguage: getFullTextLanguageOptions()[0].key as never, fullTextPaths: [] };
|
||||||
|
}
|
||||||
|
return fullTextPolicy.fullTextPaths.map((fullTextPath: FullTextPath) => ({
|
||||||
|
...fullTextPath,
|
||||||
|
pathError: getFullTextPathError(fullTextPath.path),
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const [fullTextPathData, setFullTextPathData] = React.useState<FullTextPolicyData[]>(initializeData(fullTextPolicy));
|
||||||
|
const [defaultLanguage, setDefaultLanguage] = React.useState<string>(
|
||||||
|
fullTextPolicy ? fullTextPolicy.defaultLanguage : (getFullTextLanguageOptions()[0].key as never),
|
||||||
|
);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
propagateData();
|
||||||
|
}, [fullTextPathData, defaultLanguage]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (discardChanges) {
|
||||||
|
setFullTextPathData(initializeData(fullTextPolicy));
|
||||||
|
setDefaultLanguage(fullTextPolicy.defaultLanguage);
|
||||||
|
onChangesDiscarded();
|
||||||
|
}
|
||||||
|
}, [discardChanges]);
|
||||||
|
|
||||||
|
const propagateData = () => {
|
||||||
|
const newFullTextPolicy: FullTextPolicy = {
|
||||||
|
defaultLanguage: defaultLanguage,
|
||||||
|
fullTextPaths: fullTextPathData.map((policy: FullTextPolicyData) => ({
|
||||||
|
path: policy.path,
|
||||||
|
language: policy.language,
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
const fullTextIndexes: FullTextIndex[] = fullTextPathData.map((policy) => ({
|
||||||
|
path: policy.path,
|
||||||
|
}));
|
||||||
|
const validationPassed = fullTextPathData.every((policy: FullTextPolicyData) => policy.pathError === "");
|
||||||
|
onFullTextPathChange(newFullTextPolicy, fullTextIndexes, validationPassed);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onFullTextPathValueChange = (index: number, event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const value = event.target.value.trim();
|
||||||
|
const fullTextPaths = [...fullTextPathData];
|
||||||
|
if (!fullTextPaths[index]?.path && !value.startsWith("/")) {
|
||||||
|
fullTextPaths[index].path = "/" + value;
|
||||||
|
} else {
|
||||||
|
fullTextPaths[index].path = value;
|
||||||
|
}
|
||||||
|
fullTextPaths[index].pathError = getFullTextPathError(value, index);
|
||||||
|
setFullTextPathData(fullTextPaths);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onFullTextPathPolicyChange = (index: number, option: IDropdownOption): void => {
|
||||||
|
const policies = [...fullTextPathData];
|
||||||
|
policies[index].language = option.key as never;
|
||||||
|
setFullTextPathData(policies);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onAdd = () => {
|
||||||
|
setFullTextPathData([
|
||||||
|
...fullTextPathData,
|
||||||
|
{
|
||||||
|
path: "",
|
||||||
|
language: defaultLanguage,
|
||||||
|
pathError: getFullTextPathError(""),
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDelete = (index: number) => {
|
||||||
|
const policies = fullTextPathData.filter((_uniqueKey, j) => index !== j);
|
||||||
|
setFullTextPathData(policies);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack tokens={{ childrenGap: 4 }}>
|
||||||
|
<Stack style={{ marginBottom: 10 }}>
|
||||||
|
<Label styles={labelStyles}>Default language</Label>
|
||||||
|
<Dropdown
|
||||||
|
required={true}
|
||||||
|
styles={dropdownStyles}
|
||||||
|
options={getFullTextLanguageOptions()}
|
||||||
|
selectedKey={defaultLanguage}
|
||||||
|
onChange={(_event: React.FormEvent<HTMLDivElement>, option: IDropdownOption) =>
|
||||||
|
setDefaultLanguage(option.key as never)
|
||||||
|
}
|
||||||
|
></Dropdown>
|
||||||
|
</Stack>
|
||||||
|
{fullTextPathData &&
|
||||||
|
fullTextPathData.length > 0 &&
|
||||||
|
fullTextPathData.map((fullTextPolicy: FullTextPolicyData, index: number) => (
|
||||||
|
<CollapsibleSectionComponent
|
||||||
|
key={index}
|
||||||
|
isExpandedByDefault={true}
|
||||||
|
title={`Full text path ${index + 1}`}
|
||||||
|
showDelete={true}
|
||||||
|
onDelete={() => onDelete(index)}
|
||||||
|
>
|
||||||
|
<Stack horizontal tokens={{ childrenGap: 4 }}>
|
||||||
|
<Stack
|
||||||
|
styles={{
|
||||||
|
root: {
|
||||||
|
margin: "0 0 6px 20px !important",
|
||||||
|
paddingLeft: 20,
|
||||||
|
width: "80%",
|
||||||
|
borderLeft: "1px solid",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Stack>
|
||||||
|
<Label styles={labelStyles}>Path</Label>
|
||||||
|
<TextField
|
||||||
|
id={`full-text-policy-path-${index + 1}`}
|
||||||
|
required={true}
|
||||||
|
placeholder="/fullTextPath1"
|
||||||
|
styles={textFieldStyles}
|
||||||
|
onChange={(event: React.ChangeEvent<HTMLInputElement>) => onFullTextPathValueChange(index, event)}
|
||||||
|
value={fullTextPolicy.path || ""}
|
||||||
|
errorMessage={fullTextPolicy.pathError}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
<Stack>
|
||||||
|
<Label styles={labelStyles}>Language</Label>
|
||||||
|
<Dropdown
|
||||||
|
required={true}
|
||||||
|
styles={dropdownStyles}
|
||||||
|
options={getFullTextLanguageOptions()}
|
||||||
|
selectedKey={fullTextPolicy.language}
|
||||||
|
onChange={(_event: React.FormEvent<HTMLDivElement>, option: IDropdownOption) =>
|
||||||
|
onFullTextPathPolicyChange(index, option)
|
||||||
|
}
|
||||||
|
></Dropdown>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
</CollapsibleSectionComponent>
|
||||||
|
))}
|
||||||
|
<DefaultButton id={`add-vector-policy`} styles={{ root: { maxWidth: 170, fontSize: 12 } }} onClick={onAdd}>
|
||||||
|
Add full text path
|
||||||
|
</DefaultButton>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getFullTextLanguageOptions = (): IDropdownOption[] => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
key: "en-US",
|
||||||
|
text: "English (US)",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
314
src/Explorer/Controls/InputDataList/InputDataList.tsx
Normal file
314
src/Explorer/Controls/InputDataList/InputDataList.tsx
Normal file
@ -0,0 +1,314 @@
|
|||||||
|
// This component is used to create a dropdown list of options for the user to select from.
|
||||||
|
// The options are displayed in a dropdown list when the user clicks on the input field.
|
||||||
|
// The user can then select an option from the list. The selected option is then displayed in the input field.
|
||||||
|
|
||||||
|
import { getTheme } from "@fluentui/react";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Divider,
|
||||||
|
Input,
|
||||||
|
Link,
|
||||||
|
makeStyles,
|
||||||
|
Popover,
|
||||||
|
PopoverProps,
|
||||||
|
PopoverSurface,
|
||||||
|
PositioningImperativeRef,
|
||||||
|
} from "@fluentui/react-components";
|
||||||
|
import { ArrowDownRegular, DismissRegular } from "@fluentui/react-icons";
|
||||||
|
import { NormalizedEventKey } from "Common/Constants";
|
||||||
|
import { tokens } from "Explorer/Theme/ThemeUtil";
|
||||||
|
import React, { FC, useEffect, useRef } from "react";
|
||||||
|
|
||||||
|
const useStyles = makeStyles({
|
||||||
|
container: {
|
||||||
|
padding: 0,
|
||||||
|
},
|
||||||
|
input: {
|
||||||
|
flexGrow: 1,
|
||||||
|
paddingRight: 0,
|
||||||
|
outline: "none",
|
||||||
|
"& input:focus": {
|
||||||
|
outline: "none", // Undo body :focus dashed outline
|
||||||
|
},
|
||||||
|
},
|
||||||
|
inputButton: {
|
||||||
|
border: 0,
|
||||||
|
},
|
||||||
|
dropdownHeader: {
|
||||||
|
width: "100%",
|
||||||
|
fontSize: tokens.fontSizeBase300,
|
||||||
|
fontWeight: 600,
|
||||||
|
padding: `${tokens.spacingVerticalM} 0 0 ${tokens.spacingVerticalM}`,
|
||||||
|
},
|
||||||
|
dropdownStack: {
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
gap: tokens.spacingVerticalS,
|
||||||
|
marginTop: tokens.spacingVerticalS,
|
||||||
|
marginBottom: "1px",
|
||||||
|
},
|
||||||
|
dropdownOption: {
|
||||||
|
fontSize: tokens.fontSizeBase300,
|
||||||
|
fontWeight: 400,
|
||||||
|
justifyContent: "left",
|
||||||
|
padding: `${tokens.spacingHorizontalXS} ${tokens.spacingHorizontalS} ${tokens.spacingHorizontalXS} ${tokens.spacingHorizontalL}`,
|
||||||
|
overflow: "hidden",
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
textOverflow: "ellipsis",
|
||||||
|
border: 0,
|
||||||
|
":hover": {
|
||||||
|
outline: `1px dashed ${tokens.colorNeutralForeground1Hover}`,
|
||||||
|
backgroundColor: tokens.colorNeutralBackground2Hover,
|
||||||
|
color: tokens.colorNeutralForeground1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
bottomSection: {
|
||||||
|
fontSize: tokens.fontSizeBase300,
|
||||||
|
fontWeight: 400,
|
||||||
|
padding: `${tokens.spacingHorizontalXS} ${tokens.spacingHorizontalS} ${tokens.spacingHorizontalXS} ${tokens.spacingHorizontalL}`,
|
||||||
|
overflow: "hidden",
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
textOverflow: "ellipsis",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export interface InputDatalistDropdownOptionSection {
|
||||||
|
label: string;
|
||||||
|
options: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface InputDataListProps {
|
||||||
|
dropdownOptions: InputDatalistDropdownOptionSection[];
|
||||||
|
placeholder?: string;
|
||||||
|
title?: string;
|
||||||
|
value: string;
|
||||||
|
onChange: (value: string) => void;
|
||||||
|
onKeyDown?: (e: React.KeyboardEvent<HTMLInputElement>) => void;
|
||||||
|
autofocus?: boolean; // true: acquire focus on first render
|
||||||
|
bottomLink?: {
|
||||||
|
text: string;
|
||||||
|
url: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const InputDataList: FC<InputDataListProps> = ({
|
||||||
|
dropdownOptions,
|
||||||
|
placeholder,
|
||||||
|
title,
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
onKeyDown,
|
||||||
|
autofocus,
|
||||||
|
bottomLink,
|
||||||
|
}) => {
|
||||||
|
const styles = useStyles();
|
||||||
|
const [showDropdown, setShowDropdown] = React.useState(false);
|
||||||
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
const positioningRef = React.useRef<PositioningImperativeRef>(null);
|
||||||
|
const [isInputFocused, setIsInputFocused] = React.useState(autofocus);
|
||||||
|
const [autofocusFirstDropdownItem, setAutofocusFirstDropdownItem] = React.useState(false);
|
||||||
|
|
||||||
|
const theme = getTheme();
|
||||||
|
const itemRefs = useRef([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (inputRef.current) {
|
||||||
|
positioningRef.current?.setTarget(inputRef.current);
|
||||||
|
}
|
||||||
|
}, [inputRef, positioningRef]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isInputFocused) {
|
||||||
|
inputRef.current?.focus();
|
||||||
|
}
|
||||||
|
}, [isInputFocused]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (autofocusFirstDropdownItem && showDropdown) {
|
||||||
|
// Autofocus on first item if input isn't focused
|
||||||
|
itemRefs.current[0]?.focus();
|
||||||
|
setAutofocusFirstDropdownItem(false);
|
||||||
|
}
|
||||||
|
}, [autofocusFirstDropdownItem, showDropdown]);
|
||||||
|
|
||||||
|
const handleOpenChange: PopoverProps["onOpenChange"] = (e, data) => {
|
||||||
|
if (isInputFocused && !data.open) {
|
||||||
|
// Don't close if input is focused and we're opening the dropdown (which will steal the focus)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setShowDropdown(data.open || false);
|
||||||
|
if (data.open) {
|
||||||
|
setIsInputFocused(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleInputKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||||
|
if (e.key === NormalizedEventKey.Escape) {
|
||||||
|
setShowDropdown(false);
|
||||||
|
} else if (e.key === NormalizedEventKey.DownArrow) {
|
||||||
|
setShowDropdown(true);
|
||||||
|
setAutofocusFirstDropdownItem(true);
|
||||||
|
}
|
||||||
|
onKeyDown(e);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDownDropdownItemKeyDown = (
|
||||||
|
e: React.KeyboardEvent<HTMLButtonElement | HTMLAnchorElement>,
|
||||||
|
index: number,
|
||||||
|
) => {
|
||||||
|
if (e.key === NormalizedEventKey.Enter) {
|
||||||
|
e.currentTarget.click();
|
||||||
|
} else if (e.key === NormalizedEventKey.Escape) {
|
||||||
|
setShowDropdown(false);
|
||||||
|
inputRef.current?.focus();
|
||||||
|
} else if (e.key === NormalizedEventKey.DownArrow) {
|
||||||
|
if (index + 1 < itemRefs.current.length) {
|
||||||
|
itemRefs.current[index + 1].focus();
|
||||||
|
} else {
|
||||||
|
setIsInputFocused(true);
|
||||||
|
}
|
||||||
|
} else if (e.key === NormalizedEventKey.UpArrow) {
|
||||||
|
if (index - 1 >= 0) {
|
||||||
|
itemRefs.current[index - 1].focus();
|
||||||
|
} else {
|
||||||
|
// Last item, focus back to input
|
||||||
|
setIsInputFocused(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Flatten dropdownOptions to better manage refs and focus
|
||||||
|
let flatIndex = 0;
|
||||||
|
const indexMap = new Map<string, number>();
|
||||||
|
for (let sectionIndex = 0; sectionIndex < dropdownOptions.length; sectionIndex++) {
|
||||||
|
const section = dropdownOptions[sectionIndex];
|
||||||
|
for (let optionIndex = 0; optionIndex < section.options.length; optionIndex++) {
|
||||||
|
indexMap.set(`${sectionIndex}-${optionIndex}`, flatIndex);
|
||||||
|
flatIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Input
|
||||||
|
id="filterInput"
|
||||||
|
ref={inputRef}
|
||||||
|
type="text"
|
||||||
|
size="small"
|
||||||
|
autoComplete="off"
|
||||||
|
className={`filterInput ${styles.input}`}
|
||||||
|
title={title}
|
||||||
|
placeholder={placeholder}
|
||||||
|
value={value}
|
||||||
|
autoFocus
|
||||||
|
onKeyDown={handleInputKeyDown}
|
||||||
|
onChange={(e) => {
|
||||||
|
const newValue = e.target.value;
|
||||||
|
// Don't show dropdown if there is already a value in the input field (when user is typing)
|
||||||
|
setShowDropdown(!(newValue.length > 0));
|
||||||
|
onChange(newValue);
|
||||||
|
}}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
onFocus={() => {
|
||||||
|
// Don't show dropdown if there is already a value in the input field
|
||||||
|
// or isInputFocused is undefined which means component is mounting
|
||||||
|
setShowDropdown(!(value.length > 0) && isInputFocused !== undefined);
|
||||||
|
|
||||||
|
setIsInputFocused(true);
|
||||||
|
}}
|
||||||
|
onBlur={() => {
|
||||||
|
setIsInputFocused(false);
|
||||||
|
}}
|
||||||
|
contentAfter={
|
||||||
|
value.length > 0 ? (
|
||||||
|
<Button
|
||||||
|
aria-label="Clear filter"
|
||||||
|
className={styles.inputButton}
|
||||||
|
size="small"
|
||||||
|
icon={<DismissRegular />}
|
||||||
|
onClick={() => {
|
||||||
|
onChange("");
|
||||||
|
setIsInputFocused(true);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
aria-label="Open dropdown"
|
||||||
|
className={styles.inputButton}
|
||||||
|
size="small"
|
||||||
|
icon={<ArrowDownRegular />}
|
||||||
|
onClick={() => {
|
||||||
|
setShowDropdown(true);
|
||||||
|
setAutofocusFirstDropdownItem(true);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Popover
|
||||||
|
inline
|
||||||
|
unstable_disableAutoFocus
|
||||||
|
// trapFocus
|
||||||
|
open={showDropdown}
|
||||||
|
onOpenChange={handleOpenChange}
|
||||||
|
positioning={{ positioningRef, position: "below", align: "start", offset: 4 }}
|
||||||
|
>
|
||||||
|
<PopoverSurface className={styles.container}>
|
||||||
|
{dropdownOptions.map((section, sectionIndex) => (
|
||||||
|
<div key={section.label}>
|
||||||
|
<div className={styles.dropdownHeader} style={{ color: theme.palette.themePrimary }}>
|
||||||
|
{section.label}
|
||||||
|
</div>
|
||||||
|
<div className={styles.dropdownStack}>
|
||||||
|
{section.options.map((option, index) => (
|
||||||
|
<Button
|
||||||
|
key={option}
|
||||||
|
ref={(el) => (itemRefs.current[indexMap.get(`${sectionIndex}-${index}`)] = el)}
|
||||||
|
appearance="transparent"
|
||||||
|
shape="square"
|
||||||
|
className={styles.dropdownOption}
|
||||||
|
onClick={() => {
|
||||||
|
onChange(option);
|
||||||
|
setShowDropdown(false);
|
||||||
|
setIsInputFocused(true);
|
||||||
|
}}
|
||||||
|
onBlur={() =>
|
||||||
|
!bottomLink &&
|
||||||
|
sectionIndex === dropdownOptions.length - 1 &&
|
||||||
|
index === section.options.length - 1 &&
|
||||||
|
setShowDropdown(false)
|
||||||
|
}
|
||||||
|
onKeyDown={(e: React.KeyboardEvent<HTMLButtonElement>) =>
|
||||||
|
handleDownDropdownItemKeyDown(e, indexMap.get(`${sectionIndex}-${index}`))
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{option}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
{bottomLink && (
|
||||||
|
<>
|
||||||
|
<Divider />
|
||||||
|
<div className={styles.bottomSection}>
|
||||||
|
<Link
|
||||||
|
ref={(el) => (itemRefs.current[flatIndex] = el)}
|
||||||
|
href={bottomLink.url}
|
||||||
|
target="_blank"
|
||||||
|
onBlur={() => setShowDropdown(false)}
|
||||||
|
onKeyDown={(e: React.KeyboardEvent<HTMLAnchorElement>) => handleDownDropdownItemKeyDown(e, flatIndex)}
|
||||||
|
>
|
||||||
|
{bottomLink.text}
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</PopoverSurface>
|
||||||
|
</Popover>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -4,11 +4,11 @@ import {
|
|||||||
ComputedPropertiesComponentProps,
|
ComputedPropertiesComponentProps,
|
||||||
} from "Explorer/Controls/Settings/SettingsSubComponents/ComputedPropertiesComponent";
|
} from "Explorer/Controls/Settings/SettingsSubComponents/ComputedPropertiesComponent";
|
||||||
import {
|
import {
|
||||||
ContainerVectorPolicyComponent,
|
ContainerPolicyComponent,
|
||||||
ContainerVectorPolicyComponentProps,
|
ContainerPolicyComponentProps,
|
||||||
} from "Explorer/Controls/Settings/SettingsSubComponents/ContainerVectorPolicyComponent";
|
} from "Explorer/Controls/Settings/SettingsSubComponents/ContainerPolicyComponent";
|
||||||
import { useDatabases } from "Explorer/useDatabases";
|
import { useDatabases } from "Explorer/useDatabases";
|
||||||
import { isVectorSearchEnabled } from "Utils/CapabilityUtils";
|
import { isFullTextSearchEnabled, isVectorSearchEnabled } from "Utils/CapabilityUtils";
|
||||||
import { isRunningOnPublicCloud } from "Utils/CloudUtils";
|
import { isRunningOnPublicCloud } from "Utils/CloudUtils";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import DiscardIcon from "../../../../images/discard.svg";
|
import DiscardIcon from "../../../../images/discard.svg";
|
||||||
@ -105,6 +105,13 @@ export interface SettingsComponentState {
|
|||||||
isSubSettingsSaveable: boolean;
|
isSubSettingsSaveable: boolean;
|
||||||
isSubSettingsDiscardable: boolean;
|
isSubSettingsDiscardable: boolean;
|
||||||
|
|
||||||
|
vectorEmbeddingPolicy: DataModels.VectorEmbeddingPolicy;
|
||||||
|
vectorEmbeddingPolicyBaseline: DataModels.VectorEmbeddingPolicy;
|
||||||
|
fullTextPolicy: DataModels.FullTextPolicy;
|
||||||
|
fullTextPolicyBaseline: DataModels.FullTextPolicy;
|
||||||
|
shouldDiscardContainerPolicies: boolean;
|
||||||
|
isContainerPolicyDirty: boolean;
|
||||||
|
|
||||||
indexingPolicyContent: DataModels.IndexingPolicy;
|
indexingPolicyContent: DataModels.IndexingPolicy;
|
||||||
indexingPolicyContentBaseline: DataModels.IndexingPolicy;
|
indexingPolicyContentBaseline: DataModels.IndexingPolicy;
|
||||||
shouldDiscardIndexingPolicy: boolean;
|
shouldDiscardIndexingPolicy: boolean;
|
||||||
@ -149,6 +156,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
private shouldShowIndexingPolicyEditor: boolean;
|
private shouldShowIndexingPolicyEditor: boolean;
|
||||||
private shouldShowPartitionKeyEditor: boolean;
|
private shouldShowPartitionKeyEditor: boolean;
|
||||||
private isVectorSearchEnabled: boolean;
|
private isVectorSearchEnabled: boolean;
|
||||||
|
private isFullTextSearchEnabled: boolean;
|
||||||
private totalThroughputUsed: number;
|
private totalThroughputUsed: number;
|
||||||
public mongoDBCollectionResource: MongoDBCollectionResource;
|
public mongoDBCollectionResource: MongoDBCollectionResource;
|
||||||
|
|
||||||
@ -164,6 +172,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
this.shouldShowIndexingPolicyEditor = userContext.apiType !== "Cassandra" && userContext.apiType !== "Mongo";
|
this.shouldShowIndexingPolicyEditor = userContext.apiType !== "Cassandra" && userContext.apiType !== "Mongo";
|
||||||
this.shouldShowPartitionKeyEditor = userContext.apiType === "SQL" && isRunningOnPublicCloud();
|
this.shouldShowPartitionKeyEditor = userContext.apiType === "SQL" && isRunningOnPublicCloud();
|
||||||
this.isVectorSearchEnabled = isVectorSearchEnabled() && !hasDatabaseSharedThroughput(this.collection);
|
this.isVectorSearchEnabled = isVectorSearchEnabled() && !hasDatabaseSharedThroughput(this.collection);
|
||||||
|
this.isFullTextSearchEnabled = isFullTextSearchEnabled() && !hasDatabaseSharedThroughput(this.collection);
|
||||||
|
|
||||||
this.changeFeedPolicyVisible = userContext.features.enableChangeFeedPolicy;
|
this.changeFeedPolicyVisible = userContext.features.enableChangeFeedPolicy;
|
||||||
|
|
||||||
@ -203,6 +212,13 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
isSubSettingsSaveable: false,
|
isSubSettingsSaveable: false,
|
||||||
isSubSettingsDiscardable: false,
|
isSubSettingsDiscardable: false,
|
||||||
|
|
||||||
|
vectorEmbeddingPolicy: undefined,
|
||||||
|
vectorEmbeddingPolicyBaseline: undefined,
|
||||||
|
fullTextPolicy: undefined,
|
||||||
|
fullTextPolicyBaseline: undefined,
|
||||||
|
shouldDiscardContainerPolicies: false,
|
||||||
|
isContainerPolicyDirty: false,
|
||||||
|
|
||||||
indexingPolicyContent: undefined,
|
indexingPolicyContent: undefined,
|
||||||
indexingPolicyContentBaseline: undefined,
|
indexingPolicyContentBaseline: undefined,
|
||||||
shouldDiscardIndexingPolicy: false,
|
shouldDiscardIndexingPolicy: false,
|
||||||
@ -307,6 +323,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
return (
|
return (
|
||||||
this.state.isScaleSaveable ||
|
this.state.isScaleSaveable ||
|
||||||
this.state.isSubSettingsSaveable ||
|
this.state.isSubSettingsSaveable ||
|
||||||
|
this.state.isContainerPolicyDirty ||
|
||||||
this.state.isIndexingPolicyDirty ||
|
this.state.isIndexingPolicyDirty ||
|
||||||
this.state.isConflictResolutionDirty ||
|
this.state.isConflictResolutionDirty ||
|
||||||
this.state.isComputedPropertiesDirty ||
|
this.state.isComputedPropertiesDirty ||
|
||||||
@ -318,6 +335,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
return (
|
return (
|
||||||
this.state.isScaleDiscardable ||
|
this.state.isScaleDiscardable ||
|
||||||
this.state.isSubSettingsDiscardable ||
|
this.state.isSubSettingsDiscardable ||
|
||||||
|
this.state.isContainerPolicyDirty ||
|
||||||
this.state.isIndexingPolicyDirty ||
|
this.state.isIndexingPolicyDirty ||
|
||||||
this.state.isConflictResolutionDirty ||
|
this.state.isConflictResolutionDirty ||
|
||||||
this.state.isComputedPropertiesDirty ||
|
this.state.isComputedPropertiesDirty ||
|
||||||
@ -405,6 +423,8 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
timeToLiveSeconds: this.state.timeToLiveSecondsBaseline,
|
timeToLiveSeconds: this.state.timeToLiveSecondsBaseline,
|
||||||
displayedTtlSeconds: this.state.displayedTtlSecondsBaseline,
|
displayedTtlSeconds: this.state.displayedTtlSecondsBaseline,
|
||||||
geospatialConfigType: this.state.geospatialConfigTypeBaseline,
|
geospatialConfigType: this.state.geospatialConfigTypeBaseline,
|
||||||
|
vectorEmbeddingPolicy: this.state.vectorEmbeddingPolicyBaseline,
|
||||||
|
fullTextPolicy: this.state.fullTextPolicyBaseline,
|
||||||
indexingPolicyContent: this.state.indexingPolicyContentBaseline,
|
indexingPolicyContent: this.state.indexingPolicyContentBaseline,
|
||||||
indexesToAdd: [],
|
indexesToAdd: [],
|
||||||
indexesToDrop: [],
|
indexesToDrop: [],
|
||||||
@ -416,11 +436,13 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
changeFeedPolicy: this.state.changeFeedPolicyBaseline,
|
changeFeedPolicy: this.state.changeFeedPolicyBaseline,
|
||||||
autoPilotThroughput: this.state.autoPilotThroughputBaseline,
|
autoPilotThroughput: this.state.autoPilotThroughputBaseline,
|
||||||
isAutoPilotSelected: this.state.wasAutopilotOriginallySet,
|
isAutoPilotSelected: this.state.wasAutopilotOriginallySet,
|
||||||
|
shouldDiscardContainerPolicies: true,
|
||||||
shouldDiscardIndexingPolicy: true,
|
shouldDiscardIndexingPolicy: true,
|
||||||
isScaleSaveable: false,
|
isScaleSaveable: false,
|
||||||
isScaleDiscardable: false,
|
isScaleDiscardable: false,
|
||||||
isSubSettingsSaveable: false,
|
isSubSettingsSaveable: false,
|
||||||
isSubSettingsDiscardable: false,
|
isSubSettingsDiscardable: false,
|
||||||
|
isContainerPolicyDirty: false,
|
||||||
isIndexingPolicyDirty: false,
|
isIndexingPolicyDirty: false,
|
||||||
isMongoIndexingPolicySaveable: false,
|
isMongoIndexingPolicySaveable: false,
|
||||||
isMongoIndexingPolicyDiscardable: false,
|
isMongoIndexingPolicyDiscardable: false,
|
||||||
@ -448,9 +470,17 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
private onScaleDiscardableChange = (isScaleDiscardable: boolean): void =>
|
private onScaleDiscardableChange = (isScaleDiscardable: boolean): void =>
|
||||||
this.setState({ isScaleDiscardable: isScaleDiscardable });
|
this.setState({ isScaleDiscardable: isScaleDiscardable });
|
||||||
|
|
||||||
|
private onVectorEmbeddingPolicyChange = (newVectorEmbeddingPolicy: DataModels.VectorEmbeddingPolicy): void =>
|
||||||
|
this.setState({ vectorEmbeddingPolicy: newVectorEmbeddingPolicy });
|
||||||
|
|
||||||
|
private onFullTextPolicyChange = (newFullTextPolicy: DataModels.FullTextPolicy): void =>
|
||||||
|
this.setState({ fullTextPolicy: newFullTextPolicy });
|
||||||
|
|
||||||
private onIndexingPolicyContentChange = (newIndexingPolicy: DataModels.IndexingPolicy): void =>
|
private onIndexingPolicyContentChange = (newIndexingPolicy: DataModels.IndexingPolicy): void =>
|
||||||
this.setState({ indexingPolicyContent: newIndexingPolicy });
|
this.setState({ indexingPolicyContent: newIndexingPolicy });
|
||||||
|
|
||||||
|
private resetShouldDiscardContainerPolicies = (): void => this.setState({ shouldDiscardContainerPolicies: false });
|
||||||
|
|
||||||
private resetShouldDiscardIndexingPolicy = (): void => this.setState({ shouldDiscardIndexingPolicy: false });
|
private resetShouldDiscardIndexingPolicy = (): void => this.setState({ shouldDiscardIndexingPolicy: false });
|
||||||
|
|
||||||
private logIndexingPolicySuccessMessage = (): void => {
|
private logIndexingPolicySuccessMessage = (): void => {
|
||||||
@ -538,6 +568,12 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
private onSubSettingsDiscardableChange = (isSubSettingsDiscardable: boolean): void =>
|
private onSubSettingsDiscardableChange = (isSubSettingsDiscardable: boolean): void =>
|
||||||
this.setState({ isSubSettingsDiscardable: isSubSettingsDiscardable });
|
this.setState({ isSubSettingsDiscardable: isSubSettingsDiscardable });
|
||||||
|
|
||||||
|
private onVectorEmbeddingPolicyDirtyChange = (isVectorEmbeddingPolicyDirty: boolean): void =>
|
||||||
|
this.setState({ isContainerPolicyDirty: isVectorEmbeddingPolicyDirty });
|
||||||
|
|
||||||
|
private onFullTextPolicyDirtyChange = (isFullTextPolicyDirty: boolean): void =>
|
||||||
|
this.setState({ isContainerPolicyDirty: isFullTextPolicyDirty });
|
||||||
|
|
||||||
private onIndexingPolicyDirtyChange = (isIndexingPolicyDirty: boolean): void =>
|
private onIndexingPolicyDirtyChange = (isIndexingPolicyDirty: boolean): void =>
|
||||||
this.setState({ isIndexingPolicyDirty: isIndexingPolicyDirty });
|
this.setState({ isIndexingPolicyDirty: isIndexingPolicyDirty });
|
||||||
|
|
||||||
@ -691,6 +727,10 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
const changeFeedPolicy = this.collection.rawDataModel?.changeFeedPolicy
|
const changeFeedPolicy = this.collection.rawDataModel?.changeFeedPolicy
|
||||||
? ChangeFeedPolicyState.On
|
? ChangeFeedPolicyState.On
|
||||||
: ChangeFeedPolicyState.Off;
|
: ChangeFeedPolicyState.Off;
|
||||||
|
const vectorEmbeddingPolicy: DataModels.VectorEmbeddingPolicy =
|
||||||
|
this.collection.vectorEmbeddingPolicy && this.collection.vectorEmbeddingPolicy();
|
||||||
|
const fullTextPolicy: DataModels.FullTextPolicy =
|
||||||
|
this.collection.fullTextPolicy && this.collection.fullTextPolicy();
|
||||||
const indexingPolicyContent = this.collection.indexingPolicy();
|
const indexingPolicyContent = this.collection.indexingPolicy();
|
||||||
const conflictResolutionPolicy: DataModels.ConflictResolutionPolicy =
|
const conflictResolutionPolicy: DataModels.ConflictResolutionPolicy =
|
||||||
this.collection.conflictResolutionPolicy && this.collection.conflictResolutionPolicy();
|
this.collection.conflictResolutionPolicy && this.collection.conflictResolutionPolicy();
|
||||||
@ -724,6 +764,10 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
analyticalStorageTtlSelectionBaseline: analyticalStorageTtlSelection,
|
analyticalStorageTtlSelectionBaseline: analyticalStorageTtlSelection,
|
||||||
analyticalStorageTtlSeconds: analyticalStorageTtlSeconds,
|
analyticalStorageTtlSeconds: analyticalStorageTtlSeconds,
|
||||||
analyticalStorageTtlSecondsBaseline: analyticalStorageTtlSeconds,
|
analyticalStorageTtlSecondsBaseline: analyticalStorageTtlSeconds,
|
||||||
|
vectorEmbeddingPolicy: vectorEmbeddingPolicy,
|
||||||
|
vectorEmbeddingPolicyBaseline: vectorEmbeddingPolicy,
|
||||||
|
fullTextPolicy: fullTextPolicy,
|
||||||
|
fullTextPolicyBaseline: fullTextPolicy,
|
||||||
indexingPolicyContent: indexingPolicyContent,
|
indexingPolicyContent: indexingPolicyContent,
|
||||||
indexingPolicyContentBaseline: indexingPolicyContent,
|
indexingPolicyContentBaseline: indexingPolicyContent,
|
||||||
conflictResolutionPolicyMode: conflictResolutionPolicyMode,
|
conflictResolutionPolicyMode: conflictResolutionPolicyMode,
|
||||||
@ -854,6 +898,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
this.state.isSubSettingsSaveable ||
|
this.state.isSubSettingsSaveable ||
|
||||||
|
this.state.isContainerPolicyDirty ||
|
||||||
this.state.isIndexingPolicyDirty ||
|
this.state.isIndexingPolicyDirty ||
|
||||||
this.state.isConflictResolutionDirty ||
|
this.state.isConflictResolutionDirty ||
|
||||||
this.state.isComputedPropertiesDirty
|
this.state.isComputedPropertiesDirty
|
||||||
@ -875,6 +920,10 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
const wasIndexingPolicyModified = this.state.isIndexingPolicyDirty;
|
const wasIndexingPolicyModified = this.state.isIndexingPolicyDirty;
|
||||||
newCollection.defaultTtl = defaultTtl;
|
newCollection.defaultTtl = defaultTtl;
|
||||||
|
|
||||||
|
newCollection.vectorEmbeddingPolicy = this.state.vectorEmbeddingPolicy;
|
||||||
|
|
||||||
|
newCollection.fullTextPolicy = this.state.fullTextPolicy;
|
||||||
|
|
||||||
newCollection.indexingPolicy = this.state.indexingPolicyContent;
|
newCollection.indexingPolicy = this.state.indexingPolicyContent;
|
||||||
|
|
||||||
newCollection.changeFeedPolicy =
|
newCollection.changeFeedPolicy =
|
||||||
@ -913,6 +962,8 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
this.collection.changeFeedPolicy(updatedCollection.changeFeedPolicy);
|
this.collection.changeFeedPolicy(updatedCollection.changeFeedPolicy);
|
||||||
this.collection.geospatialConfig(updatedCollection.geospatialConfig);
|
this.collection.geospatialConfig(updatedCollection.geospatialConfig);
|
||||||
this.collection.computedProperties(updatedCollection.computedProperties);
|
this.collection.computedProperties(updatedCollection.computedProperties);
|
||||||
|
this.collection.vectorEmbeddingPolicy(updatedCollection.vectorEmbeddingPolicy);
|
||||||
|
this.collection.fullTextPolicy(updatedCollection.fullTextPolicy);
|
||||||
|
|
||||||
if (wasIndexingPolicyModified) {
|
if (wasIndexingPolicyModified) {
|
||||||
await this.refreshIndexTransformationProgress();
|
await this.refreshIndexTransformationProgress();
|
||||||
@ -921,6 +972,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
this.setState({
|
this.setState({
|
||||||
isSubSettingsSaveable: false,
|
isSubSettingsSaveable: false,
|
||||||
isSubSettingsDiscardable: false,
|
isSubSettingsDiscardable: false,
|
||||||
|
isContainerPolicyDirty: false,
|
||||||
isIndexingPolicyDirty: false,
|
isIndexingPolicyDirty: false,
|
||||||
isConflictResolutionDirty: false,
|
isConflictResolutionDirty: false,
|
||||||
isComputedPropertiesDirty: false,
|
isComputedPropertiesDirty: false,
|
||||||
@ -1091,6 +1143,21 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
onSubSettingsDiscardableChange: this.onSubSettingsDiscardableChange,
|
onSubSettingsDiscardableChange: this.onSubSettingsDiscardableChange,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const containerPolicyComponentProps: ContainerPolicyComponentProps = {
|
||||||
|
vectorEmbeddingPolicy: this.state.vectorEmbeddingPolicy,
|
||||||
|
vectorEmbeddingPolicyBaseline: this.state.vectorEmbeddingPolicyBaseline,
|
||||||
|
onVectorEmbeddingPolicyChange: this.onVectorEmbeddingPolicyChange,
|
||||||
|
onVectorEmbeddingPolicyDirtyChange: this.onVectorEmbeddingPolicyDirtyChange,
|
||||||
|
isVectorSearchEnabled: this.isVectorSearchEnabled,
|
||||||
|
fullTextPolicy: this.state.fullTextPolicy,
|
||||||
|
fullTextPolicyBaseline: this.state.fullTextPolicyBaseline,
|
||||||
|
onFullTextPolicyChange: this.onFullTextPolicyChange,
|
||||||
|
onFullTextPolicyDirtyChange: this.onFullTextPolicyDirtyChange,
|
||||||
|
isFullTextSearchEnabled: this.isFullTextSearchEnabled,
|
||||||
|
shouldDiscardContainerPolicies: this.state.shouldDiscardContainerPolicies,
|
||||||
|
resetShouldDiscardContainerPolicyChange: this.resetShouldDiscardContainerPolicies,
|
||||||
|
};
|
||||||
|
|
||||||
const indexingPolicyComponentProps: IndexingPolicyComponentProps = {
|
const indexingPolicyComponentProps: IndexingPolicyComponentProps = {
|
||||||
shouldDiscardIndexingPolicy: this.state.shouldDiscardIndexingPolicy,
|
shouldDiscardIndexingPolicy: this.state.shouldDiscardIndexingPolicy,
|
||||||
resetShouldDiscardIndexingPolicy: this.resetShouldDiscardIndexingPolicy,
|
resetShouldDiscardIndexingPolicy: this.resetShouldDiscardIndexingPolicy,
|
||||||
@ -1148,10 +1215,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
explorer: this.props.settingsTab.getContainer(),
|
explorer: this.props.settingsTab.getContainer(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const containerVectorPolicyProps: ContainerVectorPolicyComponentProps = {
|
|
||||||
vectorEmbeddingPolicy: this.collection.rawDataModel?.vectorEmbeddingPolicy,
|
|
||||||
};
|
|
||||||
|
|
||||||
const tabs: SettingsV2TabInfo[] = [];
|
const tabs: SettingsV2TabInfo[] = [];
|
||||||
if (!hasDatabaseSharedThroughput(this.collection) && this.offer) {
|
if (!hasDatabaseSharedThroughput(this.collection) && this.offer) {
|
||||||
tabs.push({
|
tabs.push({
|
||||||
@ -1165,10 +1228,10 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
|
|||||||
content: <SubSettingsComponent {...subSettingsComponentProps} />,
|
content: <SubSettingsComponent {...subSettingsComponentProps} />,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.isVectorSearchEnabled) {
|
if (this.isVectorSearchEnabled || this.isFullTextSearchEnabled) {
|
||||||
tabs.push({
|
tabs.push({
|
||||||
tab: SettingsV2TabTypes.ContainerVectorPolicyTab,
|
tab: SettingsV2TabTypes.ContainerVectorPolicyTab,
|
||||||
content: <ContainerVectorPolicyComponent {...containerVectorPolicyProps} />,
|
content: <ContainerPolicyComponent {...containerPolicyComponentProps} />,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
import "@testing-library/jest-dom";
|
||||||
|
|
||||||
|
describe("ContainerPolicyComponent", () => {
|
||||||
|
//CTODO: add tests
|
||||||
|
it.skip("should render correctly", () => {});
|
||||||
|
});
|
@ -0,0 +1,163 @@
|
|||||||
|
import { DefaultButton, Pivot, PivotItem, Stack } from "@fluentui/react";
|
||||||
|
import { FullTextPolicy, VectorEmbedding, VectorEmbeddingPolicy } from "Contracts/DataModels";
|
||||||
|
import {
|
||||||
|
FullTextPoliciesComponent,
|
||||||
|
getFullTextLanguageOptions,
|
||||||
|
} from "Explorer/Controls/FullTextSeach/FullTextPoliciesComponent";
|
||||||
|
import { titleAndInputStackProps } from "Explorer/Controls/Settings/SettingsRenderUtils";
|
||||||
|
import { ContainerPolicyTabTypes, isDirty } from "Explorer/Controls/Settings/SettingsUtils";
|
||||||
|
import { VectorEmbeddingPoliciesComponent } from "Explorer/Controls/VectorSearch/VectorEmbeddingPoliciesComponent";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export interface ContainerPolicyComponentProps {
|
||||||
|
vectorEmbeddingPolicy: VectorEmbeddingPolicy;
|
||||||
|
vectorEmbeddingPolicyBaseline: VectorEmbeddingPolicy;
|
||||||
|
onVectorEmbeddingPolicyChange: (newVectorEmbeddingPolicy: VectorEmbeddingPolicy) => void;
|
||||||
|
onVectorEmbeddingPolicyDirtyChange: (isVectorEmbeddingPolicyDirty: boolean) => void;
|
||||||
|
isVectorSearchEnabled: boolean;
|
||||||
|
fullTextPolicy: FullTextPolicy;
|
||||||
|
fullTextPolicyBaseline: FullTextPolicy;
|
||||||
|
onFullTextPolicyChange: (newFullTextPolicy: FullTextPolicy) => void;
|
||||||
|
onFullTextPolicyDirtyChange: (isFullTextPolicyDirty: boolean) => void;
|
||||||
|
isFullTextSearchEnabled: boolean;
|
||||||
|
shouldDiscardContainerPolicies: boolean;
|
||||||
|
resetShouldDiscardContainerPolicyChange: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ContainerPolicyComponent: React.FC<ContainerPolicyComponentProps> = ({
|
||||||
|
vectorEmbeddingPolicy,
|
||||||
|
vectorEmbeddingPolicyBaseline,
|
||||||
|
onVectorEmbeddingPolicyChange,
|
||||||
|
onVectorEmbeddingPolicyDirtyChange,
|
||||||
|
isVectorSearchEnabled,
|
||||||
|
fullTextPolicy,
|
||||||
|
fullTextPolicyBaseline,
|
||||||
|
onFullTextPolicyChange,
|
||||||
|
onFullTextPolicyDirtyChange,
|
||||||
|
isFullTextSearchEnabled,
|
||||||
|
shouldDiscardContainerPolicies,
|
||||||
|
resetShouldDiscardContainerPolicyChange,
|
||||||
|
}) => {
|
||||||
|
const [selectedTab, setSelectedTab] = React.useState<ContainerPolicyTabTypes>(
|
||||||
|
ContainerPolicyTabTypes.VectorPolicyTab,
|
||||||
|
);
|
||||||
|
const [vectorEmbeddings, setVectorEmbeddings] = React.useState<VectorEmbedding[]>();
|
||||||
|
const [vectorEmbeddingsBaseline, setVectorEmbeddingsBaseline] = React.useState<VectorEmbedding[]>();
|
||||||
|
const [discardVectorChanges, setDiscardVectorChanges] = React.useState<boolean>(false);
|
||||||
|
const [fullTextSearchPolicy, setFullTextSearchPolicy] = React.useState<FullTextPolicy>();
|
||||||
|
const [fullTextSearchPolicyBaseline, setFullTextSearchPolicyBaseline] = React.useState<FullTextPolicy>();
|
||||||
|
const [discardFullTextChanges, setDiscardFullTextChanges] = React.useState<boolean>(false);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
setVectorEmbeddings(vectorEmbeddingPolicy?.vectorEmbeddings);
|
||||||
|
setVectorEmbeddingsBaseline(vectorEmbeddingPolicyBaseline?.vectorEmbeddings);
|
||||||
|
}, [vectorEmbeddingPolicy]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
setFullTextSearchPolicy(fullTextPolicy);
|
||||||
|
setFullTextSearchPolicyBaseline(fullTextPolicyBaseline);
|
||||||
|
}, [fullTextPolicy, fullTextPolicyBaseline]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (shouldDiscardContainerPolicies) {
|
||||||
|
setVectorEmbeddings(vectorEmbeddingPolicyBaseline?.vectorEmbeddings);
|
||||||
|
setDiscardVectorChanges(true);
|
||||||
|
setFullTextSearchPolicy(fullTextPolicyBaseline);
|
||||||
|
setDiscardFullTextChanges(true);
|
||||||
|
resetShouldDiscardContainerPolicyChange();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const checkAndSendVectorEmbeddingPoliciesToSettings = (newVectorEmbeddings: VectorEmbedding[]): void => {
|
||||||
|
if (isDirty(newVectorEmbeddings, vectorEmbeddingsBaseline)) {
|
||||||
|
onVectorEmbeddingPolicyDirtyChange(true);
|
||||||
|
onVectorEmbeddingPolicyChange({ vectorEmbeddings: newVectorEmbeddings });
|
||||||
|
} else {
|
||||||
|
resetShouldDiscardContainerPolicyChange();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkAndSendFullTextPolicyToSettings = (newFullTextPolicy: FullTextPolicy): void => {
|
||||||
|
if (isDirty(newFullTextPolicy, fullTextSearchPolicyBaseline)) {
|
||||||
|
onFullTextPolicyDirtyChange(true);
|
||||||
|
onFullTextPolicyChange(newFullTextPolicy);
|
||||||
|
} else {
|
||||||
|
resetShouldDiscardContainerPolicyChange();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onVectorChangesDiscarded = (): void => {
|
||||||
|
setDiscardVectorChanges(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onFullTextChangesDiscarded = (): void => {
|
||||||
|
setDiscardFullTextChanges(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onPivotChange = (item: PivotItem): void => {
|
||||||
|
const selectedTab = ContainerPolicyTabTypes[item.props.itemKey as keyof typeof ContainerPolicyTabTypes];
|
||||||
|
setSelectedTab(selectedTab);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Pivot onLinkClick={onPivotChange} selectedKey={ContainerPolicyTabTypes[selectedTab]}>
|
||||||
|
{isVectorSearchEnabled && (
|
||||||
|
<PivotItem
|
||||||
|
itemKey={ContainerPolicyTabTypes[ContainerPolicyTabTypes.VectorPolicyTab]}
|
||||||
|
style={{ marginTop: 20 }}
|
||||||
|
headerText="Vector Policy"
|
||||||
|
>
|
||||||
|
<Stack {...titleAndInputStackProps} styles={{ root: { position: "relative", maxWidth: "400px" } }}>
|
||||||
|
{vectorEmbeddings && (
|
||||||
|
<VectorEmbeddingPoliciesComponent
|
||||||
|
disabled={true}
|
||||||
|
vectorEmbeddings={vectorEmbeddings}
|
||||||
|
vectorIndexes={undefined}
|
||||||
|
onVectorEmbeddingChange={(vectorEmbeddings: VectorEmbedding[]) =>
|
||||||
|
checkAndSendVectorEmbeddingPoliciesToSettings(vectorEmbeddings)
|
||||||
|
}
|
||||||
|
discardChanges={discardVectorChanges}
|
||||||
|
onChangesDiscarded={onVectorChangesDiscarded}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
</PivotItem>
|
||||||
|
)}
|
||||||
|
{isFullTextSearchEnabled && (
|
||||||
|
<PivotItem
|
||||||
|
itemKey={ContainerPolicyTabTypes[ContainerPolicyTabTypes.FullTextPolicyTab]}
|
||||||
|
style={{ marginTop: 20 }}
|
||||||
|
headerText="Full Text Policy"
|
||||||
|
>
|
||||||
|
<Stack {...titleAndInputStackProps} styles={{ root: { position: "relative", maxWidth: "400px" } }}>
|
||||||
|
{fullTextSearchPolicy ? (
|
||||||
|
<FullTextPoliciesComponent
|
||||||
|
fullTextPolicy={fullTextSearchPolicy}
|
||||||
|
onFullTextPathChange={(newFullTextPolicy: FullTextPolicy) =>
|
||||||
|
checkAndSendFullTextPolicyToSettings(newFullTextPolicy)
|
||||||
|
}
|
||||||
|
discardChanges={discardFullTextChanges}
|
||||||
|
onChangesDiscarded={onFullTextChangesDiscarded}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<DefaultButton
|
||||||
|
id={"create-full-text-policy"}
|
||||||
|
styles={{ root: { fontSize: 12 } }}
|
||||||
|
onClick={() => {
|
||||||
|
checkAndSendFullTextPolicyToSettings({
|
||||||
|
defaultLanguage: getFullTextLanguageOptions()[0].key as never,
|
||||||
|
fullTextPaths: [],
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Create new full text search policy
|
||||||
|
</DefaultButton>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
</PivotItem>
|
||||||
|
)}
|
||||||
|
</Pivot>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -1,30 +0,0 @@
|
|||||||
import { Stack } from "@fluentui/react";
|
|
||||||
import { VectorEmbeddingPolicy } from "Contracts/DataModels";
|
|
||||||
import { EditorReact } from "Explorer/Controls/Editor/EditorReact";
|
|
||||||
import { titleAndInputStackProps } from "Explorer/Controls/Settings/SettingsRenderUtils";
|
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
export interface ContainerVectorPolicyComponentProps {
|
|
||||||
vectorEmbeddingPolicy: VectorEmbeddingPolicy;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ContainerVectorPolicyComponent: React.FC<ContainerVectorPolicyComponentProps> = ({
|
|
||||||
vectorEmbeddingPolicy,
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<Stack {...titleAndInputStackProps} styles={{ root: { position: "relative" } }}>
|
|
||||||
<EditorReact
|
|
||||||
language={"json"}
|
|
||||||
content={JSON.stringify(vectorEmbeddingPolicy || {}, null, 4)}
|
|
||||||
isReadOnly={true}
|
|
||||||
wordWrap={"on"}
|
|
||||||
ariaLabel={"Container vector policy"}
|
|
||||||
lineNumbers={"on"}
|
|
||||||
scrollBeyondLastLine={false}
|
|
||||||
className={"settingsV2Editor"}
|
|
||||||
spinnerClassName={"settingsV2EditorSpinner"}
|
|
||||||
fontSize={14}
|
|
||||||
/>
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
};
|
|
@ -120,11 +120,6 @@ export class IndexingPolicyComponent extends React.Component<
|
|||||||
indexTransformationProgress={this.props.indexTransformationProgress}
|
indexTransformationProgress={this.props.indexTransformationProgress}
|
||||||
refreshIndexTransformationProgress={this.props.refreshIndexTransformationProgress}
|
refreshIndexTransformationProgress={this.props.refreshIndexTransformationProgress}
|
||||||
/>
|
/>
|
||||||
{this.props.isVectorSearchEnabled && (
|
|
||||||
<MessageBar messageBarType={MessageBarType.severeWarning}>
|
|
||||||
Container vector policies and vector indexes are not modifiable after container creation
|
|
||||||
</MessageBar>
|
|
||||||
)}
|
|
||||||
{isDirty(this.props.indexingPolicyContent, this.props.indexingPolicyContentBaseline) && (
|
{isDirty(this.props.indexingPolicyContent, this.props.indexingPolicyContentBaseline) && (
|
||||||
<MessageBar messageBarType={MessageBarType.warning}>{unsavedEditorWarningMessage("indexPolicy")}</MessageBar>
|
<MessageBar messageBarType={MessageBarType.warning}>{unsavedEditorWarningMessage("indexPolicy")}</MessageBar>
|
||||||
)}
|
)}
|
||||||
|
@ -4,7 +4,14 @@ import * as ViewModels from "../../../Contracts/ViewModels";
|
|||||||
import { MongoIndex } from "../../../Utils/arm/generatedClients/cosmos/types";
|
import { MongoIndex } from "../../../Utils/arm/generatedClients/cosmos/types";
|
||||||
|
|
||||||
const zeroValue = 0;
|
const zeroValue = 0;
|
||||||
export type isDirtyTypes = boolean | string | number | DataModels.IndexingPolicy | DataModels.ComputedProperties;
|
export type isDirtyTypes =
|
||||||
|
| boolean
|
||||||
|
| string
|
||||||
|
| number
|
||||||
|
| DataModels.IndexingPolicy
|
||||||
|
| DataModels.ComputedProperties
|
||||||
|
| DataModels.VectorEmbedding[]
|
||||||
|
| DataModels.FullTextPolicy;
|
||||||
export const TtlOff = "off";
|
export const TtlOff = "off";
|
||||||
export const TtlOn = "on";
|
export const TtlOn = "on";
|
||||||
export const TtlOnNoDefault = "on-nodefault";
|
export const TtlOnNoDefault = "on-nodefault";
|
||||||
@ -50,6 +57,11 @@ export enum SettingsV2TabTypes {
|
|||||||
ContainerVectorPolicyTab,
|
ContainerVectorPolicyTab,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum ContainerPolicyTabTypes {
|
||||||
|
VectorPolicyTab,
|
||||||
|
FullTextPolicyTab,
|
||||||
|
}
|
||||||
|
|
||||||
export interface IsComponentDirtyResult {
|
export interface IsComponentDirtyResult {
|
||||||
isSaveable: boolean;
|
isSaveable: boolean;
|
||||||
isDiscardable: boolean;
|
isDiscardable: boolean;
|
||||||
@ -154,7 +166,7 @@ export const getTabTitle = (tab: SettingsV2TabTypes): string => {
|
|||||||
case SettingsV2TabTypes.ComputedPropertiesTab:
|
case SettingsV2TabTypes.ComputedPropertiesTab:
|
||||||
return "Computed Properties";
|
return "Computed Properties";
|
||||||
case SettingsV2TabTypes.ContainerVectorPolicyTab:
|
case SettingsV2TabTypes.ContainerVectorPolicyTab:
|
||||||
return "Container Vector Policy (preview)";
|
return "Container Policies";
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unknown tab ${tab}`);
|
throw new Error(`Unknown tab ${tab}`);
|
||||||
}
|
}
|
||||||
|
@ -46,6 +46,8 @@ export const collection = {
|
|||||||
query: "query",
|
query: "query",
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
|
vectorEmbeddingPolicy: ko.observable<DataModels.VectorEmbeddingPolicy>({} as DataModels.VectorEmbeddingPolicy),
|
||||||
|
fullTextPolicy: ko.observable<DataModels.FullTextPolicy>({} as DataModels.FullTextPolicy),
|
||||||
readSettings: () => {
|
readSettings: () => {
|
||||||
return;
|
return;
|
||||||
},
|
},
|
||||||
|
@ -55,6 +55,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
},
|
},
|
||||||
"databaseId": "test",
|
"databaseId": "test",
|
||||||
"defaultTtl": [Function],
|
"defaultTtl": [Function],
|
||||||
|
"fullTextPolicy": [Function],
|
||||||
"geospatialConfig": [Function],
|
"geospatialConfig": [Function],
|
||||||
"getDatabase": [Function],
|
"getDatabase": [Function],
|
||||||
"id": [Function],
|
"id": [Function],
|
||||||
@ -71,6 +72,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"readSettings": [Function],
|
"readSettings": [Function],
|
||||||
"uniqueKeyPolicy": {},
|
"uniqueKeyPolicy": {},
|
||||||
"usageSizeInKB": [Function],
|
"usageSizeInKB": [Function],
|
||||||
|
"vectorEmbeddingPolicy": [Function],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
isAutoPilotSelected={false}
|
isAutoPilotSelected={false}
|
||||||
@ -132,6 +134,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
},
|
},
|
||||||
"databaseId": "test",
|
"databaseId": "test",
|
||||||
"defaultTtl": [Function],
|
"defaultTtl": [Function],
|
||||||
|
"fullTextPolicy": [Function],
|
||||||
"geospatialConfig": [Function],
|
"geospatialConfig": [Function],
|
||||||
"getDatabase": [Function],
|
"getDatabase": [Function],
|
||||||
"id": [Function],
|
"id": [Function],
|
||||||
@ -148,6 +151,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"readSettings": [Function],
|
"readSettings": [Function],
|
||||||
"uniqueKeyPolicy": {},
|
"uniqueKeyPolicy": {},
|
||||||
"usageSizeInKB": [Function],
|
"usageSizeInKB": [Function],
|
||||||
|
"vectorEmbeddingPolicy": [Function],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
displayedTtlSeconds="5"
|
displayedTtlSeconds="5"
|
||||||
@ -249,6 +253,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
},
|
},
|
||||||
"databaseId": "test",
|
"databaseId": "test",
|
||||||
"defaultTtl": [Function],
|
"defaultTtl": [Function],
|
||||||
|
"fullTextPolicy": [Function],
|
||||||
"geospatialConfig": [Function],
|
"geospatialConfig": [Function],
|
||||||
"getDatabase": [Function],
|
"getDatabase": [Function],
|
||||||
"id": [Function],
|
"id": [Function],
|
||||||
@ -265,6 +270,7 @@ exports[`SettingsComponent renders 1`] = `
|
|||||||
"readSettings": [Function],
|
"readSettings": [Function],
|
||||||
"uniqueKeyPolicy": {},
|
"uniqueKeyPolicy": {},
|
||||||
"usageSizeInKB": [Function],
|
"usageSizeInKB": [Function],
|
||||||
|
"vectorEmbeddingPolicy": [Function],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
explorer={
|
explorer={
|
||||||
|
@ -2,7 +2,7 @@ import "@testing-library/jest-dom";
|
|||||||
import { RenderResult, fireEvent, render, screen, waitFor } from "@testing-library/react";
|
import { RenderResult, fireEvent, render, screen, waitFor } from "@testing-library/react";
|
||||||
import { VectorEmbedding, VectorIndex } from "Contracts/DataModels";
|
import { VectorEmbedding, VectorIndex } from "Contracts/DataModels";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { AddVectorEmbeddingPolicyForm } from "./AddVectorEmbeddingPolicyForm";
|
import { VectorEmbeddingPoliciesComponent } from "./VectorEmbeddingPoliciesComponent";
|
||||||
|
|
||||||
const mockVectorEmbedding: VectorEmbedding[] = [
|
const mockVectorEmbedding: VectorEmbedding[] = [
|
||||||
{ path: "/vector1", dataType: "float32", distanceFunction: "euclidean", dimensions: 0 },
|
{ path: "/vector1", dataType: "float32", distanceFunction: "euclidean", dimensions: 0 },
|
||||||
@ -17,9 +17,9 @@ describe("AddVectorEmbeddingPolicyForm", () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
component = render(
|
component = render(
|
||||||
<AddVectorEmbeddingPolicyForm
|
<VectorEmbeddingPoliciesComponent
|
||||||
vectorEmbedding={mockVectorEmbedding}
|
vectorEmbeddings={mockVectorEmbedding}
|
||||||
vectorIndex={mockVectorIndex}
|
vectorIndexes={mockVectorIndex}
|
||||||
onVectorEmbeddingChange={mockOnVectorEmbeddingChange}
|
onVectorEmbeddingChange={mockOnVectorEmbeddingChange}
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
@ -36,7 +36,7 @@ describe("AddVectorEmbeddingPolicyForm", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("calls onDelete when delete button is clicked", async () => {
|
test("calls onDelete when delete button is clicked", async () => {
|
||||||
const deleteButton = component.container.querySelector("#delete-vector-policy-1");
|
const deleteButton = component.container.querySelector("#delete-Vector-embedding-1");
|
||||||
fireEvent.click(deleteButton);
|
fireEvent.click(deleteButton);
|
||||||
expect(mockOnVectorEmbeddingChange).toHaveBeenCalled();
|
expect(mockOnVectorEmbeddingChange).toHaveBeenCalled();
|
||||||
expect(screen.queryByText("Vector embedding 1")).toBeNull();
|
expect(screen.queryByText("Vector embedding 1")).toBeNull();
|
||||||
@ -49,21 +49,19 @@ describe("AddVectorEmbeddingPolicyForm", () => {
|
|||||||
|
|
||||||
test("validates input correctly", async () => {
|
test("validates input correctly", async () => {
|
||||||
fireEvent.change(screen.getByPlaceholderText("/vector1"), { target: { value: "" } });
|
fireEvent.change(screen.getByPlaceholderText("/vector1"), { target: { value: "" } });
|
||||||
await waitFor(() => expect(screen.getByText("Vector embedding path should not be empty")).toBeInTheDocument(), {
|
await waitFor(() => expect(screen.getByText("Path should not be empty")).toBeInTheDocument(), {
|
||||||
timeout: 1500,
|
timeout: 1500,
|
||||||
});
|
});
|
||||||
await waitFor(
|
await waitFor(
|
||||||
() =>
|
() =>
|
||||||
expect(
|
expect(screen.getByText("Dimension must be greater than 0 and less than or equal 4096")).toBeInTheDocument(),
|
||||||
screen.getByText("Vector embedding dimension must be greater than 0 and less than or equal 4096"),
|
|
||||||
).toBeInTheDocument(),
|
|
||||||
{
|
{
|
||||||
timeout: 1500,
|
timeout: 1500,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
fireEvent.change(component.container.querySelector("#vector-policy-dimension-1"), { target: { value: "4096" } });
|
fireEvent.change(component.container.querySelector("#vector-policy-dimension-1"), { target: { value: "4096" } });
|
||||||
fireEvent.change(screen.getByPlaceholderText("/vector1"), { target: { value: "/vector1" } });
|
fireEvent.change(screen.getByPlaceholderText("/vector1"), { target: { value: "/vector1" } });
|
||||||
await waitFor(() => expect(screen.queryByText("Vector embedding path should not be empty")).toBeNull(), {
|
await waitFor(() => expect(screen.queryByText("Path should not be empty")).toBeNull(), {
|
||||||
timeout: 1500,
|
timeout: 1500,
|
||||||
});
|
});
|
||||||
await waitFor(
|
await waitFor(
|
@ -0,0 +1,470 @@
|
|||||||
|
import {
|
||||||
|
DefaultButton,
|
||||||
|
Dropdown,
|
||||||
|
IDropdownOption,
|
||||||
|
IStyleFunctionOrObject,
|
||||||
|
ITextFieldStyleProps,
|
||||||
|
ITextFieldStyles,
|
||||||
|
Label,
|
||||||
|
Stack,
|
||||||
|
TextField,
|
||||||
|
} from "@fluentui/react";
|
||||||
|
import { VectorEmbedding, VectorIndex } from "Contracts/DataModels";
|
||||||
|
import { CollapsibleSectionComponent } from "Explorer/Controls/CollapsiblePanel/CollapsibleSectionComponent";
|
||||||
|
import {
|
||||||
|
getDataTypeOptions,
|
||||||
|
getDistanceFunctionOptions,
|
||||||
|
getIndexTypeOptions,
|
||||||
|
} from "Explorer/Controls/VectorSearch/VectorSearchUtils";
|
||||||
|
import React, { FunctionComponent, useState } from "react";
|
||||||
|
|
||||||
|
export interface IVectorEmbeddingPoliciesComponentProps {
|
||||||
|
vectorEmbeddings: VectorEmbedding[];
|
||||||
|
onVectorEmbeddingChange: (
|
||||||
|
vectorEmbeddings: VectorEmbedding[],
|
||||||
|
vectorIndexingPolicies: VectorIndex[],
|
||||||
|
validationPassed: boolean,
|
||||||
|
) => void;
|
||||||
|
vectorIndexes?: VectorIndex[];
|
||||||
|
discardChanges?: boolean;
|
||||||
|
onChangesDiscarded?: () => void;
|
||||||
|
disabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface VectorEmbeddingPolicyData {
|
||||||
|
path: string;
|
||||||
|
dataType: VectorEmbedding["dataType"];
|
||||||
|
distanceFunction: VectorEmbedding["distanceFunction"];
|
||||||
|
dimensions: number;
|
||||||
|
indexType: VectorIndex["type"] | "none";
|
||||||
|
pathError: string;
|
||||||
|
dimensionsError: string;
|
||||||
|
diskANNShardKey?: string;
|
||||||
|
diskANNShardKeyError?: string;
|
||||||
|
indexingSearchListSize?: number;
|
||||||
|
indexingSearchListSizeError?: string;
|
||||||
|
quantizationByteSize?: number;
|
||||||
|
quantizationByteSizeError?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type VectorEmbeddingPolicyProperty = "dataType" | "distanceFunction" | "indexType";
|
||||||
|
|
||||||
|
const labelStyles = {
|
||||||
|
root: {
|
||||||
|
fontSize: 12,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const textFieldStyles: IStyleFunctionOrObject<ITextFieldStyleProps, ITextFieldStyles> = {
|
||||||
|
fieldGroup: {
|
||||||
|
height: 27,
|
||||||
|
},
|
||||||
|
field: {
|
||||||
|
fontSize: 12,
|
||||||
|
padding: "0 8px",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const dropdownStyles = {
|
||||||
|
title: {
|
||||||
|
height: 27,
|
||||||
|
lineHeight: "24px",
|
||||||
|
fontSize: 12,
|
||||||
|
},
|
||||||
|
dropdown: {
|
||||||
|
height: 27,
|
||||||
|
lineHeight: "24px",
|
||||||
|
},
|
||||||
|
dropdownItem: {
|
||||||
|
fontSize: 12,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const VectorEmbeddingPoliciesComponent: FunctionComponent<IVectorEmbeddingPoliciesComponentProps> = ({
|
||||||
|
vectorEmbeddings,
|
||||||
|
vectorIndexes,
|
||||||
|
onVectorEmbeddingChange,
|
||||||
|
discardChanges,
|
||||||
|
onChangesDiscarded,
|
||||||
|
disabled,
|
||||||
|
}): JSX.Element => {
|
||||||
|
const onVectorEmbeddingPathError = (path: string, index?: number): string => {
|
||||||
|
let error = "";
|
||||||
|
if (!path) {
|
||||||
|
error = "Path should not be empty";
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
index >= 0 &&
|
||||||
|
vectorEmbeddingPolicyData?.find(
|
||||||
|
(vectorEmbedding: VectorEmbeddingPolicyData, dataIndex: number) =>
|
||||||
|
dataIndex !== index && vectorEmbedding.path === path,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
error = "Path is already defined";
|
||||||
|
}
|
||||||
|
return error;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onVectorEmbeddingDimensionError = (dimension: number, indexType: VectorIndex["type"] | "none"): string => {
|
||||||
|
let error = "";
|
||||||
|
if (dimension <= 0 || dimension > 4096) {
|
||||||
|
error = "Dimension must be greater than 0 and less than or equal 4096";
|
||||||
|
}
|
||||||
|
if (indexType === "flat" && dimension > 505) {
|
||||||
|
error = "Maximum allowed dimension for flat index is 505";
|
||||||
|
}
|
||||||
|
return error;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onQuantizationByteSizeError = (size: number): string => {
|
||||||
|
let error = "";
|
||||||
|
if (size < 1 || size > 512) {
|
||||||
|
error = "Quantization byte size must be greater than 0 and less than or equal to 512";
|
||||||
|
}
|
||||||
|
return error;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onIndexingSearchListSizeError = (size: number): string => {
|
||||||
|
let error = "";
|
||||||
|
if (size < 25 || size > 500) {
|
||||||
|
error = "Indexing search list size must be greater than or equal to 25 and less than or equal to 500";
|
||||||
|
}
|
||||||
|
return error;
|
||||||
|
};
|
||||||
|
|
||||||
|
//TODO: no restrictions yet due to this field being removed for now.
|
||||||
|
// Uncomment and replace with validation code when field is reinstated
|
||||||
|
// const onDiskANNShardKeyError = (shardKey: string): string => {
|
||||||
|
// return "";
|
||||||
|
// };
|
||||||
|
|
||||||
|
const initializeData = (vectorEmbeddings: VectorEmbedding[], vectorIndexes: VectorIndex[]) => {
|
||||||
|
const mergedData: VectorEmbeddingPolicyData[] = [];
|
||||||
|
vectorEmbeddings.forEach((embedding) => {
|
||||||
|
const matchingIndex = displayIndexes ? vectorIndexes.find((index) => index.path === embedding.path) : undefined;
|
||||||
|
mergedData.push({
|
||||||
|
...embedding,
|
||||||
|
indexType: matchingIndex?.type || "none",
|
||||||
|
indexingSearchListSize: matchingIndex?.indexingSearchListSize || undefined,
|
||||||
|
quantizationByteSize: matchingIndex?.quantizationByteSize || undefined,
|
||||||
|
pathError: onVectorEmbeddingPathError(embedding.path),
|
||||||
|
dimensionsError: onVectorEmbeddingDimensionError(embedding.dimensions, matchingIndex?.type || "none"),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return mergedData;
|
||||||
|
};
|
||||||
|
|
||||||
|
const [displayIndexes] = useState<boolean>(!!vectorIndexes);
|
||||||
|
const [vectorEmbeddingPolicyData, setVectorEmbeddingPolicyData] = useState<VectorEmbeddingPolicyData[]>(
|
||||||
|
initializeData(vectorEmbeddings, vectorIndexes),
|
||||||
|
);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
propagateData();
|
||||||
|
}, [vectorEmbeddingPolicyData]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (discardChanges) {
|
||||||
|
setVectorEmbeddingPolicyData(initializeData(vectorEmbeddings, vectorIndexes));
|
||||||
|
onChangesDiscarded();
|
||||||
|
}
|
||||||
|
}, [discardChanges]);
|
||||||
|
|
||||||
|
const propagateData = () => {
|
||||||
|
const vectorEmbeddings: VectorEmbedding[] = vectorEmbeddingPolicyData.map((policy: VectorEmbeddingPolicyData) => ({
|
||||||
|
path: policy.path,
|
||||||
|
dataType: policy.dataType,
|
||||||
|
dimensions: policy.dimensions,
|
||||||
|
distanceFunction: policy.distanceFunction,
|
||||||
|
}));
|
||||||
|
const vectorIndexes: VectorIndex[] = vectorEmbeddingPolicyData
|
||||||
|
.filter((policy: VectorEmbeddingPolicyData) => policy.indexType !== "none")
|
||||||
|
.map(
|
||||||
|
(policy) =>
|
||||||
|
({
|
||||||
|
path: policy.path,
|
||||||
|
type: policy.indexType,
|
||||||
|
indexingSearchListSize: policy.indexingSearchListSize,
|
||||||
|
quantizationByteSize: policy.quantizationByteSize,
|
||||||
|
}) as VectorIndex,
|
||||||
|
);
|
||||||
|
const validationPassed = vectorEmbeddingPolicyData.every(
|
||||||
|
(policy: VectorEmbeddingPolicyData) => policy.pathError === "" && policy.dimensionsError === "",
|
||||||
|
);
|
||||||
|
onVectorEmbeddingChange(vectorEmbeddings, vectorIndexes, validationPassed);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onVectorEmbeddingPathChange = (index: number, event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const value = event.target.value.trim();
|
||||||
|
const vectorEmbeddings = [...vectorEmbeddingPolicyData];
|
||||||
|
if (!vectorEmbeddings[index]?.path && !value.startsWith("/")) {
|
||||||
|
vectorEmbeddings[index].path = "/" + value;
|
||||||
|
} else {
|
||||||
|
vectorEmbeddings[index].path = value;
|
||||||
|
}
|
||||||
|
const error = onVectorEmbeddingPathError(value, index);
|
||||||
|
vectorEmbeddings[index].pathError = error;
|
||||||
|
setVectorEmbeddingPolicyData(vectorEmbeddings);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onVectorEmbeddingDimensionsChange = (index: number, event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const value = parseInt(event.target.value.trim()) || 0;
|
||||||
|
const vectorEmbeddings = [...vectorEmbeddingPolicyData];
|
||||||
|
const vectorEmbedding = vectorEmbeddings[index];
|
||||||
|
vectorEmbeddings[index].dimensions = value;
|
||||||
|
const error = onVectorEmbeddingDimensionError(value, vectorEmbedding.indexType);
|
||||||
|
vectorEmbeddings[index].dimensionsError = error;
|
||||||
|
setVectorEmbeddingPolicyData(vectorEmbeddings);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onVectorEmbeddingIndexTypeChange = (index: number, option: IDropdownOption): void => {
|
||||||
|
const vectorEmbeddings = [...vectorEmbeddingPolicyData];
|
||||||
|
const vectorEmbedding = vectorEmbeddings[index];
|
||||||
|
vectorEmbeddings[index].indexType = option.key as never;
|
||||||
|
const error = onVectorEmbeddingDimensionError(vectorEmbedding.dimensions, vectorEmbedding.indexType);
|
||||||
|
vectorEmbeddings[index].dimensionsError = error;
|
||||||
|
if (vectorEmbedding.indexType === "diskANN") {
|
||||||
|
vectorEmbedding.indexingSearchListSize = 100;
|
||||||
|
} else {
|
||||||
|
vectorEmbedding.indexingSearchListSize = undefined;
|
||||||
|
}
|
||||||
|
setVectorEmbeddingPolicyData(vectorEmbeddings);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onQuantizationByteSizeChange = (index: number, event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const value = parseInt(event.target.value.trim()) || 0;
|
||||||
|
const vectorEmbeddings = [...vectorEmbeddingPolicyData];
|
||||||
|
vectorEmbeddings[index].quantizationByteSize = value;
|
||||||
|
vectorEmbeddings[index].quantizationByteSizeError = onQuantizationByteSizeError(value);
|
||||||
|
setVectorEmbeddingPolicyData(vectorEmbeddings);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onIndexingSearchListSizeChange = (index: number, event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const value = parseInt(event.target.value.trim()) || 0;
|
||||||
|
const vectorEmbeddings = [...vectorEmbeddingPolicyData];
|
||||||
|
vectorEmbeddings[index].indexingSearchListSize = value;
|
||||||
|
vectorEmbeddings[index].indexingSearchListSizeError = onIndexingSearchListSizeError(value);
|
||||||
|
setVectorEmbeddingPolicyData(vectorEmbeddings);
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: uncomment after Ignite
|
||||||
|
// DiskANNShardKey was removed for Ignite due to backend problems. Leaving this here as it will be reinstated immediately after Ignite
|
||||||
|
// const onDiskANNShardKeyChange = (index: number, event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
// const value = event.target.value.trim();
|
||||||
|
// const vectorEmbeddings = [...vectorEmbeddingPolicyData];
|
||||||
|
// if (!vectorEmbeddings[index]?.diskANNShardKey && !value.startsWith("/")) {
|
||||||
|
// vectorEmbeddings[index].diskANNShardKey = "/" + value;
|
||||||
|
// } else {
|
||||||
|
// vectorEmbeddings[index].diskANNShardKey = value;
|
||||||
|
// }
|
||||||
|
// const error = onDiskANNShardKeyError(value);
|
||||||
|
// vectorEmbeddings[index].diskANNShardKeyError = error;
|
||||||
|
// setVectorEmbeddingPolicyData(vectorEmbeddings);
|
||||||
|
// }
|
||||||
|
|
||||||
|
const onVectorEmbeddingPolicyChange = (
|
||||||
|
index: number,
|
||||||
|
option: IDropdownOption,
|
||||||
|
property: VectorEmbeddingPolicyProperty,
|
||||||
|
): void => {
|
||||||
|
const vectorEmbeddings = [...vectorEmbeddingPolicyData];
|
||||||
|
vectorEmbeddings[index][property] = option.key as never;
|
||||||
|
setVectorEmbeddingPolicyData(vectorEmbeddings);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onAdd = () => {
|
||||||
|
setVectorEmbeddingPolicyData([
|
||||||
|
...vectorEmbeddingPolicyData,
|
||||||
|
{
|
||||||
|
path: "",
|
||||||
|
dataType: "float32",
|
||||||
|
distanceFunction: "euclidean",
|
||||||
|
dimensions: 0,
|
||||||
|
indexType: "none",
|
||||||
|
pathError: onVectorEmbeddingPathError(""),
|
||||||
|
dimensionsError: onVectorEmbeddingDimensionError(0, "none"),
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDelete = (index: number) => {
|
||||||
|
const vectorEmbeddings = vectorEmbeddingPolicyData.filter((_uniqueKey, j) => index !== j);
|
||||||
|
setVectorEmbeddingPolicyData(vectorEmbeddings);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack tokens={{ childrenGap: 4 }}>
|
||||||
|
{vectorEmbeddingPolicyData &&
|
||||||
|
vectorEmbeddingPolicyData.length > 0 &&
|
||||||
|
vectorEmbeddingPolicyData.map((vectorEmbeddingPolicy: VectorEmbeddingPolicyData, index: number) => (
|
||||||
|
<CollapsibleSectionComponent
|
||||||
|
disabled={disabled}
|
||||||
|
key={index}
|
||||||
|
isExpandedByDefault={true}
|
||||||
|
title={`Vector embedding ${index + 1}`}
|
||||||
|
showDelete={true}
|
||||||
|
onDelete={() => onDelete(index)}
|
||||||
|
>
|
||||||
|
<Stack horizontal tokens={{ childrenGap: 4 }}>
|
||||||
|
<Stack
|
||||||
|
styles={{
|
||||||
|
root: {
|
||||||
|
margin: "0 0 6px 20px !important",
|
||||||
|
paddingLeft: 20,
|
||||||
|
width: "80%",
|
||||||
|
borderLeft: "1px solid",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Stack>
|
||||||
|
<Label disabled={disabled} styles={labelStyles}>
|
||||||
|
Path
|
||||||
|
</Label>
|
||||||
|
<TextField
|
||||||
|
disabled={disabled}
|
||||||
|
id={`vector-policy-path-${index + 1}`}
|
||||||
|
required={true}
|
||||||
|
placeholder="/vector1"
|
||||||
|
styles={textFieldStyles}
|
||||||
|
onChange={(event: React.ChangeEvent<HTMLInputElement>) => onVectorEmbeddingPathChange(index, event)}
|
||||||
|
value={vectorEmbeddingPolicy.path || ""}
|
||||||
|
errorMessage={vectorEmbeddingPolicy.pathError}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
<Stack>
|
||||||
|
<Label disabled={disabled} styles={labelStyles}>
|
||||||
|
Data type
|
||||||
|
</Label>
|
||||||
|
<Dropdown
|
||||||
|
disabled={disabled}
|
||||||
|
required={true}
|
||||||
|
styles={dropdownStyles}
|
||||||
|
options={getDataTypeOptions()}
|
||||||
|
selectedKey={vectorEmbeddingPolicy.dataType}
|
||||||
|
onChange={(_event: React.FormEvent<HTMLDivElement>, option: IDropdownOption) =>
|
||||||
|
onVectorEmbeddingPolicyChange(index, option, "dataType")
|
||||||
|
}
|
||||||
|
></Dropdown>
|
||||||
|
</Stack>
|
||||||
|
<Stack>
|
||||||
|
<Label disabled={disabled} styles={labelStyles}>
|
||||||
|
Distance function
|
||||||
|
</Label>
|
||||||
|
<Dropdown
|
||||||
|
disabled={disabled}
|
||||||
|
required={true}
|
||||||
|
styles={dropdownStyles}
|
||||||
|
options={getDistanceFunctionOptions()}
|
||||||
|
selectedKey={vectorEmbeddingPolicy.distanceFunction}
|
||||||
|
onChange={(_event: React.FormEvent<HTMLDivElement>, option: IDropdownOption) =>
|
||||||
|
onVectorEmbeddingPolicyChange(index, option, "distanceFunction")
|
||||||
|
}
|
||||||
|
></Dropdown>
|
||||||
|
</Stack>
|
||||||
|
<Stack>
|
||||||
|
<Label disabled={disabled} styles={labelStyles}>
|
||||||
|
Dimensions
|
||||||
|
</Label>
|
||||||
|
<TextField
|
||||||
|
disabled={disabled}
|
||||||
|
id={`vector-policy-dimension-${index + 1}`}
|
||||||
|
required={true}
|
||||||
|
styles={textFieldStyles}
|
||||||
|
value={String(vectorEmbeddingPolicy.dimensions || 0)}
|
||||||
|
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||||
|
onVectorEmbeddingDimensionsChange(index, event)
|
||||||
|
}
|
||||||
|
errorMessage={vectorEmbeddingPolicy.dimensionsError}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
{displayIndexes && (
|
||||||
|
<Stack>
|
||||||
|
<Label disabled={disabled} styles={labelStyles}>
|
||||||
|
Index type
|
||||||
|
</Label>
|
||||||
|
<Dropdown
|
||||||
|
disabled={disabled}
|
||||||
|
required={true}
|
||||||
|
styles={dropdownStyles}
|
||||||
|
options={getIndexTypeOptions()}
|
||||||
|
selectedKey={vectorEmbeddingPolicy.indexType}
|
||||||
|
onChange={(_event: React.FormEvent<HTMLDivElement>, option: IDropdownOption) =>
|
||||||
|
onVectorEmbeddingIndexTypeChange(index, option)
|
||||||
|
}
|
||||||
|
></Dropdown>
|
||||||
|
<Stack style={{ marginLeft: "10px" }}>
|
||||||
|
<Label
|
||||||
|
disabled={
|
||||||
|
disabled ||
|
||||||
|
(vectorEmbeddingPolicy.indexType !== "quantizedFlat" &&
|
||||||
|
vectorEmbeddingPolicy.indexType !== "diskANN")
|
||||||
|
}
|
||||||
|
styles={labelStyles}
|
||||||
|
>
|
||||||
|
Quantization byte size
|
||||||
|
</Label>
|
||||||
|
<TextField
|
||||||
|
disabled={
|
||||||
|
disabled ||
|
||||||
|
(vectorEmbeddingPolicy.indexType !== "quantizedFlat" &&
|
||||||
|
vectorEmbeddingPolicy.indexType !== "diskANN")
|
||||||
|
}
|
||||||
|
id={`vector-policy-quantizationByteSize-${index + 1}`}
|
||||||
|
styles={textFieldStyles}
|
||||||
|
value={String(vectorEmbeddingPolicy.quantizationByteSize || "")}
|
||||||
|
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||||
|
onQuantizationByteSizeChange(index, event)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
<Stack style={{ marginLeft: "10px" }}>
|
||||||
|
<Label disabled={disabled || vectorEmbeddingPolicy.indexType !== "diskANN"} styles={labelStyles}>
|
||||||
|
Indexing search list size
|
||||||
|
</Label>
|
||||||
|
<TextField
|
||||||
|
disabled={disabled || vectorEmbeddingPolicy.indexType !== "diskANN"}
|
||||||
|
id={`vector-policy-indexingSearchListSize-${index + 1}`}
|
||||||
|
styles={textFieldStyles}
|
||||||
|
value={String(vectorEmbeddingPolicy.indexingSearchListSize || "")}
|
||||||
|
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||||
|
onIndexingSearchListSizeChange(index, event)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
{/*TODO: uncomment after Ignite */}
|
||||||
|
{/* DiskANNShardKey was removed for Ignite due to backend problems. Leaving this here as it will be reinstated immediately after Ignite
|
||||||
|
<Stack
|
||||||
|
style={{ marginLeft: "10px" }}
|
||||||
|
>
|
||||||
|
<Label
|
||||||
|
disabled={disabled || vectorEmbeddingPolicy.indexType !== "diskANN"}
|
||||||
|
styles={labelStyles}
|
||||||
|
>DiskANN shard key</Label>
|
||||||
|
<TextField
|
||||||
|
disabled={disabled || vectorEmbeddingPolicy.indexType !== "diskANN"}
|
||||||
|
id={`vector-policy-diskANNShardKey-${index + 1}`}
|
||||||
|
styles={textFieldStyles}
|
||||||
|
value={String(vectorEmbeddingPolicy.diskANNShardKey || "")}
|
||||||
|
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||||
|
onDiskANNShardKeyChange(index, event)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
*/}
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
</CollapsibleSectionComponent>
|
||||||
|
))}
|
||||||
|
<DefaultButton
|
||||||
|
disabled={disabled}
|
||||||
|
id={`add-vector-policy`}
|
||||||
|
styles={{ root: { maxWidth: 170, fontSize: 12 } }}
|
||||||
|
onClick={onAdd}
|
||||||
|
>
|
||||||
|
Add vector embedding
|
||||||
|
</DefaultButton>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
@ -21,7 +21,11 @@ import { getNewDatabaseSharedThroughputDefault } from "Common/DatabaseUtility";
|
|||||||
import { getErrorMessage, getErrorStack } from "Common/ErrorHandlingUtils";
|
import { getErrorMessage, getErrorStack } from "Common/ErrorHandlingUtils";
|
||||||
import { configContext, Platform } from "ConfigContext";
|
import { configContext, Platform } from "ConfigContext";
|
||||||
import * as DataModels from "Contracts/DataModels";
|
import * as DataModels from "Contracts/DataModels";
|
||||||
import { AddVectorEmbeddingPolicyForm } from "Explorer/Panes/VectorSearchPanel/AddVectorEmbeddingPolicyForm";
|
import {
|
||||||
|
FullTextPoliciesComponent,
|
||||||
|
getFullTextLanguageOptions,
|
||||||
|
} from "Explorer/Controls/FullTextSeach/FullTextPoliciesComponent";
|
||||||
|
import { VectorEmbeddingPoliciesComponent } from "Explorer/Controls/VectorSearch/VectorEmbeddingPoliciesComponent";
|
||||||
import { useSidePanel } from "hooks/useSidePanel";
|
import { useSidePanel } from "hooks/useSidePanel";
|
||||||
import { useTeachingBubble } from "hooks/useTeachingBubble";
|
import { useTeachingBubble } from "hooks/useTeachingBubble";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
@ -30,7 +34,12 @@ import { Action } from "Shared/Telemetry/TelemetryConstants";
|
|||||||
import * as TelemetryProcessor from "Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "Shared/Telemetry/TelemetryProcessor";
|
||||||
import { userContext } from "UserContext";
|
import { userContext } from "UserContext";
|
||||||
import { getCollectionName } from "Utils/APITypeUtils";
|
import { getCollectionName } from "Utils/APITypeUtils";
|
||||||
import { isCapabilityEnabled, isServerlessAccount, isVectorSearchEnabled } from "Utils/CapabilityUtils";
|
import {
|
||||||
|
isCapabilityEnabled,
|
||||||
|
isFullTextSearchEnabled,
|
||||||
|
isServerlessAccount,
|
||||||
|
isVectorSearchEnabled,
|
||||||
|
} from "Utils/CapabilityUtils";
|
||||||
import { getUpsellMessage } from "Utils/PricingUtils";
|
import { getUpsellMessage } from "Utils/PricingUtils";
|
||||||
import { CollapsibleSectionComponent } from "../Controls/CollapsiblePanel/CollapsibleSectionComponent";
|
import { CollapsibleSectionComponent } from "../Controls/CollapsiblePanel/CollapsibleSectionComponent";
|
||||||
import { ThroughputInput } from "../Controls/ThroughputInput/ThroughputInput";
|
import { ThroughputInput } from "../Controls/ThroughputInput/ThroughputInput";
|
||||||
@ -109,6 +118,9 @@ export interface AddCollectionPanelState {
|
|||||||
vectorIndexingPolicy: DataModels.VectorIndex[];
|
vectorIndexingPolicy: DataModels.VectorIndex[];
|
||||||
vectorEmbeddingPolicy: DataModels.VectorEmbedding[];
|
vectorEmbeddingPolicy: DataModels.VectorEmbedding[];
|
||||||
vectorPolicyValidated: boolean;
|
vectorPolicyValidated: boolean;
|
||||||
|
fullTextPolicy: DataModels.FullTextPolicy;
|
||||||
|
fullTextIndexes: DataModels.FullTextIndex[];
|
||||||
|
fullTextPolicyValidated: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AddCollectionPanel extends React.Component<AddCollectionPanelProps, AddCollectionPanelState> {
|
export class AddCollectionPanel extends React.Component<AddCollectionPanelProps, AddCollectionPanelState> {
|
||||||
@ -147,6 +159,9 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
vectorEmbeddingPolicy: [],
|
vectorEmbeddingPolicy: [],
|
||||||
vectorIndexingPolicy: [],
|
vectorIndexingPolicy: [],
|
||||||
vectorPolicyValidated: true,
|
vectorPolicyValidated: true,
|
||||||
|
fullTextPolicy: { defaultLanguage: getFullTextLanguageOptions()[0].key as never, fullTextPaths: [] },
|
||||||
|
fullTextIndexes: [],
|
||||||
|
fullTextPolicyValidated: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -890,9 +905,9 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
>
|
>
|
||||||
<Stack id="collapsibleVectorPolicySectionContent" styles={{ root: { position: "relative" } }}>
|
<Stack id="collapsibleVectorPolicySectionContent" styles={{ root: { position: "relative" } }}>
|
||||||
<Stack styles={{ root: { paddingLeft: 40 } }}>
|
<Stack styles={{ root: { paddingLeft: 40 } }}>
|
||||||
<AddVectorEmbeddingPolicyForm
|
<VectorEmbeddingPoliciesComponent
|
||||||
vectorEmbedding={this.state.vectorEmbeddingPolicy}
|
vectorEmbeddings={this.state.vectorEmbeddingPolicy}
|
||||||
vectorIndex={this.state.vectorIndexingPolicy}
|
vectorIndexes={this.state.vectorIndexingPolicy}
|
||||||
onVectorEmbeddingChange={(
|
onVectorEmbeddingChange={(
|
||||||
vectorEmbeddingPolicy: DataModels.VectorEmbedding[],
|
vectorEmbeddingPolicy: DataModels.VectorEmbedding[],
|
||||||
vectorIndexingPolicy: DataModels.VectorIndex[],
|
vectorIndexingPolicy: DataModels.VectorIndex[],
|
||||||
@ -906,6 +921,34 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
</CollapsibleSectionComponent>
|
</CollapsibleSectionComponent>
|
||||||
</Stack>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
|
{this.shouldShowFullTextSearchParameters() && (
|
||||||
|
<Stack>
|
||||||
|
<CollapsibleSectionComponent
|
||||||
|
title="Container Full Text Search Policy"
|
||||||
|
isExpandedByDefault={false}
|
||||||
|
onExpand={() => {
|
||||||
|
this.scrollToSection("collapsibleFullTextPolicySectionContent");
|
||||||
|
}}
|
||||||
|
//TODO: uncomment when learn more text becomes available
|
||||||
|
// tooltipContent={this.getContainerFullTextPolicyTooltipContent()}
|
||||||
|
>
|
||||||
|
<Stack id="collapsibleFullTextPolicySectionContent" styles={{ root: { position: "relative" } }}>
|
||||||
|
<Stack styles={{ root: { paddingLeft: 40 } }}>
|
||||||
|
<FullTextPoliciesComponent
|
||||||
|
fullTextPolicy={this.state.fullTextPolicy}
|
||||||
|
onFullTextPathChange={(
|
||||||
|
fullTextPolicy: DataModels.FullTextPolicy,
|
||||||
|
fullTextIndexes: DataModels.FullTextIndex[],
|
||||||
|
fullTextPolicyValidated: boolean,
|
||||||
|
) => {
|
||||||
|
this.setState({ fullTextPolicy, fullTextIndexes, fullTextPolicyValidated });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
</CollapsibleSectionComponent>
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
{userContext.apiType !== "Tables" && (
|
{userContext.apiType !== "Tables" && (
|
||||||
<CollapsibleSectionComponent
|
<CollapsibleSectionComponent
|
||||||
title="Advanced"
|
title="Advanced"
|
||||||
@ -1211,6 +1254,19 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TODO: uncomment when learn more text becomes available
|
||||||
|
// private getContainerFullTextPolicyTooltipContent(): JSX.Element {
|
||||||
|
// return (
|
||||||
|
// <Text variant="small">
|
||||||
|
// Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore
|
||||||
|
// magna aliqua.{" "}
|
||||||
|
// <Link target="_blank" href="https://aka.ms/CosmosFullTextSearch">
|
||||||
|
// Learn more
|
||||||
|
// </Link>
|
||||||
|
// </Text>
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
private shouldShowCollectionThroughputInput(): boolean {
|
private shouldShowCollectionThroughputInput(): boolean {
|
||||||
if (isServerlessAccount()) {
|
if (isServerlessAccount()) {
|
||||||
return false;
|
return false;
|
||||||
@ -1274,6 +1330,10 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
return isVectorSearchEnabled() && (isServerlessAccount() || this.shouldShowCollectionThroughputInput());
|
return isVectorSearchEnabled() && (isServerlessAccount() || this.shouldShowCollectionThroughputInput());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private shouldShowFullTextSearchParameters() {
|
||||||
|
return isFullTextSearchEnabled() && (isServerlessAccount() || this.shouldShowCollectionThroughputInput());
|
||||||
|
}
|
||||||
|
|
||||||
private parseUniqueKeys(): DataModels.UniqueKeyPolicy {
|
private parseUniqueKeys(): DataModels.UniqueKeyPolicy {
|
||||||
if (this.state.uniqueKeys?.length === 0) {
|
if (this.state.uniqueKeys?.length === 0) {
|
||||||
return undefined;
|
return undefined;
|
||||||
@ -1330,11 +1390,18 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.shouldShowVectorSearchParameters() && !this.state.vectorPolicyValidated) {
|
if (this.shouldShowVectorSearchParameters()) {
|
||||||
|
if (!this.state.vectorPolicyValidated) {
|
||||||
this.setState({ errorMessage: "Please fix errors in container vector policy" });
|
this.setState({ errorMessage: "Please fix errors in container vector policy" });
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!this.state.fullTextPolicyValidated) {
|
||||||
|
this.setState({ errorMessage: "Please fix errors in container full text search polilcy" });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1423,6 +1490,10 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.shouldShowFullTextSearchParameters()) {
|
||||||
|
indexingPolicy.fullTextIndexes = this.state.fullTextIndexes;
|
||||||
|
}
|
||||||
|
|
||||||
const telemetryData = {
|
const telemetryData = {
|
||||||
database: {
|
database: {
|
||||||
id: databaseId,
|
id: databaseId,
|
||||||
@ -1482,6 +1553,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
|
|||||||
uniqueKeyPolicy,
|
uniqueKeyPolicy,
|
||||||
createMongoWildcardIndex: this.state.createMongoWildCardIndex,
|
createMongoWildcardIndex: this.state.createMongoWildCardIndex,
|
||||||
vectorEmbeddingPolicy,
|
vectorEmbeddingPolicy,
|
||||||
|
fullTextPolicy: this.state.fullTextPolicy,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.setState({ isExecuting: true });
|
this.setState({ isExecuting: true });
|
||||||
|
@ -1,300 +0,0 @@
|
|||||||
import {
|
|
||||||
DefaultButton,
|
|
||||||
Dropdown,
|
|
||||||
IDropdownOption,
|
|
||||||
IStyleFunctionOrObject,
|
|
||||||
ITextFieldStyleProps,
|
|
||||||
ITextFieldStyles,
|
|
||||||
IconButton,
|
|
||||||
Label,
|
|
||||||
Stack,
|
|
||||||
TextField,
|
|
||||||
} from "@fluentui/react";
|
|
||||||
import { VectorEmbedding, VectorIndex } from "Contracts/DataModels";
|
|
||||||
import { CollapsibleSectionComponent } from "Explorer/Controls/CollapsiblePanel/CollapsibleSectionComponent";
|
|
||||||
import {
|
|
||||||
getDataTypeOptions,
|
|
||||||
getDistanceFunctionOptions,
|
|
||||||
getIndexTypeOptions,
|
|
||||||
} from "Explorer/Panes/VectorSearchPanel/VectorSearchUtils";
|
|
||||||
import React, { FunctionComponent, useState } from "react";
|
|
||||||
|
|
||||||
export interface IAddVectorEmbeddingPolicyFormProps {
|
|
||||||
vectorEmbedding: VectorEmbedding[];
|
|
||||||
vectorIndex: VectorIndex[];
|
|
||||||
onVectorEmbeddingChange: (
|
|
||||||
vectorEmbeddings: VectorEmbedding[],
|
|
||||||
vectorIndexingPolicies: VectorIndex[],
|
|
||||||
validationPassed: boolean,
|
|
||||||
) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface VectorEmbeddingPolicyData {
|
|
||||||
path: string;
|
|
||||||
dataType: VectorEmbedding["dataType"];
|
|
||||||
distanceFunction: VectorEmbedding["distanceFunction"];
|
|
||||||
dimensions: number;
|
|
||||||
indexType: VectorIndex["type"] | "none";
|
|
||||||
pathError: string;
|
|
||||||
dimensionsError: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
type VectorEmbeddingPolicyProperty = "dataType" | "distanceFunction" | "indexType";
|
|
||||||
|
|
||||||
const textFieldStyles: IStyleFunctionOrObject<ITextFieldStyleProps, ITextFieldStyles> = {
|
|
||||||
fieldGroup: {
|
|
||||||
height: 27,
|
|
||||||
},
|
|
||||||
field: {
|
|
||||||
fontSize: 12,
|
|
||||||
padding: "0 8px",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const dropdownStyles = {
|
|
||||||
title: {
|
|
||||||
height: 27,
|
|
||||||
lineHeight: "24px",
|
|
||||||
fontSize: 12,
|
|
||||||
},
|
|
||||||
dropdown: {
|
|
||||||
height: 27,
|
|
||||||
lineHeight: "24px",
|
|
||||||
},
|
|
||||||
dropdownItem: {
|
|
||||||
fontSize: 12,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const AddVectorEmbeddingPolicyForm: FunctionComponent<IAddVectorEmbeddingPolicyFormProps> = ({
|
|
||||||
vectorEmbedding,
|
|
||||||
vectorIndex,
|
|
||||||
onVectorEmbeddingChange,
|
|
||||||
}): JSX.Element => {
|
|
||||||
const onVectorEmbeddingPathError = (path: string, index?: number): string => {
|
|
||||||
let error = "";
|
|
||||||
if (!path) {
|
|
||||||
error = "Vector embedding path should not be empty";
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
index >= 0 &&
|
|
||||||
vectorEmbeddingPolicyData?.find(
|
|
||||||
(vectorEmbedding: VectorEmbeddingPolicyData, dataIndex: number) =>
|
|
||||||
dataIndex !== index && vectorEmbedding.path === path,
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
error = "Vector embedding path is already defined";
|
|
||||||
}
|
|
||||||
return error;
|
|
||||||
};
|
|
||||||
|
|
||||||
const onVectorEmbeddingDimensionError = (dimension: number, indexType: VectorIndex["type"] | "none"): string => {
|
|
||||||
let error = "";
|
|
||||||
if (dimension <= 0 || dimension > 4096) {
|
|
||||||
error = "Vector embedding dimension must be greater than 0 and less than or equal 4096";
|
|
||||||
}
|
|
||||||
if (indexType === "flat" && dimension > 505) {
|
|
||||||
error = "Maximum allowed dimension for flat index is 505";
|
|
||||||
}
|
|
||||||
return error;
|
|
||||||
};
|
|
||||||
|
|
||||||
const initializeData = (vectorEmbedding: VectorEmbedding[], vectorIndex: VectorIndex[]) => {
|
|
||||||
const mergedData: VectorEmbeddingPolicyData[] = [];
|
|
||||||
vectorEmbedding.forEach((embedding) => {
|
|
||||||
const matchingIndex = vectorIndex.find((index) => index.path === embedding.path);
|
|
||||||
mergedData.push({
|
|
||||||
...embedding,
|
|
||||||
indexType: matchingIndex?.type || "none",
|
|
||||||
pathError: onVectorEmbeddingPathError(embedding.path),
|
|
||||||
dimensionsError: onVectorEmbeddingDimensionError(embedding.dimensions, matchingIndex?.type || "none"),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return mergedData;
|
|
||||||
};
|
|
||||||
|
|
||||||
const [vectorEmbeddingPolicyData, setVectorEmbeddingPolicyData] = useState<VectorEmbeddingPolicyData[]>(
|
|
||||||
initializeData(vectorEmbedding, vectorIndex),
|
|
||||||
);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
propagateData();
|
|
||||||
}, [vectorEmbeddingPolicyData]);
|
|
||||||
|
|
||||||
const propagateData = () => {
|
|
||||||
const vectorEmbeddings: VectorEmbedding[] = vectorEmbeddingPolicyData.map((policy: VectorEmbeddingPolicyData) => ({
|
|
||||||
dataType: policy.dataType,
|
|
||||||
dimensions: policy.dimensions,
|
|
||||||
distanceFunction: policy.distanceFunction,
|
|
||||||
path: policy.path,
|
|
||||||
}));
|
|
||||||
const vectorIndexingPolicies: VectorIndex[] = vectorEmbeddingPolicyData
|
|
||||||
.filter((policy: VectorEmbeddingPolicyData) => policy.indexType !== "none")
|
|
||||||
.map(
|
|
||||||
(policy) =>
|
|
||||||
({
|
|
||||||
path: policy.path,
|
|
||||||
type: policy.indexType,
|
|
||||||
}) as VectorIndex,
|
|
||||||
);
|
|
||||||
const validationPassed = vectorEmbeddingPolicyData.every(
|
|
||||||
(policy: VectorEmbeddingPolicyData) => policy.pathError === "" && policy.dimensionsError === "",
|
|
||||||
);
|
|
||||||
onVectorEmbeddingChange(vectorEmbeddings, vectorIndexingPolicies, validationPassed);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onVectorEmbeddingPathChange = (index: number, event: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
const value = event.target.value.trim();
|
|
||||||
const vectorEmbeddings = [...vectorEmbeddingPolicyData];
|
|
||||||
if (!vectorEmbeddings[index]?.path && !value.startsWith("/")) {
|
|
||||||
vectorEmbeddings[index].path = "/" + value;
|
|
||||||
} else {
|
|
||||||
vectorEmbeddings[index].path = value;
|
|
||||||
}
|
|
||||||
const error = onVectorEmbeddingPathError(value, index);
|
|
||||||
vectorEmbeddings[index].pathError = error;
|
|
||||||
setVectorEmbeddingPolicyData(vectorEmbeddings);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onVectorEmbeddingDimensionsChange = (index: number, event: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
const value = parseInt(event.target.value.trim()) || 0;
|
|
||||||
const vectorEmbeddings = [...vectorEmbeddingPolicyData];
|
|
||||||
const vectorEmbedding = vectorEmbeddings[index];
|
|
||||||
vectorEmbeddings[index].dimensions = value;
|
|
||||||
const error = onVectorEmbeddingDimensionError(value, vectorEmbedding.indexType);
|
|
||||||
vectorEmbeddings[index].dimensionsError = error;
|
|
||||||
setVectorEmbeddingPolicyData(vectorEmbeddings);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onVectorEmbeddingIndexTypeChange = (index: number, option: IDropdownOption): void => {
|
|
||||||
const vectorEmbeddings = [...vectorEmbeddingPolicyData];
|
|
||||||
const vectorEmbedding = vectorEmbeddings[index];
|
|
||||||
vectorEmbeddings[index].indexType = option.key as never;
|
|
||||||
const error = onVectorEmbeddingDimensionError(vectorEmbedding.dimensions, vectorEmbedding.indexType);
|
|
||||||
vectorEmbeddings[index].dimensionsError = error;
|
|
||||||
setVectorEmbeddingPolicyData(vectorEmbeddings);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onVectorEmbeddingPolicyChange = (
|
|
||||||
index: number,
|
|
||||||
option: IDropdownOption,
|
|
||||||
property: VectorEmbeddingPolicyProperty,
|
|
||||||
): void => {
|
|
||||||
const vectorEmbeddings = [...vectorEmbeddingPolicyData];
|
|
||||||
vectorEmbeddings[index][property] = option.key as never;
|
|
||||||
setVectorEmbeddingPolicyData(vectorEmbeddings);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onAdd = () => {
|
|
||||||
setVectorEmbeddingPolicyData([
|
|
||||||
...vectorEmbeddingPolicyData,
|
|
||||||
{
|
|
||||||
path: "",
|
|
||||||
dataType: "float32",
|
|
||||||
distanceFunction: "euclidean",
|
|
||||||
dimensions: 0,
|
|
||||||
indexType: "none",
|
|
||||||
pathError: onVectorEmbeddingPathError(""),
|
|
||||||
dimensionsError: onVectorEmbeddingDimensionError(0, "none"),
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onDelete = (index: number) => {
|
|
||||||
const vectorEmbeddings = vectorEmbeddingPolicyData.filter((_uniqueKey, j) => index !== j);
|
|
||||||
setVectorEmbeddingPolicyData(vectorEmbeddings);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Stack tokens={{ childrenGap: 4 }}>
|
|
||||||
{vectorEmbeddingPolicyData.length > 0 &&
|
|
||||||
vectorEmbeddingPolicyData.map((vectorEmbeddingPolicy: VectorEmbeddingPolicyData, index: number) => (
|
|
||||||
<CollapsibleSectionComponent key={index} isExpandedByDefault={true} title={`Vector embedding ${index + 1}`}>
|
|
||||||
<Stack horizontal tokens={{ childrenGap: 4 }}>
|
|
||||||
<Stack
|
|
||||||
styles={{
|
|
||||||
root: {
|
|
||||||
margin: "0 0 6px 20px !important",
|
|
||||||
paddingLeft: 20,
|
|
||||||
width: "80%",
|
|
||||||
borderLeft: "1px solid",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Stack>
|
|
||||||
<Label styles={{ root: { fontSize: 12 } }}>Path</Label>
|
|
||||||
<TextField
|
|
||||||
id={`vector-policy-path-${index + 1}`}
|
|
||||||
required={true}
|
|
||||||
placeholder="/vector1"
|
|
||||||
styles={textFieldStyles}
|
|
||||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) => onVectorEmbeddingPathChange(index, event)}
|
|
||||||
value={vectorEmbeddingPolicy.path || ""}
|
|
||||||
errorMessage={vectorEmbeddingPolicy.pathError}
|
|
||||||
/>
|
|
||||||
</Stack>
|
|
||||||
<Stack>
|
|
||||||
<Label styles={{ root: { fontSize: 12 } }}>Data type</Label>
|
|
||||||
<Dropdown
|
|
||||||
required={true}
|
|
||||||
styles={dropdownStyles}
|
|
||||||
options={getDataTypeOptions()}
|
|
||||||
selectedKey={vectorEmbeddingPolicy.dataType}
|
|
||||||
onChange={(_event: React.FormEvent<HTMLDivElement>, option: IDropdownOption) =>
|
|
||||||
onVectorEmbeddingPolicyChange(index, option, "dataType")
|
|
||||||
}
|
|
||||||
></Dropdown>
|
|
||||||
</Stack>
|
|
||||||
<Stack>
|
|
||||||
<Label styles={{ root: { fontSize: 12 } }}>Distance function</Label>
|
|
||||||
<Dropdown
|
|
||||||
required={true}
|
|
||||||
styles={dropdownStyles}
|
|
||||||
options={getDistanceFunctionOptions()}
|
|
||||||
selectedKey={vectorEmbeddingPolicy.distanceFunction}
|
|
||||||
onChange={(_event: React.FormEvent<HTMLDivElement>, option: IDropdownOption) =>
|
|
||||||
onVectorEmbeddingPolicyChange(index, option, "distanceFunction")
|
|
||||||
}
|
|
||||||
></Dropdown>
|
|
||||||
</Stack>
|
|
||||||
<Stack>
|
|
||||||
<Label styles={{ root: { fontSize: 12 } }}>Dimensions</Label>
|
|
||||||
<TextField
|
|
||||||
id={`vector-policy-dimension-${index + 1}`}
|
|
||||||
required={true}
|
|
||||||
styles={textFieldStyles}
|
|
||||||
value={String(vectorEmbeddingPolicy.dimensions || 0)}
|
|
||||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
|
||||||
onVectorEmbeddingDimensionsChange(index, event)
|
|
||||||
}
|
|
||||||
errorMessage={vectorEmbeddingPolicy.dimensionsError}
|
|
||||||
/>
|
|
||||||
</Stack>
|
|
||||||
<Stack>
|
|
||||||
<Label styles={{ root: { fontSize: 12 } }}>Index type</Label>
|
|
||||||
<Dropdown
|
|
||||||
required={true}
|
|
||||||
styles={dropdownStyles}
|
|
||||||
options={getIndexTypeOptions()}
|
|
||||||
selectedKey={vectorEmbeddingPolicy.indexType}
|
|
||||||
onChange={(_event: React.FormEvent<HTMLDivElement>, option: IDropdownOption) =>
|
|
||||||
onVectorEmbeddingIndexTypeChange(index, option)
|
|
||||||
}
|
|
||||||
></Dropdown>
|
|
||||||
</Stack>
|
|
||||||
</Stack>
|
|
||||||
<IconButton
|
|
||||||
id={`delete-vector-policy-${index + 1}`}
|
|
||||||
iconProps={{ iconName: "Delete" }}
|
|
||||||
style={{ height: 27, margin: "auto" }}
|
|
||||||
onClick={() => onDelete(index)}
|
|
||||||
/>
|
|
||||||
</Stack>
|
|
||||||
</CollapsibleSectionComponent>
|
|
||||||
))}
|
|
||||||
<DefaultButton id={`add-vector-policy`} styles={{ root: { maxWidth: 170, fontSize: 12 } }} onClick={onAdd}>
|
|
||||||
Add vector embedding
|
|
||||||
</DefaultButton>
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
};
|
|
@ -79,9 +79,13 @@ export const QueryCopilotFeedbackModal = ({
|
|||||||
readOnly
|
readOnly
|
||||||
/>
|
/>
|
||||||
<Text style={{ fontSize: 12, marginBottom: 14 }}>
|
<Text style={{ fontSize: 12, marginBottom: 14 }}>
|
||||||
By pressing submit, your feedback will be used to improve Microsoft products and services. Please see the{" "}
|
Microsoft will process the feedback you submit pursuant to your organization’s instructions in order to
|
||||||
|
improve your and your organization’s experience with this product. If you have any questions about the use
|
||||||
|
of feedback data, please contact your tenant administrator. Processing of feedback data is governed by the
|
||||||
|
Microsoft Products and Services Data Protection Addendum between your organization and Microsoft, and the
|
||||||
|
feedback you submit is considered Personal Data under that addendum. Please see the{" "}
|
||||||
{
|
{
|
||||||
<Link href="https://privacy.microsoft.com/privacystatement" target="_blank">
|
<Link href="https://go.microsoft.com/fwlink/?LinkId=521839" target="_blank">
|
||||||
Privacy statement
|
Privacy statement
|
||||||
</Link>
|
</Link>
|
||||||
}{" "}
|
}{" "}
|
||||||
|
@ -99,10 +99,10 @@ exports[`Query Copilot Feedback Modal snapshot test shoud render and match snaps
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
By pressing submit, your feedback will be used to improve Microsoft products and services. Please see the
|
Microsoft will process the feedback you submit pursuant to your organization’s instructions in order to improve your and your organization’s experience with this product. If you have any questions about the use of feedback data, please contact your tenant administrator. Processing of feedback data is governed by the Microsoft Products and Services Data Protection Addendum between your organization and Microsoft, and the feedback you submit is considered Personal Data under that addendum. Please see the
|
||||||
|
|
||||||
<StyledLinkBase
|
<StyledLinkBase
|
||||||
href="https://privacy.microsoft.com/privacystatement"
|
href="https://go.microsoft.com/fwlink/?LinkId=521839"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
Privacy statement
|
Privacy statement
|
||||||
@ -236,10 +236,10 @@ exports[`Query Copilot Feedback Modal snapshot test should cancel submission 1`]
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
By pressing submit, your feedback will be used to improve Microsoft products and services. Please see the
|
Microsoft will process the feedback you submit pursuant to your organization’s instructions in order to improve your and your organization’s experience with this product. If you have any questions about the use of feedback data, please contact your tenant administrator. Processing of feedback data is governed by the Microsoft Products and Services Data Protection Addendum between your organization and Microsoft, and the feedback you submit is considered Personal Data under that addendum. Please see the
|
||||||
|
|
||||||
<StyledLinkBase
|
<StyledLinkBase
|
||||||
href="https://privacy.microsoft.com/privacystatement"
|
href="https://go.microsoft.com/fwlink/?LinkId=521839"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
Privacy statement
|
Privacy statement
|
||||||
@ -373,10 +373,10 @@ exports[`Query Copilot Feedback Modal snapshot test should close on cancel click
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
By pressing submit, your feedback will be used to improve Microsoft products and services. Please see the
|
Microsoft will process the feedback you submit pursuant to your organization’s instructions in order to improve your and your organization’s experience with this product. If you have any questions about the use of feedback data, please contact your tenant administrator. Processing of feedback data is governed by the Microsoft Products and Services Data Protection Addendum between your organization and Microsoft, and the feedback you submit is considered Personal Data under that addendum. Please see the
|
||||||
|
|
||||||
<StyledLinkBase
|
<StyledLinkBase
|
||||||
href="https://privacy.microsoft.com/privacystatement"
|
href="https://go.microsoft.com/fwlink/?LinkId=521839"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
Privacy statement
|
Privacy statement
|
||||||
@ -510,10 +510,10 @@ exports[`Query Copilot Feedback Modal snapshot test should get user unput 1`] =
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
By pressing submit, your feedback will be used to improve Microsoft products and services. Please see the
|
Microsoft will process the feedback you submit pursuant to your organization’s instructions in order to improve your and your organization’s experience with this product. If you have any questions about the use of feedback data, please contact your tenant administrator. Processing of feedback data is governed by the Microsoft Products and Services Data Protection Addendum between your organization and Microsoft, and the feedback you submit is considered Personal Data under that addendum. Please see the
|
||||||
|
|
||||||
<StyledLinkBase
|
<StyledLinkBase
|
||||||
href="https://privacy.microsoft.com/privacystatement"
|
href="https://go.microsoft.com/fwlink/?LinkId=521839"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
Privacy statement
|
Privacy statement
|
||||||
@ -647,10 +647,10 @@ exports[`Query Copilot Feedback Modal snapshot test should not render dont show
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
By pressing submit, your feedback will be used to improve Microsoft products and services. Please see the
|
Microsoft will process the feedback you submit pursuant to your organization’s instructions in order to improve your and your organization’s experience with this product. If you have any questions about the use of feedback data, please contact your tenant administrator. Processing of feedback data is governed by the Microsoft Products and Services Data Protection Addendum between your organization and Microsoft, and the feedback you submit is considered Personal Data under that addendum. Please see the
|
||||||
|
|
||||||
<StyledLinkBase
|
<StyledLinkBase
|
||||||
href="https://privacy.microsoft.com/privacystatement"
|
href="https://go.microsoft.com/fwlink/?LinkId=521839"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
Privacy statement
|
Privacy statement
|
||||||
@ -784,10 +784,10 @@ exports[`Query Copilot Feedback Modal snapshot test should render dont show agai
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
By pressing submit, your feedback will be used to improve Microsoft products and services. Please see the
|
Microsoft will process the feedback you submit pursuant to your organization’s instructions in order to improve your and your organization’s experience with this product. If you have any questions about the use of feedback data, please contact your tenant administrator. Processing of feedback data is governed by the Microsoft Products and Services Data Protection Addendum between your organization and Microsoft, and the feedback you submit is considered Personal Data under that addendum. Please see the
|
||||||
|
|
||||||
<StyledLinkBase
|
<StyledLinkBase
|
||||||
href="https://privacy.microsoft.com/privacystatement"
|
href="https://go.microsoft.com/fwlink/?LinkId=521839"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
Privacy statement
|
Privacy statement
|
||||||
@ -936,10 +936,10 @@ exports[`Query Copilot Feedback Modal snapshot test should submit submission 1`]
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
By pressing submit, your feedback will be used to improve Microsoft products and services. Please see the
|
Microsoft will process the feedback you submit pursuant to your organization’s instructions in order to improve your and your organization’s experience with this product. If you have any questions about the use of feedback data, please contact your tenant administrator. Processing of feedback data is governed by the Microsoft Products and Services Data Protection Addendum between your organization and Microsoft, and the feedback you submit is considered Personal Data under that addendum. Please see the
|
||||||
|
|
||||||
<StyledLinkBase
|
<StyledLinkBase
|
||||||
href="https://privacy.microsoft.com/privacystatement"
|
href="https://go.microsoft.com/fwlink/?LinkId=521839"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
Privacy statement
|
Privacy statement
|
||||||
|
@ -282,6 +282,7 @@ export const SidebarContainer: React.FC<SidebarProps> = ({ explorer }) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<div className="sidebarContainer">
|
||||||
<Allotment ref={allotment} onChange={onChange} onDragEnd={onDragEnd} className="resourceTreeAndTabs">
|
<Allotment ref={allotment} onChange={onChange} onDragEnd={onDragEnd} className="resourceTreeAndTabs">
|
||||||
{/* Collections Tree - Start */}
|
{/* Collections Tree - Start */}
|
||||||
{hasSidebar && (
|
{hasSidebar && (
|
||||||
@ -344,5 +345,6 @@ export const SidebarContainer: React.FC<SidebarProps> = ({ explorer }) => {
|
|||||||
<Tabs explorer={explorer} />
|
<Tabs explorer={explorer} />
|
||||||
</Allotment.Pane>
|
</Allotment.Pane>
|
||||||
</Allotment>
|
</Allotment>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -757,6 +757,10 @@ export class CassandraAPIDataClient extends TableDataClient {
|
|||||||
CassandraProxyEndpoints.Mooncake,
|
CassandraProxyEndpoints.Mooncake,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
if (configContext.globallyEnabledCassandraAPIs.includes(api)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
configContext.NEW_CASSANDRA_APIS?.includes(api) &&
|
configContext.NEW_CASSANDRA_APIS?.includes(api) &&
|
||||||
activeCassandraProxyEndpoints.includes(configContext.CASSANDRA_PROXY_ENDPOINT)
|
activeCassandraProxyEndpoints.includes(configContext.CASSANDRA_PROXY_ENDPOINT)
|
||||||
|
@ -49,6 +49,7 @@ jest.mock("Common/dataAccess/queryDocuments", () => ({
|
|||||||
requestCharge: 1,
|
requestCharge: 1,
|
||||||
activityId: "activityId",
|
activityId: "activityId",
|
||||||
indexMetrics: "indexMetrics",
|
indexMetrics: "indexMetrics",
|
||||||
|
correlatedActivityId: undefined,
|
||||||
}),
|
}),
|
||||||
})),
|
})),
|
||||||
}));
|
}));
|
||||||
@ -385,22 +386,6 @@ describe("Documents tab (noSql API)", () => {
|
|||||||
it("should render the page", () => {
|
it("should render the page", () => {
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("clicking on Edit filter should render the Apply Filter button", () => {
|
|
||||||
wrapper
|
|
||||||
.findWhere((node) => node.text() === "Edit Filter")
|
|
||||||
.at(0)
|
|
||||||
.simulate("click");
|
|
||||||
expect(wrapper.findWhere((node) => node.text() === "Apply Filter").exists()).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("clicking on Edit filter should render input for filter", () => {
|
|
||||||
wrapper
|
|
||||||
.findWhere((node) => node.text() === "Edit Filter")
|
|
||||||
.at(0)
|
|
||||||
.simulate("click");
|
|
||||||
expect(wrapper.find("Input.filterInput").exists()).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Command bar buttons", () => {
|
describe("Command bar buttons", () => {
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { Item, ItemDefinition, PartitionKey, PartitionKeyDefinition, QueryIterator, Resource } from "@azure/cosmos";
|
import { Item, ItemDefinition, PartitionKey, PartitionKeyDefinition, QueryIterator, Resource } from "@azure/cosmos";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Input,
|
|
||||||
Link,
|
Link,
|
||||||
MessageBar,
|
MessageBar,
|
||||||
MessageBarBody,
|
MessageBarBody,
|
||||||
@ -10,8 +9,7 @@ import {
|
|||||||
makeStyles,
|
makeStyles,
|
||||||
shorthands,
|
shorthands,
|
||||||
} from "@fluentui/react-components";
|
} from "@fluentui/react-components";
|
||||||
import { Dismiss16Filled } from "@fluentui/react-icons";
|
import { QueryCopilotSampleContainerId, QueryCopilotSampleDatabaseId } from "Common/Constants";
|
||||||
import { KeyCodes, QueryCopilotSampleContainerId, QueryCopilotSampleDatabaseId } from "Common/Constants";
|
|
||||||
import { getErrorMessage, getErrorStack } from "Common/ErrorHandlingUtils";
|
import { getErrorMessage, getErrorStack } from "Common/ErrorHandlingUtils";
|
||||||
import MongoUtility from "Common/MongoUtility";
|
import MongoUtility from "Common/MongoUtility";
|
||||||
import { createDocument } from "Common/dataAccess/createDocument";
|
import { createDocument } from "Common/dataAccess/createDocument";
|
||||||
@ -26,6 +24,7 @@ import { Platform, configContext } from "ConfigContext";
|
|||||||
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";
|
||||||
|
import { InputDataList, InputDatalistDropdownOptionSection } from "Explorer/Controls/InputDataList/InputDataList";
|
||||||
import { ProgressModalDialog } from "Explorer/Controls/ProgressModalDialog";
|
import { ProgressModalDialog } from "Explorer/Controls/ProgressModalDialog";
|
||||||
import Explorer from "Explorer/Explorer";
|
import Explorer from "Explorer/Explorer";
|
||||||
import { useCommandBar } from "Explorer/Menus/CommandBar/CommandBarComponentAdapter";
|
import { useCommandBar } from "Explorer/Menus/CommandBar/CommandBarComponentAdapter";
|
||||||
@ -74,6 +73,7 @@ const MAX_FILTER_HISTORY_COUNT = 100; // Datalist will become scrollable, so we
|
|||||||
const NO_SQL_THROTTLING_DOC_URL =
|
const NO_SQL_THROTTLING_DOC_URL =
|
||||||
"https://learn.microsoft.com/azure/cosmos-db/nosql/troubleshoot-request-rate-too-large";
|
"https://learn.microsoft.com/azure/cosmos-db/nosql/troubleshoot-request-rate-too-large";
|
||||||
const MONGO_THROTTLING_DOC_URL = "https://learn.microsoft.com/azure/cosmos-db/mongodb/prevent-rate-limiting-errors";
|
const MONGO_THROTTLING_DOC_URL = "https://learn.microsoft.com/azure/cosmos-db/mongodb/prevent-rate-limiting-errors";
|
||||||
|
const DATA_EXPLORER_DOC_URL = "https://learn.microsoft.com/en-us/azure/cosmos-db/data-explorer";
|
||||||
|
|
||||||
const loadMoreHeight = LayoutConstants.rowHeight;
|
const loadMoreHeight = LayoutConstants.rowHeight;
|
||||||
export const useDocumentsTabStyles = makeStyles({
|
export const useDocumentsTabStyles = makeStyles({
|
||||||
@ -90,12 +90,6 @@ export const useDocumentsTabStyles = makeStyles({
|
|||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
...cosmosShorthands.borderBottom(),
|
...cosmosShorthands.borderBottom(),
|
||||||
},
|
},
|
||||||
filterInput: {
|
|
||||||
flexGrow: 1,
|
|
||||||
},
|
|
||||||
appliedFilter: {
|
|
||||||
flexGrow: 1,
|
|
||||||
},
|
|
||||||
tableContainer: {
|
tableContainer: {
|
||||||
marginRight: tokens.spacingHorizontalXXXL,
|
marginRight: tokens.spacingHorizontalXXXL,
|
||||||
},
|
},
|
||||||
@ -556,8 +550,6 @@ export interface IDocumentsTabComponentProps {
|
|||||||
isTabActive: boolean;
|
isTabActive: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const getUniqueId = (collection: ViewModels.CollectionBase): string => `${collection.databaseId}-${collection.id()}`;
|
|
||||||
|
|
||||||
const getDefaultSqlFilters = (partitionKeys: string[]) =>
|
const getDefaultSqlFilters = (partitionKeys: string[]) =>
|
||||||
['WHERE c.id = "foo"', "ORDER BY c._ts DESC", 'WHERE c.id = "foo" ORDER BY c._ts DESC', "ORDER BY c._ts ASC"].concat(
|
['WHERE c.id = "foo"', "ORDER BY c._ts DESC", 'WHERE c.id = "foo" ORDER BY c._ts DESC', "ORDER BY c._ts ASC"].concat(
|
||||||
partitionKeys.map((partitionKey) => `WHERE c.${partitionKey} = "foo"`),
|
partitionKeys.map((partitionKey) => `WHERE c.${partitionKey} = "foo"`),
|
||||||
@ -583,14 +575,9 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
|
|||||||
onIsExecutingChange,
|
onIsExecutingChange,
|
||||||
isTabActive,
|
isTabActive,
|
||||||
}): JSX.Element => {
|
}): JSX.Element => {
|
||||||
const [isFilterCreated, setIsFilterCreated] = useState<boolean>(true);
|
|
||||||
const [isFilterExpanded, setIsFilterExpanded] = useState<boolean>(false);
|
|
||||||
const [isFilterFocused, setIsFilterFocused] = useState<boolean>(false);
|
|
||||||
const [appliedFilter, setAppliedFilter] = useState<string>("");
|
|
||||||
const [filterContent, setFilterContent] = useState<string>("");
|
const [filterContent, setFilterContent] = useState<string>("");
|
||||||
const [documentIds, setDocumentIds] = useState<ExtendedDocumentId[]>([]);
|
const [documentIds, setDocumentIds] = useState<ExtendedDocumentId[]>([]);
|
||||||
const [isExecuting, setIsExecuting] = useState<boolean>(false);
|
const [isExecuting, setIsExecuting] = useState<boolean>(false);
|
||||||
const filterInput = useRef<HTMLInputElement>(null);
|
|
||||||
const styles = useDocumentsTabStyles();
|
const styles = useDocumentsTabStyles();
|
||||||
|
|
||||||
// Query
|
// Query
|
||||||
@ -610,7 +597,7 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
|
|||||||
// Table user clicked on this row
|
// Table user clicked on this row
|
||||||
const [clickedRowIndex, setClickedRowIndex] = useState<number>(RESET_INDEX);
|
const [clickedRowIndex, setClickedRowIndex] = useState<number>(RESET_INDEX);
|
||||||
// Table multiple selection
|
// Table multiple selection
|
||||||
const [selectedRows, setSelectedRows] = React.useState<Set<TableRowId>>(() => new Set<TableRowId>([0]));
|
const [selectedRows, setSelectedRows] = React.useState<Set<TableRowId>>(() => new Set<TableRowId>());
|
||||||
|
|
||||||
// Command buttons
|
// Command buttons
|
||||||
const [editorState, setEditorState] = useState<ViewModels.DocumentExplorerState>(
|
const [editorState, setEditorState] = useState<ViewModels.DocumentExplorerState>(
|
||||||
@ -657,29 +644,6 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
|
|||||||
|
|
||||||
const setKeyboardActions = useKeyboardActionGroup(KeyboardActionGroup.ACTIVE_TAB);
|
const setKeyboardActions = useKeyboardActionGroup(KeyboardActionGroup.ACTIVE_TAB);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isFilterFocused) {
|
|
||||||
filterInput.current?.focus();
|
|
||||||
}
|
|
||||||
}, [isFilterFocused]);
|
|
||||||
|
|
||||||
// Clicked row must be defined
|
|
||||||
useEffect(() => {
|
|
||||||
if (documentIds.length > 0) {
|
|
||||||
let currentClickedRowIndex = clickedRowIndex;
|
|
||||||
if (
|
|
||||||
(currentClickedRowIndex === RESET_INDEX &&
|
|
||||||
editorState === ViewModels.DocumentExplorerState.noDocumentSelected) ||
|
|
||||||
currentClickedRowIndex > documentIds.length - 1
|
|
||||||
) {
|
|
||||||
// reset clicked row or the current clicked row is out of bounds
|
|
||||||
currentClickedRowIndex = INITIAL_SELECTED_ROW_INDEX;
|
|
||||||
setSelectedRows(new Set([INITIAL_SELECTED_ROW_INDEX]));
|
|
||||||
onDocumentClicked(currentClickedRowIndex, documentIds);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [documentIds, clickedRowIndex, editorState]);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recursively delete all documents by retrying throttled requests (429).
|
* Recursively delete all documents by retrying throttled requests (429).
|
||||||
* This only works for NoSQL, because the bulk response includes status for each delete document request.
|
* This only works for NoSQL, because the bulk response includes status for each delete document request.
|
||||||
@ -773,11 +737,6 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
|
|||||||
}, timeout);
|
}, timeout);
|
||||||
}, [bulkDeleteOperation, bulkDeleteProcess, bulkDeleteMode]);
|
}, [bulkDeleteOperation, bulkDeleteProcess, bulkDeleteMode]);
|
||||||
|
|
||||||
const applyFilterButton = {
|
|
||||||
enabled: true,
|
|
||||||
visible: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
const partitionKey: DataModels.PartitionKey = useMemo(
|
const partitionKey: DataModels.PartitionKey = useMemo(
|
||||||
() => _partitionKey || (_collection && _collection.partitionKey),
|
() => _partitionKey || (_collection && _collection.partitionKey),
|
||||||
[_collection, _partitionKey],
|
[_collection, _partitionKey],
|
||||||
@ -848,10 +807,6 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
|
|||||||
// This is executed in onActivate() in the original code.
|
// This is executed in onActivate() in the original code.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setKeyboardActions({
|
setKeyboardActions({
|
||||||
[KeyboardAction.SEARCH]: () => {
|
|
||||||
onShowFilterClick();
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
[KeyboardAction.CLEAR_SEARCH]: () => {
|
[KeyboardAction.CLEAR_SEARCH]: () => {
|
||||||
setFilterContent("");
|
setFilterContent("");
|
||||||
refreshDocumentsGrid(true);
|
refreshDocumentsGrid(true);
|
||||||
@ -1334,12 +1289,6 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
const onShowFilterClick = () => {
|
|
||||||
setIsFilterCreated(true);
|
|
||||||
setIsFilterExpanded(true);
|
|
||||||
setIsFilterFocused(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const queryTimeoutEnabled = useCallback(
|
const queryTimeoutEnabled = useCallback(
|
||||||
(): boolean => !isPreferredApiMongoDB && LocalStorageUtility.getEntryBoolean(StorageKey.QueryTimeoutEnabled),
|
(): boolean => !isPreferredApiMongoDB && LocalStorageUtility.getEntryBoolean(StorageKey.QueryTimeoutEnabled),
|
||||||
[isPreferredApiMongoDB],
|
[isPreferredApiMongoDB],
|
||||||
@ -1381,19 +1330,6 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
|
|||||||
selectedColumnIds,
|
selectedColumnIds,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const onHideFilterClick = (): void => {
|
|
||||||
setIsFilterExpanded(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onCloseButtonKeyDown: KeyboardEventHandler<HTMLSpanElement> = (event) => {
|
|
||||||
if (event.keyCode === KeyCodes.Enter || event.keyCode === KeyCodes.Space) {
|
|
||||||
onHideFilterClick();
|
|
||||||
event.stopPropagation();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const updateDocumentIds = (newDocumentsIds: DocumentId[]): void => {
|
const updateDocumentIds = (newDocumentsIds: DocumentId[]): void => {
|
||||||
setDocumentIds(newDocumentsIds);
|
setDocumentIds(newDocumentsIds);
|
||||||
|
|
||||||
@ -1535,14 +1471,9 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onFilterKeyDown = (e: React.KeyboardEvent<HTMLInputElement>): void => {
|
const onFilterKeyDown = (e: React.KeyboardEvent<HTMLInputElement>): void => {
|
||||||
if (e.key === "Enter") {
|
if (e.key === Constants.NormalizedEventKey.Enter) {
|
||||||
onApplyFilterClick();
|
onApplyFilterClick();
|
||||||
|
|
||||||
// Suppress the default behavior of the key
|
|
||||||
e.preventDefault();
|
|
||||||
} else if (e.key === "Escape") {
|
|
||||||
onHideFilterClick();
|
|
||||||
|
|
||||||
// Suppress the default behavior of the key
|
// Suppress the default behavior of the key
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
@ -2040,10 +1971,6 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
|
|||||||
applyFilterButtonPressed,
|
applyFilterButtonPressed,
|
||||||
});
|
});
|
||||||
|
|
||||||
// collapse filter
|
|
||||||
setAppliedFilter(filterContent);
|
|
||||||
setIsFilterExpanded(false);
|
|
||||||
|
|
||||||
// If apply filter is pressed, reset current selected document
|
// If apply filter is pressed, reset current selected document
|
||||||
if (applyFilterButtonPressed) {
|
if (applyFilterButtonPressed) {
|
||||||
setClickedRowIndex(RESET_INDEX);
|
setClickedRowIndex(RESET_INDEX);
|
||||||
@ -2120,97 +2047,59 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
|
|||||||
(partitionKey.systemKey && !isPreferredApiMongoDB) || (isPreferredApiMongoDB && isMongoBulkDeleteDisabled);
|
(partitionKey.systemKey && !isPreferredApiMongoDB) || (isPreferredApiMongoDB && isMongoBulkDeleteDisabled);
|
||||||
// -------------------------------------------------------
|
// -------------------------------------------------------
|
||||||
|
|
||||||
|
const getFilterChoices = (): InputDatalistDropdownOptionSection[] => {
|
||||||
|
const options: InputDatalistDropdownOptionSection[] = [];
|
||||||
|
const nonBlankLastFilters = lastFilterContents.filter((filter) => filter.trim() !== "");
|
||||||
|
if (nonBlankLastFilters.length > 0) {
|
||||||
|
options.push({
|
||||||
|
label: "Saved filters",
|
||||||
|
options: nonBlankLastFilters,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
options.push({
|
||||||
|
label: "Default filters",
|
||||||
|
options: isPreferredApiMongoDB ? defaultMongoFilters : getDefaultSqlFilters(partitionKeyProperties),
|
||||||
|
});
|
||||||
|
return options;
|
||||||
|
};
|
||||||
|
|
||||||
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" }}>
|
||||||
{isFilterCreated && (
|
|
||||||
<>
|
|
||||||
{!isFilterExpanded && !isPreferredApiMongoDB && (
|
|
||||||
<div className={styles.filterRow}>
|
|
||||||
<span>SELECT * FROM c</span>
|
|
||||||
<span className={styles.appliedFilter}>{appliedFilter}</span>
|
|
||||||
<Button appearance="primary" size="small" onClick={onShowFilterClick}>
|
|
||||||
Edit Filter
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{!isFilterExpanded && isPreferredApiMongoDB && (
|
|
||||||
<div className={styles.filterRow}>
|
|
||||||
{appliedFilter.length > 0 && <span>Filter :</span>}
|
|
||||||
{!(appliedFilter.length > 0) && <span className="noFilterApplied">No filter applied</span>}
|
|
||||||
<span className={styles.appliedFilter}>{appliedFilter}</span>
|
|
||||||
<Button appearance="primary" size="small" onClick={onShowFilterClick}>
|
|
||||||
Edit Filter
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{isFilterExpanded && (
|
|
||||||
<div className={styles.filterRow}>
|
<div className={styles.filterRow}>
|
||||||
{!isPreferredApiMongoDB && <span> SELECT * FROM c </span>}
|
{!isPreferredApiMongoDB && <span> SELECT * FROM c </span>}
|
||||||
<Input
|
<InputDataList
|
||||||
ref={filterInput}
|
dropdownOptions={getFilterChoices()}
|
||||||
type="text"
|
|
||||||
size="small"
|
|
||||||
list={`filtersList-${getUniqueId(_collection)}`}
|
|
||||||
className={`filterInput ${styles.filterInput}`}
|
|
||||||
title="Type a query predicate or choose one from the list."
|
|
||||||
placeholder={
|
placeholder={
|
||||||
isPreferredApiMongoDB
|
isPreferredApiMongoDB
|
||||||
? "Type a query predicate (e.g., {´a´:´foo´}), or choose one from the drop down list, or leave empty to query all documents."
|
? "Type a query predicate (e.g., {´a´:´foo´}), or choose one from the drop down list, or leave empty to query all documents."
|
||||||
: "Type a query predicate (e.g., WHERE c.id=´1´), or choose one from the drop down list, or leave empty to query all documents."
|
: "Type a query predicate (e.g., WHERE c.id=´1´), or choose one from the drop down list, or leave empty to query all documents."
|
||||||
}
|
}
|
||||||
|
title="Type a query predicate or choose one from the list."
|
||||||
value={filterContent}
|
value={filterContent}
|
||||||
autoFocus={true}
|
onChange={(value) => setFilterContent(value)}
|
||||||
onKeyDown={onFilterKeyDown}
|
onKeyDown={onFilterKeyDown}
|
||||||
onChange={(e) => setFilterContent(e.target.value)}
|
bottomLink={{ text: "Learn more", url: DATA_EXPLORER_DOC_URL }}
|
||||||
onBlur={() => setIsFilterFocused(false)}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<datalist id={`filtersList-${getUniqueId(_collection)}`}>
|
|
||||||
{addStringsNoDuplicate(
|
|
||||||
lastFilterContents,
|
|
||||||
isPreferredApiMongoDB ? defaultMongoFilters : getDefaultSqlFilters(partitionKeyProperties),
|
|
||||||
).map((filter) => (
|
|
||||||
<option key={filter} value={filter} />
|
|
||||||
))}
|
|
||||||
</datalist>
|
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
appearance="primary"
|
appearance="primary"
|
||||||
size="small"
|
size="small"
|
||||||
onClick={onApplyFilterClick}
|
onClick={() => {
|
||||||
disabled={!applyFilterButton.enabled}
|
if (isExecuting) {
|
||||||
aria-label="Apply filter"
|
if (!isPreferredApiMongoDB) {
|
||||||
|
queryAbortController.abort();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
onApplyFilterClick();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={isExecuting && isPreferredApiMongoDB}
|
||||||
|
aria-label={!isExecuting || isPreferredApiMongoDB ? "Apply filter" : "Cancel"}
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
>
|
>
|
||||||
Apply Filter
|
{!isExecuting || isPreferredApiMongoDB ? "Apply Filter" : "Cancel"}
|
||||||
</Button>
|
</Button>
|
||||||
{!isPreferredApiMongoDB && isExecuting && (
|
|
||||||
<Button
|
|
||||||
appearance="primary"
|
|
||||||
size="small"
|
|
||||||
aria-label="Cancel Query"
|
|
||||||
onClick={() => queryAbortController.abort()}
|
|
||||||
tabIndex={0}
|
|
||||||
>
|
|
||||||
Cancel Query
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
<Button
|
|
||||||
aria-label="close filter"
|
|
||||||
tabIndex={0}
|
|
||||||
onClick={onHideFilterClick}
|
|
||||||
onKeyDown={onCloseButtonKeyDown}
|
|
||||||
appearance="transparent"
|
|
||||||
size="small"
|
|
||||||
icon={<Dismiss16Filled />}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{/* <Split> doesn't like to be a flex child */}
|
|
||||||
<div style={{ overflow: "hidden", height: "100%" }}>
|
|
||||||
<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]);
|
||||||
@ -2232,7 +2121,6 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
|
|||||||
<DocumentsTableComponent
|
<DocumentsTableComponent
|
||||||
onRefreshTable={() => refreshDocumentsGrid(false)}
|
onRefreshTable={() => refreshDocumentsGrid(false)}
|
||||||
items={tableItems}
|
items={tableItems}
|
||||||
onItemClicked={(index) => onDocumentClicked(index, documentIds)}
|
|
||||||
onSelectedRowsChange={onSelectedRowsChange}
|
onSelectedRowsChange={onSelectedRowsChange}
|
||||||
selectedRows={selectedRows}
|
selectedRows={selectedRows}
|
||||||
size={tableContainerSizePx}
|
size={tableContainerSizePx}
|
||||||
@ -2283,7 +2171,6 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
|
|||||||
</Allotment.Pane>
|
</Allotment.Pane>
|
||||||
</Allotment>
|
</Allotment>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
{bulkDeleteOperation && (
|
{bulkDeleteOperation && (
|
||||||
<ProgressModalDialog
|
<ProgressModalDialog
|
||||||
isOpen={isBulkDeleteDialogOpen}
|
isOpen={isBulkDeleteDialogOpen}
|
||||||
|
@ -67,6 +67,13 @@ jest.mock("Explorer/Controls/Dialog", () => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
// Added as recent change to @azure/core-util would cause randomUUID() to throw an error during jest tests.
|
||||||
|
// TODO: when not using beta version of @azure/cosmos sdk try removing this
|
||||||
|
jest.mock("@azure/core-util", () => ({
|
||||||
|
...jest.requireActual("@azure/core-util"),
|
||||||
|
randomUUID: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
async function waitForComponentToPaint<P = unknown>(wrapper: ReactWrapper<P> | ShallowWrapper<P>, amount = 0) {
|
async function waitForComponentToPaint<P = unknown>(wrapper: ReactWrapper<P> | ShallowWrapper<P>, amount = 0) {
|
||||||
let newWrapper;
|
let newWrapper;
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
|
@ -14,7 +14,6 @@ describe("DocumentsTableComponent", () => {
|
|||||||
{ [ID_HEADER]: "2", [PARTITION_KEY_HEADER]: "pk2" },
|
{ [ID_HEADER]: "2", [PARTITION_KEY_HEADER]: "pk2" },
|
||||||
{ [ID_HEADER]: "3", [PARTITION_KEY_HEADER]: "pk3" },
|
{ [ID_HEADER]: "3", [PARTITION_KEY_HEADER]: "pk3" },
|
||||||
],
|
],
|
||||||
onItemClicked: (): void => {},
|
|
||||||
onSelectedRowsChange: (): void => {},
|
onSelectedRowsChange: (): void => {},
|
||||||
selectedRows: new Set<TableRowId>(),
|
selectedRows: new Set<TableRowId>(),
|
||||||
size: {
|
size: {
|
||||||
|
@ -68,7 +68,6 @@ export type ColumnDefinition = {
|
|||||||
export interface IDocumentsTableComponentProps {
|
export interface IDocumentsTableComponentProps {
|
||||||
onRefreshTable: () => void;
|
onRefreshTable: () => void;
|
||||||
items: DocumentsTableComponentItem[];
|
items: DocumentsTableComponentItem[];
|
||||||
onItemClicked: (index: number) => void;
|
|
||||||
onSelectedRowsChange: (selectedItemsIndices: Set<TableRowId>) => void;
|
onSelectedRowsChange: (selectedItemsIndices: Set<TableRowId>) => void;
|
||||||
selectedRows: Set<TableRowId>;
|
selectedRows: Set<TableRowId>;
|
||||||
size: { height: number; width: number };
|
size: { height: number; width: number };
|
||||||
@ -98,6 +97,7 @@ const defaultSize = {
|
|||||||
idealWidth: 200,
|
idealWidth: 200,
|
||||||
minWidth: 50,
|
minWidth: 50,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DocumentsTableComponent: React.FC<IDocumentsTableComponentProps> = ({
|
export const DocumentsTableComponent: React.FC<IDocumentsTableComponentProps> = ({
|
||||||
onRefreshTable,
|
onRefreshTable,
|
||||||
items,
|
items,
|
||||||
@ -115,6 +115,8 @@ export const DocumentsTableComponent: React.FC<IDocumentsTableComponentProps> =
|
|||||||
}: IDocumentsTableComponentProps) => {
|
}: IDocumentsTableComponentProps) => {
|
||||||
const styles = useDocumentsTabStyles();
|
const styles = useDocumentsTabStyles();
|
||||||
|
|
||||||
|
const sortedRowsRef = React.useRef(null);
|
||||||
|
|
||||||
const [columnSizingOptions, setColumnSizingOptions] = React.useState<TableColumnSizingOptions>(() => {
|
const [columnSizingOptions, setColumnSizingOptions] = React.useState<TableColumnSizingOptions>(() => {
|
||||||
const columnSizesMap: ColumnSizesMap = readSubComponentState(SubComponentName.ColumnSizes, collection, {});
|
const columnSizesMap: ColumnSizesMap = readSubComponentState(SubComponentName.ColumnSizes, collection, {});
|
||||||
const columnSizesPx: TableColumnSizingOptions = {};
|
const columnSizesPx: TableColumnSizingOptions = {};
|
||||||
@ -291,22 +293,42 @@ export const DocumentsTableComponent: React.FC<IDocumentsTableComponentProps> =
|
|||||||
|
|
||||||
const [selectionStartIndex, setSelectionStartIndex] = React.useState<number>(INITIAL_SELECTED_ROW_INDEX);
|
const [selectionStartIndex, setSelectionStartIndex] = React.useState<number>(INITIAL_SELECTED_ROW_INDEX);
|
||||||
const onTableCellClicked = useCallback(
|
const onTableCellClicked = useCallback(
|
||||||
(e: React.MouseEvent, index: number) => {
|
(e: React.MouseEvent | undefined, index: number, rowId: TableRowId) => {
|
||||||
if (isSelectionDisabled) {
|
if (isSelectionDisabled) {
|
||||||
// Only allow click
|
// Only allow click
|
||||||
onSelectedRowsChange(new Set<TableRowId>([index]));
|
onSelectedRowsChange(new Set<TableRowId>([rowId]));
|
||||||
setSelectionStartIndex(index);
|
setSelectionStartIndex(index);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The selection helper computes in the index space (what's visible to the user in the table, ie the sorted array).
|
||||||
|
// selectedRows is in the rowId space (the index of the original unsorted array), so it must be converted to the index space.
|
||||||
|
const selectedRowsIndex = new Set<number>();
|
||||||
|
selectedRows.forEach((rowId) => {
|
||||||
|
const index = sortedRowsRef.current.findIndex((row: TableRowData) => row.rowId === rowId);
|
||||||
|
if (index !== -1) {
|
||||||
|
selectedRowsIndex.add(index);
|
||||||
|
} else {
|
||||||
|
// This should never happen
|
||||||
|
console.error(`Row with rowId ${rowId} not found in sorted rows`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const result = selectionHelper(
|
const result = selectionHelper(
|
||||||
selectedRows as Set<number>,
|
selectedRowsIndex,
|
||||||
index,
|
index,
|
||||||
isEnvironmentShiftPressed(e),
|
e && isEnvironmentShiftPressed(e),
|
||||||
isEnvironmentCtrlPressed(e),
|
e && isEnvironmentCtrlPressed(e),
|
||||||
selectionStartIndex,
|
selectionStartIndex,
|
||||||
);
|
);
|
||||||
onSelectedRowsChange(result.selection);
|
|
||||||
|
// Convert selectionHelper result from index space back to rowId space
|
||||||
|
const selectedRowIds = new Set<TableRowId>();
|
||||||
|
result.selection.forEach((index) => {
|
||||||
|
selectedRowIds.add(sortedRowsRef.current[index].rowId);
|
||||||
|
});
|
||||||
|
onSelectedRowsChange(selectedRowIds);
|
||||||
|
|
||||||
if (result.selectionStartIndex !== undefined) {
|
if (result.selectionStartIndex !== undefined) {
|
||||||
setSelectionStartIndex(result.selectionStartIndex);
|
setSelectionStartIndex(result.selectionStartIndex);
|
||||||
}
|
}
|
||||||
@ -320,16 +342,20 @@ export const DocumentsTableComponent: React.FC<IDocumentsTableComponentProps> =
|
|||||||
* - a key is down and the cell is clicked by the mouse
|
* - a key is down and the cell is clicked by the mouse
|
||||||
*/
|
*/
|
||||||
const onIdClicked = useCallback(
|
const onIdClicked = useCallback(
|
||||||
(e: React.KeyboardEvent<Element>, index: number) => {
|
(e: React.KeyboardEvent<Element>, rowId: TableRowId) => {
|
||||||
if (e.key === NormalizedEventKey.Enter || e.key === NormalizedEventKey.Space) {
|
if (e.key === NormalizedEventKey.Enter || e.key === NormalizedEventKey.Space) {
|
||||||
onSelectedRowsChange(new Set<TableRowId>([index]));
|
onSelectedRowsChange(new Set<TableRowId>([rowId]));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[onSelectedRowsChange],
|
[onSelectedRowsChange],
|
||||||
);
|
);
|
||||||
|
|
||||||
const RenderRow = ({ index, style, data }: ReactWindowRenderFnProps) => {
|
const RenderRow = ({ index, style, data }: ReactWindowRenderFnProps) => {
|
||||||
const { item, selected, appearance, onClick, onKeyDown } = data[index];
|
// WARNING: because the table sorts the data, 'index' is not the same as 'rowId'
|
||||||
|
// The rowId is the index of the item in the original array,
|
||||||
|
// while the index is the index of the item in the sorted array
|
||||||
|
const { item, selected, appearance, onClick, onKeyDown, rowId } = data[index];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableRow
|
<TableRow
|
||||||
aria-rowindex={index + 2}
|
aria-rowindex={index + 2}
|
||||||
@ -359,8 +385,8 @@ export const DocumentsTableComponent: React.FC<IDocumentsTableComponentProps> =
|
|||||||
key={column.columnId}
|
key={column.columnId}
|
||||||
className={styles.tableCell}
|
className={styles.tableCell}
|
||||||
// When clicking on a cell with shift/ctrl key, onKeyDown is called instead of onClick.
|
// When clicking on a cell with shift/ctrl key, onKeyDown is called instead of onClick.
|
||||||
onClick={(e: React.MouseEvent<Element, MouseEvent>) => onTableCellClicked(e, index)}
|
onClick={(e: React.MouseEvent<Element, MouseEvent>) => onTableCellClicked(e, index, rowId)}
|
||||||
onKeyPress={(e: React.KeyboardEvent<Element>) => onIdClicked(e, index)}
|
onKeyPress={(e: React.KeyboardEvent<Element>) => onIdClicked(e, rowId)}
|
||||||
{...columnSizing.getTableCellProps(column.columnId)}
|
{...columnSizing.getTableCellProps(column.columnId)}
|
||||||
tabIndex={column.columnId === "id" ? 0 : -1}
|
tabIndex={column.columnId === "id" ? 0 : -1}
|
||||||
>
|
>
|
||||||
@ -420,6 +446,19 @@ export const DocumentsTableComponent: React.FC<IDocumentsTableComponentProps> =
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Store the sorted rows in a ref which won't trigger a re-render (as opposed to a state)
|
||||||
|
sortedRowsRef.current = rows;
|
||||||
|
|
||||||
|
// If there are no selected rows, auto select the first row
|
||||||
|
const [autoSelectFirstDoc, setAutoSelectFirstDoc] = React.useState<boolean>(true);
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (autoSelectFirstDoc && sortedRowsRef.current?.length > 0 && selectedRows.size === 0) {
|
||||||
|
setAutoSelectFirstDoc(false);
|
||||||
|
const DOC_INDEX_TO_SELECT = 0;
|
||||||
|
onTableCellClicked(undefined, DOC_INDEX_TO_SELECT, sortedRowsRef.current[DOC_INDEX_TO_SELECT].rowId);
|
||||||
|
}
|
||||||
|
}, [selectedRows, onTableCellClicked, autoSelectFirstDoc]);
|
||||||
|
|
||||||
const toggleAllKeydown = React.useCallback(
|
const toggleAllKeydown = React.useCallback(
|
||||||
(e: React.KeyboardEvent<HTMLDivElement>) => {
|
(e: React.KeyboardEvent<HTMLDivElement>) => {
|
||||||
if (e.key === " ") {
|
if (e.key === " ") {
|
||||||
|
@ -19,25 +19,44 @@ exports[`Documents tab (noSql API) when rendered should render the page 1`] = `
|
|||||||
<span>
|
<span>
|
||||||
SELECT * FROM c
|
SELECT * FROM c
|
||||||
</span>
|
</span>
|
||||||
<span
|
<InputDataList
|
||||||
className="___r7kt3y0_0000000 fqerorx"
|
bottomLink={
|
||||||
|
{
|
||||||
|
"text": "Learn more",
|
||||||
|
"url": "https://learn.microsoft.com/en-us/azure/cosmos-db/data-explorer",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dropdownOptions={
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"label": "Default filters",
|
||||||
|
"options": [
|
||||||
|
"WHERE c.id = "foo"",
|
||||||
|
"ORDER BY c._ts DESC",
|
||||||
|
"WHERE c.id = "foo" ORDER BY c._ts DESC",
|
||||||
|
"ORDER BY c._ts ASC",
|
||||||
|
"WHERE c.foo = "foo"",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
onChange={[Function]}
|
||||||
|
onKeyDown={[Function]}
|
||||||
|
placeholder="Type a query predicate (e.g., WHERE c.id=´1´), or choose one from the drop down list, or leave empty to query all documents."
|
||||||
|
title="Type a query predicate or choose one from the list."
|
||||||
|
value=""
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
appearance="primary"
|
appearance="primary"
|
||||||
|
aria-label="Apply filter"
|
||||||
|
disabled={false}
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
size="small"
|
size="small"
|
||||||
|
tabIndex={0}
|
||||||
>
|
>
|
||||||
Edit Filter
|
Apply Filter
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
style={
|
|
||||||
{
|
|
||||||
"height": "100%",
|
|
||||||
"overflow": "hidden",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Allotment
|
<Allotment
|
||||||
onDragEnd={[Function]}
|
onDragEnd={[Function]}
|
||||||
>
|
>
|
||||||
@ -90,7 +109,6 @@ exports[`Documents tab (noSql API) when rendered should render the page 1`] = `
|
|||||||
isRowSelectionDisabled={true}
|
isRowSelectionDisabled={true}
|
||||||
items={[]}
|
items={[]}
|
||||||
onColumnSelectionChange={[Function]}
|
onColumnSelectionChange={[Function]}
|
||||||
onItemClicked={[Function]}
|
|
||||||
onRefreshTable={[Function]}
|
onRefreshTable={[Function]}
|
||||||
onSelectedRowsChange={[Function]}
|
onSelectedRowsChange={[Function]}
|
||||||
selectedColumnIds={
|
selectedColumnIds={
|
||||||
@ -98,11 +116,7 @@ exports[`Documents tab (noSql API) when rendered should render the page 1`] = `
|
|||||||
"id",
|
"id",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
selectedRows={
|
selectedRows={Set {}}
|
||||||
Set {
|
|
||||||
0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -122,6 +136,5 @@ exports[`Documents tab (noSql API) when rendered should render the page 1`] = `
|
|||||||
</Allotment.Pane>
|
</Allotment.Pane>
|
||||||
</Allotment>
|
</Allotment>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</CosmosFluentProvider>
|
</CosmosFluentProvider>
|
||||||
`;
|
`;
|
||||||
|
@ -39,7 +39,6 @@ exports[`DocumentsTableComponent should not render selection column when isSelec
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
onItemClicked={[Function]}
|
|
||||||
onRefreshTable={[Function]}
|
onRefreshTable={[Function]}
|
||||||
onSelectedRowsChange={[Function]}
|
onSelectedRowsChange={[Function]}
|
||||||
selectedColumnIds={[]}
|
selectedColumnIds={[]}
|
||||||
@ -504,7 +503,6 @@ exports[`DocumentsTableComponent should render documents and partition keys in h
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
onItemClicked={[Function]}
|
|
||||||
onRefreshTable={[Function]}
|
onRefreshTable={[Function]}
|
||||||
onSelectedRowsChange={[Function]}
|
onSelectedRowsChange={[Function]}
|
||||||
selectedColumnIds={[]}
|
selectedColumnIds={[]}
|
||||||
|
@ -52,6 +52,8 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
public partitionKeyProperties: string[];
|
public partitionKeyProperties: string[];
|
||||||
public id: ko.Observable<string>;
|
public id: ko.Observable<string>;
|
||||||
public defaultTtl: ko.Observable<number>;
|
public defaultTtl: ko.Observable<number>;
|
||||||
|
public vectorEmbeddingPolicy: ko.Observable<DataModels.VectorEmbeddingPolicy>;
|
||||||
|
public fullTextPolicy: ko.Observable<DataModels.FullTextPolicy>;
|
||||||
public indexingPolicy: ko.Observable<DataModels.IndexingPolicy>;
|
public indexingPolicy: ko.Observable<DataModels.IndexingPolicy>;
|
||||||
public uniqueKeyPolicy: DataModels.UniqueKeyPolicy;
|
public uniqueKeyPolicy: DataModels.UniqueKeyPolicy;
|
||||||
public usageSizeInKB: ko.Observable<number>;
|
public usageSizeInKB: ko.Observable<number>;
|
||||||
@ -110,6 +112,8 @@ export default class Collection implements ViewModels.Collection {
|
|||||||
|
|
||||||
this.id = ko.observable(data.id);
|
this.id = ko.observable(data.id);
|
||||||
this.defaultTtl = ko.observable(data.defaultTtl);
|
this.defaultTtl = ko.observable(data.defaultTtl);
|
||||||
|
this.vectorEmbeddingPolicy = ko.observable(data.vectorEmbeddingPolicy);
|
||||||
|
this.fullTextPolicy = ko.observable(data.fullTextPolicy);
|
||||||
this.indexingPolicy = ko.observable(data.indexingPolicy);
|
this.indexingPolicy = ko.observable(data.indexingPolicy);
|
||||||
this.usageSizeInKB = ko.observable();
|
this.usageSizeInKB = ko.observable();
|
||||||
this.offer = ko.observable();
|
this.offer = ko.observable();
|
||||||
|
@ -75,6 +75,7 @@ export interface UserContext {
|
|||||||
readonly masterKey?: string;
|
readonly masterKey?: string;
|
||||||
readonly subscriptionId?: string;
|
readonly subscriptionId?: string;
|
||||||
readonly tenantId?: string;
|
readonly tenantId?: string;
|
||||||
|
readonly userName?: string;
|
||||||
readonly resourceGroup?: string;
|
readonly resourceGroup?: string;
|
||||||
readonly databaseAccount?: DatabaseAccount;
|
readonly databaseAccount?: DatabaseAccount;
|
||||||
readonly endpoint?: string;
|
readonly endpoint?: string;
|
||||||
|
@ -91,7 +91,8 @@ export async function acquireMsalTokenForAccount(
|
|||||||
// This will eventually throw InteractionRequiredAuthError if silent is true, we won't handle it here.
|
// This will eventually throw InteractionRequiredAuthError if silent is true, we won't handle it here.
|
||||||
const loginRequest = {
|
const loginRequest = {
|
||||||
scopes: [hrefEndpoint],
|
scopes: [hrefEndpoint],
|
||||||
loginHint: user_hint,
|
loginHint: user_hint ?? userContext.userName,
|
||||||
|
authority: userContext.tenantId ? `${configContext.AAD_ENDPOINT}${userContext.tenantId}` : undefined,
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
if (silent) {
|
if (silent) {
|
||||||
@ -132,7 +133,8 @@ export async function acquireMsalTokenForAccount(
|
|||||||
account: msalAccount || null,
|
account: msalAccount || null,
|
||||||
forceRefresh: true,
|
forceRefresh: true,
|
||||||
scopes: [hrefEndpoint],
|
scopes: [hrefEndpoint],
|
||||||
authority: `${configContext.AAD_ENDPOINT}${msalAccount.tenantId}`,
|
loginHint: user_hint ?? userContext.userName,
|
||||||
|
authority: `${configContext.AAD_ENDPOINT}${userContext.tenantId ?? msalAccount.tenantId}`,
|
||||||
};
|
};
|
||||||
return acquireTokenWithMsal(msalInstance, tokenRequest, silent);
|
return acquireTokenWithMsal(msalInstance, tokenRequest, silent);
|
||||||
}
|
}
|
||||||
|
@ -20,3 +20,7 @@ export const isServerlessAccount = (): boolean => {
|
|||||||
export const isVectorSearchEnabled = (): boolean => {
|
export const isVectorSearchEnabled = (): boolean => {
|
||||||
return userContext.apiType === "SQL" && isCapabilityEnabled(Constants.CapabilityNames.EnableNoSQLVectorSearch);
|
return userContext.apiType === "SQL" && isCapabilityEnabled(Constants.CapabilityNames.EnableNoSQLVectorSearch);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const isFullTextSearchEnabled = (): boolean => {
|
||||||
|
return userContext.apiType === "SQL" && isCapabilityEnabled(Constants.CapabilityNames.EnableNoSQLFullTextSearch);
|
||||||
|
};
|
||||||
|
@ -1237,6 +1237,7 @@ export interface SqlContainerResource {
|
|||||||
id: string;
|
id: string;
|
||||||
|
|
||||||
vectorEmbeddingPolicy?: VectorEmbeddingPolicy;
|
vectorEmbeddingPolicy?: VectorEmbeddingPolicy;
|
||||||
|
fullTextPolicy?: FullTextPolicy;
|
||||||
|
|
||||||
/* The configuration of the indexing policy. By default, the indexing is automatic for all document paths within the container */
|
/* The configuration of the indexing policy. By default, the indexing is automatic for all document paths within the container */
|
||||||
indexingPolicy?: IndexingPolicy;
|
indexingPolicy?: IndexingPolicy;
|
||||||
@ -1281,6 +1282,28 @@ export interface VectorEmbedding {
|
|||||||
distanceFunction?: string;
|
distanceFunction?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface FullTextPolicy {
|
||||||
|
/**
|
||||||
|
* The default language for the full text .
|
||||||
|
*/
|
||||||
|
defaultLanguage: string;
|
||||||
|
/**
|
||||||
|
* The paths to be indexed for full text search.
|
||||||
|
*/
|
||||||
|
fullTextPaths: FullTextPath[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FullTextPath {
|
||||||
|
/**
|
||||||
|
* The path to be indexed for full text search.
|
||||||
|
*/
|
||||||
|
path: string;
|
||||||
|
/**
|
||||||
|
* The language for the full text path.
|
||||||
|
*/
|
||||||
|
language: string;
|
||||||
|
}
|
||||||
|
|
||||||
/* Cosmos DB indexing policy */
|
/* Cosmos DB indexing policy */
|
||||||
export interface IndexingPolicy {
|
export interface IndexingPolicy {
|
||||||
/* Indicates if the indexing policy is automatic */
|
/* Indicates if the indexing policy is automatic */
|
||||||
@ -1301,6 +1324,8 @@ export interface IndexingPolicy {
|
|||||||
spatialIndexes?: SpatialSpec[];
|
spatialIndexes?: SpatialSpec[];
|
||||||
|
|
||||||
vectorIndexes?: VectorIndex[];
|
vectorIndexes?: VectorIndex[];
|
||||||
|
|
||||||
|
fullTextIndexes?: FullTextIndex[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface VectorIndex {
|
export interface VectorIndex {
|
||||||
@ -1308,6 +1333,11 @@ export interface VectorIndex {
|
|||||||
type?: string;
|
type?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface FullTextIndex {
|
||||||
|
/** The path in the JSON document to index. */
|
||||||
|
path: string;
|
||||||
|
}
|
||||||
|
|
||||||
/* undocumented */
|
/* undocumented */
|
||||||
export interface ExcludedPath {
|
export interface ExcludedPath {
|
||||||
/* The path for which the indexing behavior applies to. Index paths typically start with root and end with wildcard (/path/*) */
|
/* The path for which the indexing behavior applies to. Index paths typically start with root and end with wildcard (/path/*) */
|
||||||
|
@ -695,6 +695,7 @@ function updateContextsFromPortalMessage(inputs: DataExplorerInputsFrame) {
|
|||||||
subscriptionId: inputs.subscriptionId,
|
subscriptionId: inputs.subscriptionId,
|
||||||
tenantId: inputs.tenantId,
|
tenantId: inputs.tenantId,
|
||||||
subscriptionType: inputs.subscriptionType,
|
subscriptionType: inputs.subscriptionType,
|
||||||
|
userName: inputs.userName,
|
||||||
quotaId: inputs.quotaId,
|
quotaId: inputs.quotaId,
|
||||||
portalEnv: inputs.serverId as PortalEnv,
|
portalEnv: inputs.serverId as PortalEnv,
|
||||||
hasWriteAccess: inputs.hasWriteAccess ?? true,
|
hasWriteAccess: inputs.hasWriteAccess ?? true,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user