Compare commits

..

2 Commits

Author SHA1 Message Date
Laurent Nguyen
d8ffcc4591 Merge branch 'master' into users/languy/fabric-appdevbtn-openssettings 2025-05-02 12:13:21 +02:00
Laurent Nguyen
e220e0e74b Fabric Home App Dev button opens Fabric UX extension Settings Connection tab 2025-04-29 14:31:56 +02:00
113 changed files with 1332 additions and 3795 deletions

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
<svg width="15" height="15" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" overflow="hidden"><defs><clipPath id="clip0"><rect x="479" y="279" width="15" height="15"/></clipPath><clipPath id="clip1"><rect x="-0.287396" y="-0.171573" width="152381" height="152381"/></clipPath><image width="35" height="35" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAMAAAApB0NrAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAH4UExURQAAAASExCGs7CC//wB9vQB5uxSU1yaz8ySy9AB8uwB8vAB5uxOR1Say8yax8iaw8gB6vAB6uwB7uwB4uROQ1Cax8ySy8QCAvwB3ugB5ugB2uROP1CWw8yav8wCT2QCQ1QCP1wB2uQB1uBOO0yWv8yat8wCP1QCP1QCP1QCO1QCQ1wB1tQB2uAB3uAB1tx+k6SSt8ySt8QCN0wCO1ACM0wBzuQBztAB1twB0tgB0tiSr8SSs8gCM1ACM1ACEywBwtQBxtAB0tgBztQB0sySp8SSr8gCL0wCK0gCL0wCHzwByuABusgBztQBttiOq8gB6wQCI0QCJ0gB7wySn8SOo8gBxtABtsABwtgCFzwCI0gCG0QCJ0SKn8SKn8iKl8QBvsABvsgBvsQBtsABssgCCzACG0QCF0ACDzyKm8QBtrwBusABsrgCAzQCE0ACF0ACF0ACE0CKj8SKl8QBssQBtrwBtrwBsrgBsrwCK1QCBzwCD0ACA0B2d7CGj8QBsrABrrQBwrwCA3wCC0ACCzwCBzwB+zhGM3iGi8SKh8QCAzQCAzgCAzwB9zRCK3iCh8SGh8SCf8QB+zAB/zgB8zRCJ3SCg8SCf7wB+zQB6zBCI3CCe8CCf8CCe8CCf8SCf/wB7zAB4yxGJ3h6c8CCd7yCf7wmA0RqX60C//5CaUeMAAACodFJOUwA8XAho+//ncID/////53iM//////9wCKv/////gCiYILf///+AMPP/70wYw/+3lP+AXP+MKNv/+3uA/3z/+////+NAgP9c9/////+7HP+A///MgP9Y+////7scgP+AeP/////7/+NA/2D/iyTb//t4gP808//vUBjD/7eU/yifIAi3//////+Ar////////4CI/////3B//////+9/CGj7/+dwEDhYBCm1XqwAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAFpSURBVDhPY2AYBaiAkYkZXQgdsLCysXPgV8XJxc3Dy8vHj0eVgKCQsIioqKioGLoMDIhLSEpKSknLyMjIyKLLyckrgJUoCgsLCyspq6ioqKiiKVFT19DUYmDQ1tEFAT19AwMDA0M0NUbGxsbGJqZm5ubm5haWDFbW1tbWVmhqGGxsbW1t7ewdHB2dnBkYXFxdXV1d0NUwuLl7eHh4enn7+DIwMLj4+fn5Yaph8A8IDAwMDBIHsYNDQkJCgtFVMISGhUdERkZGRkUzMDDExMbGxsahK4lPSExKTklNTU1NS2dgiMvIyMhAV5OZlZWVlZ2Tm5eXl5dfwFBYVFRUVIimpriktKycgaGisgoEqmtqa2tr0dUw1NU3gKjGpuaWlpbWtvb29vYOdDUw0NjZ1dXd09vX39c3AV0OASZOmjR5ytSpU6dOQ5dBAtN7ZsycNXvO3HnoEshg/oKFixYvQRdFA0uXLUcXGqEAAH4FV0z+qQbjAAAAAElFTkSuQmCC" preserveAspectRatio="none" id="img2"></image><clipPath id="clip3"><path d="M44291.4 46947.4 187148 46947.4 187148 188823 44291.4 188823Z" fill-rule="evenodd" clip-rule="evenodd"/></clipPath></defs><g clip-path="url(#clip0)" transform="translate(-479 -279)"><g clip-path="url(#clip1)" transform="matrix(0.000105 0 0 0.000105 479 279)"><g clip-path="url(#clip3)" transform="matrix(1 0 0 1.00692 -44291.4 -47272.4)"><use width="100%" height="100%" xlink:href="#img2" transform="scale(6709.45 6709.45)"></use></g></g></g></svg>

Before

Width:  |  Height:  |  Size: 2.4 KiB

View File

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

View File

@@ -211,12 +211,3 @@ a:focus {
.fileImportImg img { .fileImportImg img {
filter: brightness(0) saturate(100%); filter: brightness(0) saturate(100%);
} }
.tabPanesContainer {
overflow: auto !important;
}
.tabs-container {
min-height: 500px;
min-width: 500px;
}

211
package-lock.json generated
View File

@@ -10,7 +10,7 @@
"hasInstallScript": true, "hasInstallScript": true,
"dependencies": { "dependencies": {
"@azure/arm-cosmosdb": "9.1.0", "@azure/arm-cosmosdb": "9.1.0",
"@azure/cosmos": "4.3.0", "@azure/cosmos": "4.2.0-beta.1",
"@azure/cosmos-language-service": "0.0.5", "@azure/cosmos-language-service": "0.0.5",
"@azure/identity": "4.5.0", "@azure/identity": "4.5.0",
"@azure/msal-browser": "2.14.2", "@azure/msal-browser": "2.14.2",
@@ -290,71 +290,59 @@
"version": "2.6.2", "version": "2.6.2",
"license": "0BSD" "license": "0BSD"
}, },
"node_modules/@azure/core-http-compat": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@azure/core-http-compat/-/core-http-compat-2.3.0.tgz",
"integrity": "sha512-qLQujmUypBBG0gxHd0j6/Jdmul6ttl24c8WGiLXIk7IHXdBlfoBqW27hyz3Xn6xbfdyVSarl1Ttbk0AwnZBYCw==",
"dependencies": {
"@azure/abort-controller": "^2.0.0",
"@azure/core-client": "^1.3.0",
"@azure/core-rest-pipeline": "^1.20.0"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/@azure/core-lro": {
"version": "2.7.2",
"resolved": "https://registry.npmjs.org/@azure/core-lro/-/core-lro-2.7.2.tgz",
"integrity": "sha512-0YIpccoX8m/k00O7mDDMdJpbr6mf1yWo2dfmxt5A8XVZVVMz2SSKaEbMCeJRvgQ0IaSlqhjT47p4hVIRRy90xw==",
"dependencies": {
"@azure/abort-controller": "^2.0.0",
"@azure/core-util": "^1.2.0",
"@azure/logger": "^1.0.0",
"tslib": "^2.6.2"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/@azure/core-lro/node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
},
"node_modules/@azure/core-paging": {
"version": "1.6.2",
"resolved": "https://registry.npmjs.org/@azure/core-paging/-/core-paging-1.6.2.tgz",
"integrity": "sha512-YKWi9YuCU04B55h25cnOYZHxXYtEvQEbKST5vqRga7hWY9ydd3FZHdeQF8pyh+acWZvppw13M/LMGx0LABUVMA==",
"dependencies": {
"tslib": "^2.6.2"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/@azure/core-paging/node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
},
"node_modules/@azure/core-rest-pipeline": { "node_modules/@azure/core-rest-pipeline": {
"version": "1.20.0", "version": "1.18.0",
"resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.20.0.tgz", "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.18.0.tgz",
"integrity": "sha512-ASoP8uqZBS3H/8N8at/XwFr6vYrRP3syTK0EUjDXQy0Y1/AUS+QeIRThKmTNJO2RggvBBxaXDPM7YoIwDGeA0g==", "integrity": "sha512-QSoGUp4Eq/gohEFNJaUOwTN7BCc2nHTjjbm75JT0aD7W65PWM1H/tItz0GsABn22uaKyGxiMhWQLt2r+FGU89Q==",
"dependencies": { "dependencies": {
"@azure/abort-controller": "^2.0.0", "@azure/abort-controller": "^2.0.0",
"@azure/core-auth": "^1.8.0", "@azure/core-auth": "^1.8.0",
"@azure/core-tracing": "^1.0.1", "@azure/core-tracing": "^1.0.1",
"@azure/core-util": "^1.11.0", "@azure/core-util": "^1.11.0",
"@azure/logger": "^1.0.0", "@azure/logger": "^1.0.0",
"@typespec/ts-http-runtime": "^0.2.2", "http-proxy-agent": "^7.0.0",
"https-proxy-agent": "^7.0.0",
"tslib": "^2.6.2" "tslib": "^2.6.2"
}, },
"engines": { "engines": {
"node": ">=18.0.0" "node": ">=18.0.0"
} }
}, },
"node_modules/@azure/core-rest-pipeline/node_modules/agent-base": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz",
"integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==",
"dependencies": {
"debug": "^4.3.4"
},
"engines": {
"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": {
"version": "2.6.2", "version": "2.6.2",
"license": "0BSD" "license": "0BSD"
@@ -391,16 +379,15 @@
"license": "0BSD" "license": "0BSD"
}, },
"node_modules/@azure/cosmos": { "node_modules/@azure/cosmos": {
"version": "4.3.0", "version": "4.2.0-beta.1",
"resolved": "https://registry.npmjs.org/@azure/cosmos/-/cosmos-4.3.0.tgz", "resolved": "https://registry.npmjs.org/@azure/cosmos/-/cosmos-4.2.0-beta.1.tgz",
"integrity": "sha512-0Ls3l1uWBBSphx6YRhnM+w7rSvq8qVugBCdO6kSiNuRYXEf6+YWLjbzz4e7L2kkz/6ScFdZIOJYP+XtkiRYOhA==", "integrity": "sha512-mREONehm1DxjEKXGaNU6Wmpf9Ckb9IrhKFXhDFVs45pxmoEb3y2s/Ub0owuFmqlphpcS1zgtYQn5exn+lwnJuQ==",
"dependencies": { "dependencies": {
"@azure/abort-controller": "^2.0.0", "@azure/abort-controller": "^2.0.0",
"@azure/core-auth": "^1.7.1", "@azure/core-auth": "^1.7.1",
"@azure/core-rest-pipeline": "^1.15.1", "@azure/core-rest-pipeline": "^1.15.1",
"@azure/core-tracing": "^1.1.1", "@azure/core-tracing": "^1.1.1",
"@azure/core-util": "^1.8.1", "@azure/core-util": "^1.8.1",
"@azure/keyvault-keys": "^4.8.0",
"fast-json-stable-stringify": "^2.1.0", "fast-json-stable-stringify": "^2.1.0",
"jsbi": "^4.3.0", "jsbi": "^4.3.0",
"priorityqueuejs": "^2.0.0", "priorityqueuejs": "^2.0.0",
@@ -505,66 +492,14 @@
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
}, },
"node_modules/@azure/keyvault-common": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@azure/keyvault-common/-/keyvault-common-2.0.0.tgz",
"integrity": "sha512-wRLVaroQtOqfg60cxkzUkGKrKMsCP6uYXAOomOIysSMyt1/YM0eUn9LqieAWM8DLcU4+07Fio2YGpPeqUbpP9w==",
"dependencies": {
"@azure/abort-controller": "^2.0.0",
"@azure/core-auth": "^1.3.0",
"@azure/core-client": "^1.5.0",
"@azure/core-rest-pipeline": "^1.8.0",
"@azure/core-tracing": "^1.0.0",
"@azure/core-util": "^1.10.0",
"@azure/logger": "^1.1.4",
"tslib": "^2.2.0"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/@azure/keyvault-common/node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
},
"node_modules/@azure/keyvault-keys": {
"version": "4.9.0",
"resolved": "https://registry.npmjs.org/@azure/keyvault-keys/-/keyvault-keys-4.9.0.tgz",
"integrity": "sha512-ZBP07+K4Pj3kS4TF4XdkqFcspWwBHry3vJSOFM5k5ZABvf7JfiMonvaFk2nBF6xjlEbMpz5PE1g45iTMme0raQ==",
"dependencies": {
"@azure/abort-controller": "^2.0.0",
"@azure/core-auth": "^1.3.0",
"@azure/core-client": "^1.5.0",
"@azure/core-http-compat": "^2.0.1",
"@azure/core-lro": "^2.2.0",
"@azure/core-paging": "^1.1.1",
"@azure/core-rest-pipeline": "^1.8.1",
"@azure/core-tracing": "^1.0.0",
"@azure/core-util": "^1.0.0",
"@azure/keyvault-common": "^2.0.0",
"@azure/logger": "^1.0.0",
"tslib": "^2.2.0"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/@azure/keyvault-keys/node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
},
"node_modules/@azure/logger": { "node_modules/@azure/logger": {
"version": "1.2.0", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.2.0.tgz", "license": "MIT",
"integrity": "sha512-0hKEzLhpw+ZTAfNJyRrn6s+V0nDWzXk9OjBr2TiGIu0OfMr5s2V4FpKLTAK3Ca5r5OKLbf4hkOGDPyiRjie/jA==",
"dependencies": { "dependencies": {
"@typespec/ts-http-runtime": "^0.2.2", "tslib": "^2.2.0"
"tslib": "^2.6.2"
}, },
"engines": { "engines": {
"node": ">=18.0.0" "node": ">=14.0.0"
} }
}, },
"node_modules/@azure/logger/node_modules/tslib": { "node_modules/@azure/logger/node_modules/tslib": {
@@ -13139,56 +13074,6 @@
"url": "https://opencollective.com/typescript-eslint" "url": "https://opencollective.com/typescript-eslint"
} }
}, },
"node_modules/@typespec/ts-http-runtime": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/@typespec/ts-http-runtime/-/ts-http-runtime-0.2.2.tgz",
"integrity": "sha512-Gz/Sm64+Sq/vklJu1tt9t+4R2lvnud8NbTD/ZfpZtMiUX7YeVpCA8j6NSW8ptwcoLL+NmYANwqP8DV0q/bwl2w==",
"dependencies": {
"http-proxy-agent": "^7.0.0",
"https-proxy-agent": "^7.0.0",
"tslib": "^2.6.2"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/@typespec/ts-http-runtime/node_modules/agent-base": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz",
"integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==",
"engines": {
"node": ">= 14"
}
},
"node_modules/@typespec/ts-http-runtime/node_modules/http-proxy-agent": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
"integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
"dependencies": {
"agent-base": "^7.1.0",
"debug": "^4.3.4"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/@typespec/ts-http-runtime/node_modules/https-proxy-agent": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
"integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
"dependencies": {
"agent-base": "^7.1.2",
"debug": "4"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/@typespec/ts-http-runtime/node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
},
"node_modules/@ungap/url-search-params": { "node_modules/@ungap/url-search-params": {
"version": "0.2.2", "version": "0.2.2",
"license": "ISC" "license": "ISC"

View File

@@ -5,7 +5,7 @@
"main": "index.js", "main": "index.js",
"dependencies": { "dependencies": {
"@azure/arm-cosmosdb": "9.1.0", "@azure/arm-cosmosdb": "9.1.0",
"@azure/cosmos": "4.3.0", "@azure/cosmos": "4.2.0-beta.1",
"@azure/cosmos-language-service": "0.0.5", "@azure/cosmos-language-service": "0.0.5",
"@azure/identity": "4.5.0", "@azure/identity": "4.5.0",
"@azure/msal-browser": "2.14.2", "@azure/msal-browser": "2.14.2",

View File

@@ -10,7 +10,7 @@
"hasInstallScript": true, "hasInstallScript": true,
"dependencies": { "dependencies": {
"@azure/arm-cosmosdb": "9.1.0", "@azure/arm-cosmosdb": "9.1.0",
"@azure/cosmos": "4.3.0", "@azure/cosmos": "4.2.0-beta.1",
"@azure/cosmos-language-service": "0.0.5", "@azure/cosmos-language-service": "0.0.5",
"@azure/identity": "4.5.0", "@azure/identity": "4.5.0",
"@azure/msal-browser": "2.14.2", "@azure/msal-browser": "2.14.2",
@@ -377,8 +377,8 @@
"license": "0BSD" "license": "0BSD"
}, },
"node_modules/@azure/cosmos": { "node_modules/@azure/cosmos": {
"version": "4.3.0", "version": "4.2.0-beta.1",
"resolved": "https://registry.npmjs.org/@azure/cosmos/-/cosmos-4.3.0.tgz", "resolved": "https://registry.npmjs.org/@azure/cosmos/-/cosmos-4.2.0-beta.1.tgz",
"integrity": "sha512-mREONehm1DxjEKXGaNU6Wmpf9Ckb9IrhKFXhDFVs45pxmoEb3y2s/Ub0owuFmqlphpcS1zgtYQn5exn+lwnJuQ==", "integrity": "sha512-mREONehm1DxjEKXGaNU6Wmpf9Ckb9IrhKFXhDFVs45pxmoEb3y2s/Ub0owuFmqlphpcS1zgtYQn5exn+lwnJuQ==",
"dependencies": { "dependencies": {
"@azure/abort-controller": "^2.0.0", "@azure/abort-controller": "^2.0.0",

View File

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

View File

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

View File

@@ -4,18 +4,13 @@ import * as React from "react";
export interface TooltipProps { export interface TooltipProps {
children: string; children: string;
className?: string; className?: string;
ariaLabelForTooltip?: string;
} }
export const InfoTooltip: React.FunctionComponent<TooltipProps> = ({ export const InfoTooltip: React.FunctionComponent<TooltipProps> = ({ children, className }: TooltipProps) => {
children,
className,
ariaLabelForTooltip = children,
}: TooltipProps) => {
return ( return (
<span className={className}> <span className={className}>
<TooltipHost content={children}> <TooltipHost content={children}>
<Icon iconName="Info" aria-label={ariaLabelForTooltip} className="panelInfoIcon" tabIndex={0} /> <Icon iconName="Info" ariaLabel={children} className="panelInfoIcon" tabIndex={0} />
</TooltipHost> </TooltipHost>
</span> </span>
); );

View File

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

View File

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

View File

@@ -26,7 +26,6 @@ export const getCommonQueryOptions = (options: FeedOptions): FeedOptions => {
options.maxItemCount || options.maxItemCount ||
(storedItemPerPageSetting !== undefined && storedItemPerPageSetting) || (storedItemPerPageSetting !== undefined && storedItemPerPageSetting) ||
Queries.itemsPerPage; Queries.itemsPerPage;
options.enableQueryControl = LocalStorageUtility.getEntryBoolean(StorageKey.QueryControlEnabled);
options.maxDegreeOfParallelism = LocalStorageUtility.getEntryNumber(StorageKey.MaxDegreeOfParellism); options.maxDegreeOfParallelism = LocalStorageUtility.getEntryNumber(StorageKey.MaxDegreeOfParellism);
options.disableNonStreamingOrderByQuery = !isVectorSearchEnabled(); options.disableNonStreamingOrderByQuery = !isVectorSearchEnabled();
return options; return options;

View File

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

View File

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

View File

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

View File

@@ -21,7 +21,7 @@ export type DataExploreMessageV3 =
} }
| { | {
type: FabricMessageTypes.OpenSettings; type: FabricMessageTypes.OpenSettings;
settingsId: string; params: [{ settingsId?: "About" | "Connection" }];
}; };
export interface GetCosmosTokenMessageOptions { export interface GetCosmosTokenMessageOptions {
verb: "connect" | "delete" | "get" | "head" | "options" | "patch" | "post" | "put" | "trace"; verb: "connect" | "delete" | "get" | "head" | "options" | "patch" | "post" | "put" | "trace";

View File

@@ -7,7 +7,6 @@ export interface ArmEntity {
type: string; type: string;
kind: string; kind: string;
tags?: Tags; tags?: Tags;
resourceGroup?: string;
} }
export interface DatabaseAccount extends ArmEntity { export interface DatabaseAccount extends ArmEntity {
@@ -211,7 +210,7 @@ export interface IndexingPolicy {
export interface VectorIndex { export interface VectorIndex {
path: string; path: string;
type: "flat" | "diskANN" | "quantizedFlat"; type: "flat" | "diskANN" | "quantizedFlat";
vectorIndexShardKey?: string[]; diskANNShardKey?: string;
indexingSearchListSize?: number; indexingSearchListSize?: number;
quantizationByteSize?: number; quantizationByteSize?: number;
} }
@@ -389,7 +388,7 @@ export interface VectorEmbeddingPolicy {
} }
export interface VectorEmbedding { export interface VectorEmbedding {
dataType: "float32" | "uint8" | "int8"; dataType: "float16" | "float32" | "uint8" | "int8";
dimensions: number; dimensions: number;
distanceFunction: "euclidean" | "cosine" | "dotproduct"; distanceFunction: "euclidean" | "cosine" | "dotproduct";
path: string; path: string;

View File

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

View File

@@ -1,6 +1,7 @@
import { AuthType } from "AuthType"; import { AuthType } from "AuthType";
import { shallow } from "enzyme"; import { shallow } from "enzyme";
import ko from "knockout"; import ko from "knockout";
import { Features } from "Platform/Hosted/extractFeatures";
import React from "react"; import React from "react";
import { updateCollection } from "../../../Common/dataAccess/updateCollection"; import { updateCollection } from "../../../Common/dataAccess/updateCollection";
import { updateOffer } from "../../../Common/dataAccess/updateOffer"; import { updateOffer } from "../../../Common/dataAccess/updateOffer";
@@ -252,7 +253,7 @@ describe("SettingsComponent", () => {
it("should save throughput bucket changes when Save button is clicked", async () => { it("should save throughput bucket changes when Save button is clicked", async () => {
updateUserContext({ updateUserContext({
apiType: "SQL", apiType: "SQL",
throughputBucketsEnabled: true, features: { enableThroughputBuckets: true } as Features,
authType: AuthType.AAD, authType: AuthType.AAD,
}); });

View File

@@ -13,7 +13,7 @@ import {
} from "Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/ThroughputBucketsComponent"; } from "Explorer/Controls/Settings/SettingsSubComponents/ThroughputInputComponents/ThroughputBucketsComponent";
import { useDatabases } from "Explorer/useDatabases"; import { useDatabases } from "Explorer/useDatabases";
import { isFabricNative } from "Platform/Fabric/FabricUtil"; import { isFabricNative } from "Platform/Fabric/FabricUtil";
import { 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";
@@ -188,10 +188,13 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
this.isGlobalSecondaryIndex = this.isGlobalSecondaryIndex =
!!this.collection?.materializedViewDefinition() || !!this.collection?.materializedViews(); !!this.collection?.materializedViewDefinition() || !!this.collection?.materializedViews();
this.isVectorSearchEnabled = isVectorSearchEnabled() && !hasDatabaseSharedThroughput(this.collection); this.isVectorSearchEnabled = isVectorSearchEnabled() && !hasDatabaseSharedThroughput(this.collection);
this.isFullTextSearchEnabled = userContext.apiType === "SQL"; this.isFullTextSearchEnabled = isFullTextSearchEnabled() && !hasDatabaseSharedThroughput(this.collection);
this.changeFeedPolicyVisible = userContext.features.enableChangeFeedPolicy; this.changeFeedPolicyVisible = userContext.features.enableChangeFeedPolicy;
this.throughputBucketsEnabled = userContext.throughputBucketsEnabled; this.throughputBucketsEnabled =
userContext.apiType === "SQL" &&
userContext.features.enableThroughputBuckets &&
userContext.authType === AuthType.AAD;
// Mongo container with system partition key still treat as "Fixed" // Mongo container with system partition key still treat as "Fixed"
this.isFixedContainer = this.isFixedContainer =
@@ -1071,11 +1074,11 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
databaseId: this.collection.databaseId, databaseId: this.collection.databaseId,
collectionId: this.collection.id(), collectionId: this.collection.id(),
currentOffer: this.collection.offer(), currentOffer: this.collection.offer(),
autopilotThroughput: this.collection.offer?.()?.autoscaleMaxThroughput autopilotThroughput: this.collection.offer().autoscaleMaxThroughput
? this.collection.offer?.()?.autoscaleMaxThroughput ? this.collection.offer().autoscaleMaxThroughput
: undefined, : undefined,
manualThroughput: this.collection.offer?.()?.manualThroughput manualThroughput: this.collection.offer().manualThroughput
? this.collection.offer?.()?.manualThroughput ? this.collection.offer().manualThroughput
: undefined, : undefined,
throughputBuckets: this.state.throughputBuckets, throughputBuckets: this.state.throughputBuckets,
}); });
@@ -1091,7 +1094,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
currentOffer: this.collection.offer(), currentOffer: this.collection.offer(),
autopilotThroughput: this.state.isAutoPilotSelected ? this.state.autoPilotThroughput : undefined, autopilotThroughput: this.state.isAutoPilotSelected ? this.state.autoPilotThroughput : undefined,
manualThroughput: this.state.isAutoPilotSelected ? undefined : this.state.throughput, manualThroughput: this.state.isAutoPilotSelected ? undefined : this.state.throughput,
throughputBuckets: this.throughputBucketsEnabled ? this.state.throughputBuckets : undefined,
}; };
if (this.hasProvisioningTypeChanged()) { if (this.hasProvisioningTypeChanged()) {
if (this.state.isAutoPilotSelected) { if (this.state.isAutoPilotSelected) {
@@ -1213,7 +1215,6 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
isFullTextSearchEnabled: this.isFullTextSearchEnabled, isFullTextSearchEnabled: this.isFullTextSearchEnabled,
shouldDiscardContainerPolicies: this.state.shouldDiscardContainerPolicies, shouldDiscardContainerPolicies: this.state.shouldDiscardContainerPolicies,
resetShouldDiscardContainerPolicyChange: this.resetShouldDiscardContainerPolicies, resetShouldDiscardContainerPolicyChange: this.resetShouldDiscardContainerPolicies,
isGlobalSecondaryIndex: this.isGlobalSecondaryIndex,
}; };
const indexingPolicyComponentProps: IndexingPolicyComponentProps = { const indexingPolicyComponentProps: IndexingPolicyComponentProps = {
@@ -1342,7 +1343,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
}); });
} }
if (this.throughputBucketsEnabled && !hasDatabaseSharedThroughput(this.collection) && this.offer) { if (this.throughputBucketsEnabled) {
tabs.push({ tabs.push({
tab: SettingsV2TabTypes.ThroughputBucketsTab, tab: SettingsV2TabTypes.ThroughputBucketsTab,
content: <ThroughputBucketsComponent {...throughputBucketsComponentProps} />, content: <ThroughputBucketsComponent {...throughputBucketsComponentProps} />,

View File

@@ -22,7 +22,6 @@ export interface ContainerPolicyComponentProps {
isFullTextSearchEnabled: boolean; isFullTextSearchEnabled: boolean;
shouldDiscardContainerPolicies: boolean; shouldDiscardContainerPolicies: boolean;
resetShouldDiscardContainerPolicyChange: () => void; resetShouldDiscardContainerPolicyChange: () => void;
isGlobalSecondaryIndex?: boolean;
} }
export const ContainerPolicyComponent: React.FC<ContainerPolicyComponentProps> = ({ export const ContainerPolicyComponent: React.FC<ContainerPolicyComponentProps> = ({

View File

@@ -143,39 +143,4 @@ describe("SubSettingsComponent", () => {
expect(subSettingsComponentInstance.getTtlValue(TtlType.On)).toEqual(TtlOn); expect(subSettingsComponentInstance.getTtlValue(TtlType.On)).toEqual(TtlOn);
expect(subSettingsComponentInstance.getTtlValue(TtlType.Off)).toEqual(TtlOff); expect(subSettingsComponentInstance.getTtlValue(TtlType.Off)).toEqual(TtlOff);
}); });
it("uniqueKey is visible", () => {
updateUserContext({
databaseAccount: {
properties: {
capabilities: [{ name: "EnableSQL" }],
},
} as DatabaseAccount,
});
const subSettingsComponent = new SubSettingsComponent(baseProps);
expect(subSettingsComponent.getUniqueKeyVisible()).toEqual(true);
});
it("uniqueKey not visible due to no keys", () => {
const props = {
...baseProps,
...(baseProps.collection.rawDataModel.uniqueKeyPolicy.uniqueKeys = []),
};
const subSettingsComponent = new SubSettingsComponent(props);
expect(subSettingsComponent.getUniqueKeyVisible()).toEqual(false);
});
it("uniqueKey not visible for API", () => {
const newContainer = new Explorer();
updateUserContext({
databaseAccount: {
properties: {
capabilities: [{ name: "EnableMongo" }],
},
} as DatabaseAccount,
});
const props = { ...baseProps, container: newContainer };
const subSettingsComponent = new SubSettingsComponent(props);
expect(subSettingsComponent.getUniqueKeyVisible()).toEqual(false);
});
}); });

View File

@@ -63,16 +63,12 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
private geospatialVisible: boolean; private geospatialVisible: boolean;
private partitionKeyValue: string; private partitionKeyValue: string;
private partitionKeyName: string; private partitionKeyName: string;
private uniqueKeyName: string;
private uniqueKeyValue: string;
constructor(props: SubSettingsComponentProps) { constructor(props: SubSettingsComponentProps) {
super(props); super(props);
this.geospatialVisible = userContext.apiType === "SQL"; this.geospatialVisible = userContext.apiType === "SQL";
this.partitionKeyName = userContext.apiType === "Mongo" ? "Shard key" : "Partition key"; this.partitionKeyName = userContext.apiType === "Mongo" ? "Shard key" : "Partition key";
this.partitionKeyValue = this.getPartitionKeyValue(); this.partitionKeyValue = this.getPartitionKeyValue();
this.uniqueKeyName = "Unique keys";
this.uniqueKeyValue = this.getUniqueKeyValue();
} }
componentDidMount(): void { componentDidMount(): void {
@@ -355,28 +351,6 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
public isLargePartitionKeyEnabled = (): boolean => this.props.collection.partitionKey?.version >= 2; public isLargePartitionKeyEnabled = (): boolean => this.props.collection.partitionKey?.version >= 2;
public isHierarchicalPartitionedContainer = (): boolean => this.props.collection.partitionKey?.kind === "MultiHash"; public isHierarchicalPartitionedContainer = (): boolean => this.props.collection.partitionKey?.kind === "MultiHash";
public getUniqueKeyVisible = (): boolean => {
return this.props.collection.rawDataModel.uniqueKeyPolicy?.uniqueKeys.length > 0 && userContext.apiType === "SQL";
};
private getUniqueKeyValue = (): string => {
const paths = this.props.collection.rawDataModel.uniqueKeyPolicy?.uniqueKeys?.[0]?.paths;
return paths?.join(", ") || "";
};
private getUniqueKeyComponent = (): JSX.Element => (
<Stack {...titleAndInputStackProps}>
{this.getUniqueKeyVisible() && (
<TextField
label={this.uniqueKeyName}
disabled
styles={getTextFieldStyles(undefined, undefined)}
defaultValue={this.uniqueKeyValue}
/>
)}
</Stack>
);
public render(): JSX.Element { public render(): JSX.Element {
return ( return (
<Stack {...subComponentStackProps}> <Stack {...subComponentStackProps}>
@@ -389,8 +363,6 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
{this.props.changeFeedPolicyVisible && this.getChangeFeedComponent()} {this.props.changeFeedPolicyVisible && this.getChangeFeedComponent()}
{this.getPartitionKeyComponent()} {this.getPartitionKeyComponent()}
{this.getUniqueKeyComponent()}
</Stack> </Stack>
); );
} }

View File

@@ -26,7 +26,7 @@ describe("ThroughputBucketsComponent", () => {
it("renders the correct number of buckets", () => { it("renders the correct number of buckets", () => {
render(<ThroughputBucketsComponent {...defaultProps} />); render(<ThroughputBucketsComponent {...defaultProps} />);
expect(screen.getAllByText(/Bucket \d+/)).toHaveLength(5); expect(screen.getAllByText(/Group \d+/)).toHaveLength(5);
}); });
it("renders buckets in the correct order even if input is unordered", () => { it("renders buckets in the correct order even if input is unordered", () => {
@@ -36,14 +36,8 @@ describe("ThroughputBucketsComponent", () => {
]; ];
render(<ThroughputBucketsComponent {...defaultProps} currentBuckets={unorderedBuckets} />); render(<ThroughputBucketsComponent {...defaultProps} currentBuckets={unorderedBuckets} />);
const bucketLabels = screen.getAllByText(/Bucket \d+/).map((el) => el.textContent); const bucketLabels = screen.getAllByText(/Group \d+/).map((el) => el.textContent);
expect(bucketLabels).toEqual([ expect(bucketLabels).toEqual(["Group 1 (Data Explorer Query Bucket)", "Group 2", "Group 3", "Group 4", "Group 5"]);
"Bucket 1 (Data Explorer Query Bucket)",
"Bucket 2",
"Bucket 3",
"Bucket 4",
"Bucket 5",
]);
}); });
it("renders all provided buckets even if they exceed the max default bucket count", () => { it("renders all provided buckets even if they exceed the max default bucket count", () => {
@@ -59,7 +53,7 @@ describe("ThroughputBucketsComponent", () => {
render(<ThroughputBucketsComponent {...defaultProps} currentBuckets={oversizedBuckets} />); render(<ThroughputBucketsComponent {...defaultProps} currentBuckets={oversizedBuckets} />);
expect(screen.getAllByText(/Bucket \d+/)).toHaveLength(7); expect(screen.getAllByText(/Group \d+/)).toHaveLength(7);
expect(screen.getByDisplayValue("50")).toBeInTheDocument(); expect(screen.getByDisplayValue("50")).toBeInTheDocument();
expect(screen.getByDisplayValue("60")).toBeInTheDocument(); expect(screen.getByDisplayValue("60")).toBeInTheDocument();
@@ -177,7 +171,7 @@ describe("ThroughputBucketsComponent", () => {
it("ensures default buckets are used when no buckets are provided", () => { it("ensures default buckets are used when no buckets are provided", () => {
render(<ThroughputBucketsComponent {...defaultProps} currentBuckets={[]} />); render(<ThroughputBucketsComponent {...defaultProps} currentBuckets={[]} />);
expect(screen.getAllByText(/Bucket \d+/)).toHaveLength(5); expect(screen.getAllByText(/Group \d+/)).toHaveLength(5);
expect(screen.getAllByDisplayValue("100")).toHaveLength(5); expect(screen.getAllByDisplayValue("100")).toHaveLength(5);
}); });
}); });

View File

@@ -76,7 +76,7 @@ export const ThroughputBucketsComponent: FC<ThroughputBucketsComponentProps> = (
value={bucket.maxThroughputPercentage} value={bucket.maxThroughputPercentage}
onChange={(newValue) => handleBucketChange(bucket.id, newValue)} onChange={(newValue) => handleBucketChange(bucket.id, newValue)}
showValue={false} showValue={false}
label={`Bucket ${bucket.id}${bucket.id === 1 ? " (Data Explorer Query Bucket)" : ""}`} label={`Group ${bucket.id}${bucket.id === 1 ? " (Data Explorer Query Bucket)" : ""}`}
styles={{ root: { flex: 2, maxWidth: 400 } }} styles={{ root: { flex: 2, maxWidth: 400 } }}
disabled={bucket.maxThroughputPercentage === 100} disabled={bucket.maxThroughputPercentage === 100}
/> />

View File

@@ -285,7 +285,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
serverId, serverId,
numberOfRegions, numberOfRegions,
isMultimaster, isMultimaster,
false, true,
); );
return ( return (
<div> <div>

View File

@@ -231,34 +231,6 @@ exports[`SubSettingsComponent analyticalTimeToLive hidden 1`] = `
Non-hierarchically partitioned container. Non-hierarchically partitioned container.
</Text> </Text>
</Stack> </Stack>
<Stack
tokens={
{
"childrenGap": 5,
}
}
>
<StyledTextFieldBase
defaultValue="/id"
disabled={true}
label="Unique keys"
styles={
{
"fieldGroup": {
"borderColor": "",
"height": 25,
"selectors": {
":disabled": {
"backgroundColor": undefined,
"borderColor": undefined,
},
},
"width": 300,
},
}
}
/>
</Stack>
</Stack> </Stack>
`; `;
@@ -548,34 +520,6 @@ exports[`SubSettingsComponent analyticalTimeToLiveSeconds hidden 1`] = `
Non-hierarchically partitioned container. Non-hierarchically partitioned container.
</Text> </Text>
</Stack> </Stack>
<Stack
tokens={
{
"childrenGap": 5,
}
}
>
<StyledTextFieldBase
defaultValue="/id"
disabled={true}
label="Unique keys"
styles={
{
"fieldGroup": {
"borderColor": "",
"height": 25,
"selectors": {
":disabled": {
"backgroundColor": undefined,
"borderColor": undefined,
},
},
"width": 300,
},
}
}
/>
</Stack>
</Stack> </Stack>
`; `;
@@ -825,34 +769,6 @@ exports[`SubSettingsComponent changeFeedPolicy hidden 1`] = `
Non-hierarchically partitioned container. Non-hierarchically partitioned container.
</Text> </Text>
</Stack> </Stack>
<Stack
tokens={
{
"childrenGap": 5,
}
}
>
<StyledTextFieldBase
defaultValue="/id"
disabled={true}
label="Unique keys"
styles={
{
"fieldGroup": {
"borderColor": "",
"height": 25,
"selectors": {
":disabled": {
"backgroundColor": undefined,
"borderColor": undefined,
},
},
"width": 300,
},
}
}
/>
</Stack>
</Stack> </Stack>
`; `;
@@ -1167,34 +1083,6 @@ exports[`SubSettingsComponent renders 1`] = `
Non-hierarchically partitioned container. Non-hierarchically partitioned container.
</Text> </Text>
</Stack> </Stack>
<Stack
tokens={
{
"childrenGap": 5,
}
}
>
<StyledTextFieldBase
defaultValue="/id"
disabled={true}
label="Unique keys"
styles={
{
"fieldGroup": {
"borderColor": "",
"height": 25,
"selectors": {
":disabled": {
"backgroundColor": undefined,
"borderColor": undefined,
},
},
"width": 300,
},
}
}
/>
</Stack>
</Stack> </Stack>
`; `;
@@ -1483,33 +1371,5 @@ exports[`SubSettingsComponent timeToLiveSeconds hidden 1`] = `
Non-hierarchically partitioned container. Non-hierarchically partitioned container.
</Text> </Text>
</Stack> </Stack>
<Stack
tokens={
{
"childrenGap": 5,
}
}
>
<StyledTextFieldBase
defaultValue="/id"
disabled={true}
label="Unique keys"
styles={
{
"fieldGroup": {
"borderColor": "",
"height": 25,
"selectors": {
":disabled": {
"backgroundColor": undefined,
"borderColor": undefined,
},
},
"width": 300,
},
}
}
/>
</Stack>
</Stack> </Stack>
`; `;

View File

@@ -17,15 +17,7 @@ export const collection = {
includedPaths: [], includedPaths: [],
excludedPaths: [], excludedPaths: [],
}), }),
rawDataModel: { uniqueKeyPolicy: {} as DataModels.UniqueKeyPolicy,
uniqueKeyPolicy: {
uniqueKeys: [
{
paths: ["/id"],
},
],
},
},
usageSizeInKB: ko.observable(100), usageSizeInKB: ko.observable(100),
offer: ko.observable<DataModels.Offer>({ offer: ko.observable<DataModels.Offer>({
autoscaleMaxThroughput: undefined, autoscaleMaxThroughput: undefined,

View File

@@ -71,18 +71,8 @@ exports[`SettingsComponent renders 1`] = `
"partitionKeyProperties": [ "partitionKeyProperties": [
"partitionKey", "partitionKey",
], ],
"rawDataModel": {
"uniqueKeyPolicy": {
"uniqueKeys": [
{
"paths": [
"/id",
],
},
],
},
},
"readSettings": [Function], "readSettings": [Function],
"uniqueKeyPolicy": {},
"usageSizeInKB": [Function], "usageSizeInKB": [Function],
"vectorEmbeddingPolicy": [Function], "vectorEmbeddingPolicy": [Function],
} }
@@ -163,18 +153,8 @@ exports[`SettingsComponent renders 1`] = `
"partitionKeyProperties": [ "partitionKeyProperties": [
"partitionKey", "partitionKey",
], ],
"rawDataModel": {
"uniqueKeyPolicy": {
"uniqueKeys": [
{
"paths": [
"/id",
],
},
],
},
},
"readSettings": [Function], "readSettings": [Function],
"uniqueKeyPolicy": {},
"usageSizeInKB": [Function], "usageSizeInKB": [Function],
"vectorEmbeddingPolicy": [Function], "vectorEmbeddingPolicy": [Function],
} }
@@ -198,32 +178,6 @@ exports[`SettingsComponent renders 1`] = `
timeToLiveSecondsBaseline={5} timeToLiveSecondsBaseline={5}
/> />
</PivotItem> </PivotItem>
<PivotItem
headerText="Container Policies"
itemKey="ContainerVectorPolicyTab"
key="ContainerVectorPolicyTab"
style={
{
"marginTop": 20,
}
}
>
<ContainerPolicyComponent
fullTextPolicy={{}}
fullTextPolicyBaseline={{}}
isFullTextSearchEnabled={true}
isGlobalSecondaryIndex={true}
isVectorSearchEnabled={false}
onFullTextPolicyChange={[Function]}
onFullTextPolicyDirtyChange={[Function]}
onVectorEmbeddingPolicyChange={[Function]}
onVectorEmbeddingPolicyDirtyChange={[Function]}
resetShouldDiscardContainerPolicyChange={[Function]}
shouldDiscardContainerPolicies={false}
vectorEmbeddingPolicy={{}}
vectorEmbeddingPolicyBaseline={{}}
/>
</PivotItem>
<PivotItem <PivotItem
headerText="Indexing Policy" headerText="Indexing Policy"
itemKey="IndexingPolicyTab" itemKey="IndexingPolicyTab"
@@ -320,18 +274,8 @@ exports[`SettingsComponent renders 1`] = `
"partitionKeyProperties": [ "partitionKeyProperties": [
"partitionKey", "partitionKey",
], ],
"rawDataModel": {
"uniqueKeyPolicy": {
"uniqueKeys": [
{
"paths": [
"/id",
],
},
],
},
},
"readSettings": [Function], "readSettings": [Function],
"uniqueKeyPolicy": {},
"usageSizeInKB": [Function], "usageSizeInKB": [Function],
"vectorEmbeddingPolicy": [Function], "vectorEmbeddingPolicy": [Function],
} }
@@ -460,18 +404,8 @@ exports[`SettingsComponent renders 1`] = `
"partitionKeyProperties": [ "partitionKeyProperties": [
"partitionKey", "partitionKey",
], ],
"rawDataModel": {
"uniqueKeyPolicy": {
"uniqueKeys": [
{
"paths": [
"/id",
],
},
],
},
},
"readSettings": [Function], "readSettings": [Function],
"uniqueKeyPolicy": {},
"usageSizeInKB": [Function], "usageSizeInKB": [Function],
"vectorEmbeddingPolicy": [Function], "vectorEmbeddingPolicy": [Function],
} }

View File

@@ -1,4 +1,4 @@
import { Stack, Text } from "@fluentui/react"; import { Text } from "@fluentui/react";
import React, { FunctionComponent } from "react"; import React, { FunctionComponent } from "react";
import { InfoTooltip } from "../../../../Common/Tooltip/InfoTooltip"; import { InfoTooltip } from "../../../../Common/Tooltip/InfoTooltip";
import * as SharedConstants from "../../../../Shared/Constants"; import * as SharedConstants from "../../../../Shared/Constants";
@@ -44,42 +44,33 @@ export const CostEstimateText: FunctionComponent<CostEstimateTextProps> = ({
const currencySign: string = getCurrencySign(serverId); const currencySign: string = getCurrencySign(serverId);
const multiplier = getMultimasterMultiplier(numberOfRegions, multimasterEnabled); const multiplier = getMultimasterMultiplier(numberOfRegions, multimasterEnabled);
const pricePerRu = isAutoscale ? getAutoscalePricePerRu(serverId, multiplier) : getPricePerRu(serverId, multiplier); const pricePerRu = isAutoscale ? getAutoscalePricePerRu(serverId, multiplier) : getPricePerRu(serverId, multiplier);
const estimatedMonthlyCost = "Estimated monthly cost";
const iconWithEstimatedCostDisclaimer: JSX.Element = ( const iconWithEstimatedCostDisclaimer: JSX.Element = <InfoTooltip>{estimatedCostDisclaimer}</InfoTooltip>;
<InfoTooltip ariaLabelForTooltip={`${estimatedMonthlyCost} ${currency} ${estimatedCostDisclaimer}`}>
{estimatedCostDisclaimer}
</InfoTooltip>
);
if (isAutoscale) { if (isAutoscale) {
return ( return (
<Stack style={{ marginBottom: 6 }}> <Text variant="small">
<Text variant="small"> Estimated monthly cost ({currency}){iconWithEstimatedCostDisclaimer}:{" "}
{estimatedMonthlyCost} ({currency}){iconWithEstimatedCostDisclaimer}:{" "} <b>
<b> {currencySign + calculateEstimateNumber(monthlyPrice / 10)} -{" "}
{currencySign + calculateEstimateNumber(monthlyPrice / 10)} -{" "} {currencySign + calculateEstimateNumber(monthlyPrice)}{" "}
{currencySign + calculateEstimateNumber(monthlyPrice)}{" "} </b>
</b> ({numberOfRegions + (numberOfRegions === 1 ? " region" : " regions")}, {requestUnits / 10} - {requestUnits}{" "}
({numberOfRegions + (numberOfRegions === 1 ? " region" : " regions")}, {requestUnits / 10} - {requestUnits}{" "} RU/s, {currencySign + pricePerRu}/RU)
RU/s, {currencySign + pricePerRu}/RU) </Text>
</Text>
</Stack>
); );
} }
return ( return (
<Stack style={{ marginBottom: 8 }}> <Text variant="small">
<Text variant="small"> Estimated cost ({currency}){iconWithEstimatedCostDisclaimer}:{" "}
Estimated cost ({currency}){iconWithEstimatedCostDisclaimer}:{" "} <b>
<b> {currencySign + calculateEstimateNumber(hourlyPrice)} hourly /{" "}
{currencySign + calculateEstimateNumber(hourlyPrice)} hourly /{" "} {currencySign + calculateEstimateNumber(dailyPrice)} daily /{" "}
{currencySign + calculateEstimateNumber(dailyPrice)} daily /{" "} {currencySign + calculateEstimateNumber(monthlyPrice)} monthly{" "}
{currencySign + calculateEstimateNumber(monthlyPrice)} monthly{" "} </b>
</b> ({numberOfRegions + (numberOfRegions === 1 ? " region" : " regions")}, {requestUnits}RU/s,{" "}
({numberOfRegions + (numberOfRegions === 1 ? " region" : " regions")}, {requestUnits}RU/s,{" "} {currencySign + pricePerRu}/RU)
{currencySign + pricePerRu}/RU) </Text>
</Text>
</Stack>
); );
}; };

View File

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

View File

@@ -9,7 +9,6 @@ import {
Stack, Stack,
TextField, TextField,
} from "@fluentui/react"; } from "@fluentui/react";
import { InfoTooltip } from "Common/Tooltip/InfoTooltip";
import { VectorEmbedding, VectorIndex } from "Contracts/DataModels"; import { VectorEmbedding, VectorIndex } from "Contracts/DataModels";
import { CollapsibleSectionComponent } from "Explorer/Controls/CollapsiblePanel/CollapsibleSectionComponent"; import { CollapsibleSectionComponent } from "Explorer/Controls/CollapsiblePanel/CollapsibleSectionComponent";
import { import {
@@ -30,7 +29,6 @@ export interface IVectorEmbeddingPoliciesComponentProps {
discardChanges?: boolean; discardChanges?: boolean;
onChangesDiscarded?: () => void; onChangesDiscarded?: () => void;
disabled?: boolean; disabled?: boolean;
isGlobalSecondaryIndex?: boolean;
} }
export interface VectorEmbeddingPolicyData { export interface VectorEmbeddingPolicyData {
@@ -41,7 +39,8 @@ export interface VectorEmbeddingPolicyData {
indexType: VectorIndex["type"] | "none"; indexType: VectorIndex["type"] | "none";
pathError: string; pathError: string;
dimensionsError: string; dimensionsError: string;
vectorIndexShardKey?: string[]; diskANNShardKey?: string;
diskANNShardKeyError?: string;
indexingSearchListSize?: number; indexingSearchListSize?: number;
indexingSearchListSizeError?: string; indexingSearchListSizeError?: string;
quantizationByteSize?: number; quantizationByteSize?: number;
@@ -88,7 +87,6 @@ export const VectorEmbeddingPoliciesComponent: FunctionComponent<IVectorEmbeddin
discardChanges, discardChanges,
onChangesDiscarded, onChangesDiscarded,
disabled, disabled,
isGlobalSecondaryIndex,
}): JSX.Element => { }): JSX.Element => {
const onVectorEmbeddingPathError = (path: string, index?: number): string => { const onVectorEmbeddingPathError = (path: string, index?: number): string => {
let error = ""; let error = "";
@@ -134,6 +132,12 @@ export const VectorEmbeddingPoliciesComponent: FunctionComponent<IVectorEmbeddin
return error; 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 initializeData = (vectorEmbeddings: VectorEmbedding[], vectorIndexes: VectorIndex[]) => {
const mergedData: VectorEmbeddingPolicyData[] = []; const mergedData: VectorEmbeddingPolicyData[] = [];
vectorEmbeddings.forEach((embedding) => { vectorEmbeddings.forEach((embedding) => {
@@ -143,7 +147,6 @@ export const VectorEmbeddingPoliciesComponent: FunctionComponent<IVectorEmbeddin
indexType: matchingIndex?.type || "none", indexType: matchingIndex?.type || "none",
indexingSearchListSize: matchingIndex?.indexingSearchListSize || undefined, indexingSearchListSize: matchingIndex?.indexingSearchListSize || undefined,
quantizationByteSize: matchingIndex?.quantizationByteSize || undefined, quantizationByteSize: matchingIndex?.quantizationByteSize || undefined,
vectorIndexShardKey: matchingIndex?.vectorIndexShardKey || undefined,
pathError: onVectorEmbeddingPathError(embedding.path), pathError: onVectorEmbeddingPathError(embedding.path),
dimensionsError: onVectorEmbeddingDimensionError(embedding.dimensions, matchingIndex?.type || "none"), dimensionsError: onVectorEmbeddingDimensionError(embedding.dimensions, matchingIndex?.type || "none"),
}); });
@@ -183,7 +186,6 @@ export const VectorEmbeddingPoliciesComponent: FunctionComponent<IVectorEmbeddin
type: policy.indexType, type: policy.indexType,
indexingSearchListSize: policy.indexingSearchListSize, indexingSearchListSize: policy.indexingSearchListSize,
quantizationByteSize: policy.quantizationByteSize, quantizationByteSize: policy.quantizationByteSize,
vectorIndexShardKey: policy.vectorIndexShardKey,
}) as VectorIndex, }) as VectorIndex,
); );
const validationPassed = vectorEmbeddingPolicyData.every( const validationPassed = vectorEmbeddingPolicyData.every(
@@ -245,16 +247,20 @@ export const VectorEmbeddingPoliciesComponent: FunctionComponent<IVectorEmbeddin
setVectorEmbeddingPolicyData(vectorEmbeddings); setVectorEmbeddingPolicyData(vectorEmbeddings);
}; };
const onShardKeyChange = (index: number, event: React.ChangeEvent<HTMLInputElement>) => { // TODO: uncomment after Ignite
const value = event.target.value.trim(); // DiskANNShardKey was removed for Ignite due to backend problems. Leaving this here as it will be reinstated immediately after Ignite
const vectorEmbeddings = [...vectorEmbeddingPolicyData]; // const onDiskANNShardKeyChange = (index: number, event: React.ChangeEvent<HTMLInputElement>) => {
if (!vectorEmbeddings[index]?.vectorIndexShardKey?.[0] && !value.startsWith("/")) { // const value = event.target.value.trim();
vectorEmbeddings[index].vectorIndexShardKey = ["/" + value]; // const vectorEmbeddings = [...vectorEmbeddingPolicyData];
} else { // if (!vectorEmbeddings[index]?.diskANNShardKey && !value.startsWith("/")) {
vectorEmbeddings[index].vectorIndexShardKey = [value]; // vectorEmbeddings[index].diskANNShardKey = "/" + value;
} // } else {
setVectorEmbeddingPolicyData(vectorEmbeddings); // vectorEmbeddings[index].diskANNShardKey = value;
}; // }
// const error = onDiskANNShardKeyError(value);
// vectorEmbeddings[index].diskANNShardKeyError = error;
// setVectorEmbeddingPolicyData(vectorEmbeddings);
// }
const onVectorEmbeddingPolicyChange = ( const onVectorEmbeddingPolicyChange = (
index: number, index: number,
@@ -286,11 +292,6 @@ export const VectorEmbeddingPoliciesComponent: FunctionComponent<IVectorEmbeddin
setVectorEmbeddingPolicyData(vectorEmbeddings); setVectorEmbeddingPolicyData(vectorEmbeddings);
}; };
const getQuantizationByteSizeTooltipContent = (): string => {
const containerName: string = isGlobalSecondaryIndex ? "global secondary index" : "container";
return `This is dynamically set by the ${containerName} if left blank, or it can be set to a fixed number`;
};
return ( return (
<Stack tokens={{ childrenGap: 4 }}> <Stack tokens={{ childrenGap: 4 }}>
{vectorEmbeddingPolicyData && {vectorEmbeddingPolicyData &&
@@ -401,7 +402,6 @@ export const VectorEmbeddingPoliciesComponent: FunctionComponent<IVectorEmbeddin
styles={labelStyles} styles={labelStyles}
> >
Quantization byte size Quantization byte size
<InfoTooltip>{getQuantizationByteSizeTooltipContent()}</InfoTooltip>
</Label> </Label>
<TextField <TextField
disabled={ disabled={
@@ -431,18 +431,26 @@ export const VectorEmbeddingPoliciesComponent: FunctionComponent<IVectorEmbeddin
} }
/> />
</Stack> </Stack>
<Stack style={{ marginLeft: "10px" }}> {/*TODO: uncomment after Ignite */}
<Label disabled={disabled || vectorEmbeddingPolicy.indexType !== "diskANN"} styles={labelStyles}> {/* DiskANNShardKey was removed for Ignite due to backend problems. Leaving this here as it will be reinstated immediately after Ignite
Vector index shard key <Stack
</Label> style={{ marginLeft: "10px" }}
>
<Label
disabled={disabled || vectorEmbeddingPolicy.indexType !== "diskANN"}
styles={labelStyles}
>DiskANN shard key</Label>
<TextField <TextField
disabled={disabled || vectorEmbeddingPolicy.indexType !== "diskANN"} disabled={disabled || vectorEmbeddingPolicy.indexType !== "diskANN"}
id={`vector-policy-vectorIndexShardKey-${index + 1}`} id={`vector-policy-diskANNShardKey-${index + 1}`}
styles={textFieldStyles} styles={textFieldStyles}
value={String(vectorEmbeddingPolicy.vectorIndexShardKey?.[0] ?? "")} value={String(vectorEmbeddingPolicy.diskANNShardKey || "")}
onChange={(event: React.ChangeEvent<HTMLInputElement>) => onShardKeyChange(index, event)} onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
onDiskANNShardKeyChange(index, event)
}
/> />
</Stack> </Stack>
*/}
</Stack> </Stack>
)} )}
</Stack> </Stack>

View File

@@ -12,7 +12,6 @@ import { isFabricMirrored, isFabricMirroredKey, scheduleRefreshFabricToken } fro
import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility"; import { LocalStorageUtility, StorageKey } from "Shared/StorageUtility";
import { acquireMsalTokenForAccount } from "Utils/AuthorizationUtils"; import { acquireMsalTokenForAccount } from "Utils/AuthorizationUtils";
import { allowedNotebookServerUrls, validateEndpoint } from "Utils/EndpointUtils"; import { allowedNotebookServerUrls, validateEndpoint } from "Utils/EndpointUtils";
import { featureRegistered } from "Utils/FeatureRegistrationUtils";
import { update } from "Utils/arm/generatedClients/cosmos/databaseAccounts"; import { update } from "Utils/arm/generatedClients/cosmos/databaseAccounts";
import { useQueryCopilot } from "hooks/useQueryCopilot"; import { useQueryCopilot } from "hooks/useQueryCopilot";
import * as ko from "knockout"; import * as ko from "knockout";
@@ -31,7 +30,6 @@ import { readDatabases } from "../Common/dataAccess/readDatabases";
import * as DataModels from "../Contracts/DataModels"; import * as DataModels from "../Contracts/DataModels";
import { ContainerConnectionInfo, IPhoenixServiceInfo, IProvisionData, IResponse } from "../Contracts/DataModels"; import { ContainerConnectionInfo, IPhoenixServiceInfo, IProvisionData, IResponse } from "../Contracts/DataModels";
import * as ViewModels from "../Contracts/ViewModels"; import * as ViewModels from "../Contracts/ViewModels";
import { UploadDetailsRecord } from "../Contracts/ViewModels";
import { GitHubOAuthService } from "../GitHub/GitHubOAuthService"; import { GitHubOAuthService } from "../GitHub/GitHubOAuthService";
import { PhoenixClient } from "../Phoenix/PhoenixClient"; import { PhoenixClient } from "../Phoenix/PhoenixClient";
import * as ExplorerSettings from "../Shared/ExplorerSettings"; import * as ExplorerSettings from "../Shared/ExplorerSettings";
@@ -284,42 +282,6 @@ export default class Explorer {
} }
} }
public openInVsCode(): void {
const activeTab = useTabs.getState().activeTab;
const resourceId = encodeURIComponent(userContext.databaseAccount.id);
const database = encodeURIComponent(activeTab?.collection?.databaseId);
const container = encodeURIComponent(activeTab?.collection?.id());
const baseUrl = `vscode://ms-azuretools.vscode-cosmosdb?resourceId=${resourceId}`;
const vscodeUrl = activeTab ? `${baseUrl}&database=${database}&container=${container}` : baseUrl;
const openVSCodeDialogProps: DialogProps = {
linkProps: {
linkText: "Download Visual Studio Code",
linkUrl: "https://code.visualstudio.com/download",
},
isModal: true,
title: `Open your Azure Cosmos DB account in Visual Studio Code`,
subText: `Please ensure Visual Studio Code is installed on your device.
If you don't have it installed, please download it from the link below.`,
primaryButtonText: "Open in VS Code",
secondaryButtonText: "Cancel",
onPrimaryButtonClick: () => {
try {
window.location.href = vscodeUrl;
TelemetryProcessor.traceStart(Action.OpenVSCode);
} catch (error) {
logConsoleError(`Failed to open VS Code: ${getErrorMessage(error)}`);
}
},
onSecondaryButtonClick: () => {
useDialog.getState().closeDialog();
TelemetryProcessor.traceCancel(Action.OpenVSCode);
},
};
useDialog.getState().openDialog(openVSCodeDialogProps);
}
public async openCESCVAFeedbackBlade(): Promise<void> { public async openCESCVAFeedbackBlade(): Promise<void> {
sendMessage({ type: MessageTypes.OpenCESCVAFeedbackBlade }); sendMessage({ type: MessageTypes.OpenCESCVAFeedbackBlade });
Logger.logInfo( Logger.logInfo(
@@ -1116,8 +1078,8 @@ export default class Explorer {
} }
} }
public openUploadItemsPane(onUpload?: (data: UploadDetailsRecord[]) => void): void { public openUploadItemsPane(): void {
useSidePanel.getState().openSidePanel("Upload " + getUploadName(), <UploadItemsPane onUpload={onUpload} />); useSidePanel.getState().openSidePanel("Upload " + getUploadName(), <UploadItemsPane />);
} }
public openExecuteSprocParamsPanel(storedProcedure: StoredProcedure): void { public openExecuteSprocParamsPanel(storedProcedure: StoredProcedure): void {
useSidePanel useSidePanel
@@ -1125,7 +1087,7 @@ export default class Explorer {
.openSidePanel("Input parameters", <ExecuteSprocParamsPane storedProcedure={storedProcedure} />); .openSidePanel("Input parameters", <ExecuteSprocParamsPane storedProcedure={storedProcedure} />);
} }
public getDownloadModalContent(fileName: string): JSX.Element { public getDownloadModalConent(fileName: string): JSX.Element {
if (useNotebook.getState().isPhoenixNotebooks) { if (useNotebook.getState().isPhoenixNotebooks) {
return ( return (
<> <>
@@ -1171,11 +1133,6 @@ export default class Explorer {
await this.initNotebooks(userContext.databaseAccount); await this.initNotebooks(userContext.databaseAccount);
} }
if (userContext.authType === AuthType.AAD && userContext.apiType === "SQL") {
const throughputBucketsEnabled = await featureRegistered(userContext.subscriptionId, "ThroughputBucketing");
updateUserContext({ throughputBucketsEnabled });
}
this.refreshSampleData(); this.refreshSampleData();
} }

View File

@@ -16,12 +16,7 @@ import * as StorageUtility from "../../../Shared/StorageUtility";
import { LocalStorageUtility, StorageKey } from "../../../Shared/StorageUtility"; import { LocalStorageUtility, StorageKey } from "../../../Shared/StorageUtility";
import { Action } from "../../../Shared/Telemetry/TelemetryConstants"; import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor"; import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import { import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../../../Utils/NotificationConsoleUtils";
logConsoleError,
logConsoleInfo,
logConsoleProgress,
logConsoleWarning,
} from "../../../Utils/NotificationConsoleUtils";
import { EditorReact } from "../../Controls/Editor/EditorReact"; import { EditorReact } from "../../Controls/Editor/EditorReact";
import * as InputTypeaheadComponent from "../../Controls/InputTypeahead/InputTypeaheadComponent"; import * as InputTypeaheadComponent from "../../Controls/InputTypeahead/InputTypeaheadComponent";
import * as TabComponent from "../../Controls/Tabs/TabComponent"; import * as TabComponent from "../../Controls/Tabs/TabComponent";
@@ -1088,7 +1083,6 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
public static reportToConsole(type: ConsoleDataType.InProgress, msg: string, ...errorData: any[]): () => void; public static reportToConsole(type: ConsoleDataType.InProgress, msg: string, ...errorData: any[]): () => void;
public static reportToConsole(type: ConsoleDataType.Info, msg: string, ...errorData: any[]): void; public static reportToConsole(type: ConsoleDataType.Info, msg: string, ...errorData: any[]): void;
public static reportToConsole(type: ConsoleDataType.Error, msg: string, ...errorData: any[]): void; public static reportToConsole(type: ConsoleDataType.Error, msg: string, ...errorData: any[]): void;
public static reportToConsole(type: ConsoleDataType.Warning, msg: string, ...errorData: any[]): void;
public static reportToConsole(type: ConsoleDataType, msg: string, ...errorData: any[]): void | (() => void) { public static reportToConsole(type: ConsoleDataType, msg: string, ...errorData: any[]): void | (() => void) {
let errorDataStr = ""; let errorDataStr = "";
if (errorData && errorData.length > 0) { if (errorData && errorData.length > 0) {
@@ -1105,8 +1099,6 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
return logConsoleInfo(consoleMessage); return logConsoleInfo(consoleMessage);
case ConsoleDataType.InProgress: case ConsoleDataType.InProgress:
return logConsoleProgress(consoleMessage); return logConsoleProgress(consoleMessage);
case ConsoleDataType.Warning:
return logConsoleWarning(consoleMessage);
} }
} }

View File

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

View File

@@ -14,7 +14,6 @@ import OpenQueryFromDiskIcon from "../../../../images/OpenQueryFromDisk.svg";
import OpenInTabIcon from "../../../../images/open-in-tab.svg"; import OpenInTabIcon from "../../../../images/open-in-tab.svg";
import SettingsIcon from "../../../../images/settings_15x15.svg"; import SettingsIcon from "../../../../images/settings_15x15.svg";
import SynapseIcon from "../../../../images/synapse-link.svg"; import SynapseIcon from "../../../../images/synapse-link.svg";
import VSCodeIcon from "../../../../images/vscode.svg";
import { AuthType } from "../../../AuthType"; import { AuthType } from "../../../AuthType";
import * as Constants from "../../../Common/Constants"; import * as Constants from "../../../Common/Constants";
import { Platform, configContext } from "../../../ConfigContext"; import { Platform, configContext } from "../../../ConfigContext";
@@ -61,10 +60,6 @@ export function createStaticCommandBarButtons(
addDivider(); addDivider();
buttons.push(addSynapseLink); buttons.push(addSynapseLink);
} }
if (userContext.apiType !== "Gremlin") {
const addVsCode = createOpenVsCodeDialogButton(container);
buttons.push(addVsCode);
}
} }
if (isDataplaneRbacSupported(userContext.apiType)) { if (isDataplaneRbacSupported(userContext.apiType)) {
@@ -273,18 +268,6 @@ function createOpenSynapseLinkDialogButton(container: Explorer): CommandButtonCo
}; };
} }
function createOpenVsCodeDialogButton(container: Explorer): CommandButtonComponentProps {
const label = "Visual Studio Code";
return {
iconSrc: VSCodeIcon,
iconAlt: label,
onCommandClick: () => container.openInVsCode(),
commandButtonLabel: label,
hasPopup: false,
ariaLabel: label,
};
}
function createLoginForEntraIDButton(container: Explorer): CommandButtonComponentProps { function createLoginForEntraIDButton(container: Explorer): CommandButtonComponentProps {
if (configContext.platform !== Platform.Portal) { if (configContext.platform !== Platform.Portal) {
return undefined; return undefined;
@@ -517,6 +500,6 @@ export function createPostgreButtons(container: Explorer): CommandButtonComponen
export function createVCoreMongoButtons(container: Explorer): CommandButtonComponentProps[] { export function createVCoreMongoButtons(container: Explorer): CommandButtonComponentProps[] {
const openVCoreMongoTerminalButton = createOpenTerminalButtonByKind(container, ViewModels.TerminalKind.VCoreMongo); const openVCoreMongoTerminalButton = createOpenTerminalButtonByKind(container, ViewModels.TerminalKind.VCoreMongo);
const addVsCode = createOpenVsCodeDialogButton(container);
return [openVCoreMongoTerminalButton, addVsCode]; return [openVCoreMongoTerminalButton];
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -25,7 +25,7 @@ import { FullTextPoliciesComponent } from "Explorer/Controls/FullTextSeach/FullT
import { VectorEmbeddingPoliciesComponent } from "Explorer/Controls/VectorSearch/VectorEmbeddingPoliciesComponent"; import { VectorEmbeddingPoliciesComponent } from "Explorer/Controls/VectorSearch/VectorEmbeddingPoliciesComponent";
import { import {
AllPropertiesIndexed, AllPropertiesIndexed,
AnalyticalStoreHeader, AnalyticalStorageContent,
ContainerVectorPolicyTooltipContent, ContainerVectorPolicyTooltipContent,
FullTextPolicyDefault, FullTextPolicyDefault,
getPartitionKey, getPartitionKey,
@@ -49,7 +49,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 { ValidCosmosDbIdDescription, ValidCosmosDbIdInputPattern } from "Utils/ValidationUtils"; import { ValidCosmosDbIdDescription, ValidCosmosDbIdInputPattern } from "Utils/ValidationUtils";
import { CollapsibleSectionComponent } from "../../Controls/CollapsiblePanel/CollapsibleSectionComponent"; import { CollapsibleSectionComponent } from "../../Controls/CollapsiblePanel/CollapsibleSectionComponent";
@@ -60,7 +65,6 @@ import { useDatabases } from "../../useDatabases";
import { PanelFooterComponent } from "../PanelFooterComponent"; import { PanelFooterComponent } from "../PanelFooterComponent";
import { PanelInfoErrorComponent } from "../PanelInfoErrorComponent"; import { PanelInfoErrorComponent } from "../PanelInfoErrorComponent";
import { PanelLoadingScreen } from "../PanelLoadingScreen"; import { PanelLoadingScreen } from "../PanelLoadingScreen";
import * as AutoPilotUtils from "../../../Utils/AutoPilotUtils";
export interface AddCollectionPanelProps { export interface AddCollectionPanelProps {
explorer: Explorer; explorer: Explorer;
@@ -106,7 +110,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
private collectionThroughput: number; private collectionThroughput: number;
private isCollectionAutoscale: boolean; private isCollectionAutoscale: boolean;
private isCostAcknowledged: boolean; private isCostAcknowledged: boolean;
private showFullTextSearch: boolean;
constructor(props: AddCollectionPanelProps) { constructor(props: AddCollectionPanelProps) {
super(props); super(props);
@@ -141,8 +144,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
fullTextIndexes: [], fullTextIndexes: [],
fullTextPolicyValidated: true, fullTextPolicyValidated: true,
}; };
this.showFullTextSearch = userContext.apiType === "SQL";
} }
componentDidMount(): void { componentDidMount(): void {
@@ -265,7 +266,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
<div className="panelMainContent"> <div className="panelMainContent">
{!(isFabricNative() && this.props.databaseId !== undefined) && ( {!(isFabricNative() && this.props.databaseId !== undefined) && (
<Stack hidden={userContext.apiType === "Tables"} style={{ marginBottom: -2 }}> <Stack hidden={userContext.apiType === "Tables"}>
<Stack horizontal> <Stack horizontal>
<span className="mandatoryStar">*&nbsp;</span> <span className="mandatoryStar">*&nbsp;</span>
<Text className="panelTextBold" variant="small"> <Text className="panelTextBold" variant="small">
@@ -336,6 +337,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
size={40} size={40}
className="panelTextField" className="panelTextField"
aria-label="New database id, Type a new database id" aria-label="New database id, Type a new database id"
autoFocus
tabIndex={0} tabIndex={0}
value={this.state.newDatabaseId} value={this.state.newDatabaseId}
onChange={(event: React.ChangeEvent<HTMLInputElement>) => onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
@@ -406,12 +408,12 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
responsiveMode={999} responsiveMode={999}
/> />
)} )}
<Separator className="panelSeparator" />
</Stack> </Stack>
)} )}
<Separator className="panelSeparator" style={{ marginTop: -4, marginBottom: -4 }} />
<Stack> <Stack>
<Stack horizontal style={{ marginTop: -5, marginBottom: 1 }}> <Stack horizontal>
<span className="mandatoryStar">*&nbsp;</span> <span className="mandatoryStar">*&nbsp;</span>
<Text className="panelTextBold" variant="small"> <Text className="panelTextBold" variant="small">
{`${getCollectionName()} id`} {`${getCollectionName()} id`}
@@ -449,10 +451,10 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
} }
/> />
</Stack> </Stack>
<Separator className="panelSeparator" style={{ marginTop: -5, marginBottom: -5 }} />
{this.shouldShowIndexingOptionsForFreeTierAccount() && ( {this.shouldShowIndexingOptionsForFreeTierAccount() && (
<Stack> <Stack>
<Stack horizontal style={{ marginTop: -4, marginBottom: -5 }}> <Stack horizontal>
<span className="mandatoryStar">*&nbsp;</span> <span className="mandatoryStar">*&nbsp;</span>
<Text className="panelTextBold" variant="small"> <Text className="panelTextBold" variant="small">
Indexing Indexing
@@ -498,7 +500,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
(!this.state.isSharedThroughputChecked || (!this.state.isSharedThroughputChecked ||
this.props.explorer.isFixedCollectionWithSharedThroughputSupported()) && ( this.props.explorer.isFixedCollectionWithSharedThroughputSupported()) && (
<Stack> <Stack>
<Stack horizontal style={{ marginTop: -5, marginBottom: -4 }}> <Stack horizontal>
<span className="mandatoryStar">*&nbsp;</span> <span className="mandatoryStar">*&nbsp;</span>
<Text className="panelTextBold" variant="small"> <Text className="panelTextBold" variant="small">
Sharding Sharding
@@ -554,7 +556,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
{this.state.isSharded && ( {this.state.isSharded && (
<Stack> <Stack>
<Stack horizontal style={{ marginTop: -5, marginBottom: -4 }}> <Stack horizontal>
<span className="mandatoryStar">*&nbsp;</span> <span className="mandatoryStar">*&nbsp;</span>
<Text className="panelTextBold" variant="small"> <Text className="panelTextBold" variant="small">
{getPartitionKeyName()} {getPartitionKeyName()}
@@ -598,7 +600,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
{userContext.apiType === "SQL" && {userContext.apiType === "SQL" &&
this.state.subPartitionKeys.map((subPartitionKey: string, index: number) => { this.state.subPartitionKeys.map((subPartitionKey: string, index: number) => {
return ( return (
<Stack style={{ marginBottom: 2, marginTop: -5 }} key={`uniqueKey${index}`} horizontal> <Stack style={{ marginBottom: 8 }} key={`uniqueKey${index}`} horizontal>
<div <div
style={{ style={{
width: "20px", width: "20px",
@@ -666,7 +668,6 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
)} )}
</Stack> </Stack>
)} )}
<Separator className="panelSeparator" style={{ marginTop: 2, marginBottom: -4 }} />
</Stack> </Stack>
)} )}
@@ -727,7 +728,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
)} )}
{!isFabricNative() && userContext.apiType === "SQL" && ( {!isFabricNative() && userContext.apiType === "SQL" && (
<Stack style={{ marginTop: -2, marginBottom: -4 }}> <Stack>
{UniqueKeysHeader()} {UniqueKeysHeader()}
{this.state.uniqueKeys.map((uniqueKey: string, i: number): JSX.Element => { {this.state.uniqueKeys.map((uniqueKey: string, i: number): JSX.Element => {
return ( return (
@@ -741,6 +742,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
: "Comma separated paths e.g. /firstName,/address/zipCode" : "Comma separated paths e.g. /firstName,/address/zipCode"
} }
className="panelTextField" className="panelTextField"
autoFocus
value={uniqueKey} value={uniqueKey}
onChange={(event: React.ChangeEvent<HTMLInputElement>) => { onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
const uniqueKeys = this.state.uniqueKeys.map((uniqueKey: string, j: number) => { const uniqueKeys = this.state.uniqueKeys.map((uniqueKey: string, j: number) => {
@@ -775,12 +777,10 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
</Stack> </Stack>
)} )}
<Separator className="panelSeparator" style={{ marginTop: -15, marginBottom: -4 }} />
{shouldShowAnalyticalStoreOptions() && ( {shouldShowAnalyticalStoreOptions() && (
<Stack className="panelGroupSpacing" style={{ marginTop: -4 }}> <Stack className="panelGroupSpacing">
<Text className="panelTextBold" variant="small"> <Text className="panelTextBold" variant="small">
{AnalyticalStoreHeader()} {AnalyticalStorageContent()}
</Text> </Text>
<Stack horizontal verticalAlign="center"> <Stack horizontal verticalAlign="center">
@@ -821,7 +821,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
<Stack className="panelGroupSpacing"> <Stack className="panelGroupSpacing">
<Text variant="small"> <Text variant="small">
Azure Synapse Link is required for creating an analytical store{" "} Azure Synapse Link is required for creating an analytical store{" "}
{getCollectionName().toLocaleLowerCase()}. Enable Synapse Link for this Cosmos DB account. <br /> {getCollectionName().toLocaleLowerCase()}. Enable Synapse Link for this Cosmos DB account.{" "}
<Link <Link
href="https://aka.ms/cosmosdb-synapselink" href="https://aka.ms/cosmosdb-synapselink"
target="_blank" target="_blank"
@@ -1161,7 +1161,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
} }
private shouldShowFullTextSearchParameters() { private shouldShowFullTextSearchParameters() {
return !isFabricNative() && this.showFullTextSearch; return isFullTextSearchEnabled() && (isServerlessAccount() || this.shouldShowCollectionThroughputInput());
} }
private parseUniqueKeys(): DataModels.UniqueKeyPolicy { private parseUniqueKeys(): DataModels.UniqueKeyPolicy {
@@ -1316,7 +1316,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
}; };
} }
if (this.showFullTextSearch) { if (this.shouldShowFullTextSearchParameters()) {
indexingPolicy.fullTextIndexes = this.state.fullTextIndexes; indexingPolicy.fullTextIndexes = this.state.fullTextIndexes;
} }
@@ -1350,12 +1350,7 @@ export class AddCollectionPanel extends React.Component<AddCollectionPanelProps,
let offerThroughput: number; let offerThroughput: number;
let autoPilotMaxThroughput: number; let autoPilotMaxThroughput: number;
// Throughput if (databaseLevelThroughput) {
if (isFabricNative()) {
// Fabric Native accounts are always autoscale and have a fixed throughput of 1K
autoPilotMaxThroughput = AutoPilotUtils.autoPilotThroughput1K;
offerThroughput = undefined;
} else if (databaseLevelThroughput) {
if (this.state.createNewDatabase) { if (this.state.createNewDatabase) {
if (this.isNewDatabaseAutoscale) { if (this.isNewDatabaseAutoscale) {
autoPilotMaxThroughput = this.newDatabaseThroughput; autoPilotMaxThroughput = this.newDatabaseThroughput;

View File

@@ -73,7 +73,7 @@ export function UniqueKeysHeader(): JSX.Element {
"Unique keys provide developers with the ability to add a layer of data integrity to their database. By creating a unique key policy when a container is created, you ensure the uniqueness of one or more values per partition key."; "Unique keys provide developers with the ability to add a layer of data integrity to their database. By creating a unique key policy when a container is created, you ensure the uniqueness of one or more values per partition key.";
return ( return (
<Stack horizontal style={{ marginBottom: -2 }}> <Stack horizontal>
<Text className="panelTextBold" variant="small"> <Text className="panelTextBold" variant="small">
Unique keys Unique keys
</Text> </Text>
@@ -98,21 +98,6 @@ export function shouldShowAnalyticalStoreOptions(): boolean {
} }
} }
export function AnalyticalStoreHeader(): JSX.Element {
const tooltipContent =
"Enable analytical store capability to perform near real-time analytics on your operational data, without impacting the performance of transactional workloads.";
return (
<Stack horizontal style={{ marginBottom: -2 }}>
<Text className="panelTextBold" variant="small">
Analytical Store
</Text>
<TooltipHost directionalHint={DirectionalHint.bottomLeftEdge} content={tooltipContent}>
<Icon iconName="Info" className="panelInfoIcon" tabIndex={0} ariaLabel={tooltipContent} />
</TooltipHost>
</Stack>
);
}
export function AnalyticalStorageContent(): JSX.Element { export function AnalyticalStorageContent(): JSX.Element {
return ( return (
<Text variant="small"> <Text variant="small">

View File

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

View File

@@ -40,12 +40,12 @@ import { PanelInfoErrorComponent } from "Explorer/Panes/PanelInfoErrorComponent"
import { PanelLoadingScreen } from "Explorer/Panes/PanelLoadingScreen"; import { PanelLoadingScreen } from "Explorer/Panes/PanelLoadingScreen";
import { useDatabases } from "Explorer/useDatabases"; import { useDatabases } from "Explorer/useDatabases";
import { useSidePanel } from "hooks/useSidePanel"; import { useSidePanel } from "hooks/useSidePanel";
import React, { MutableRefObject, useEffect, useRef, useState } from "react"; import React, { useEffect, useState } from "react";
import { CollectionCreation } from "Shared/Constants"; import { CollectionCreation } from "Shared/Constants";
import { Action } from "Shared/Telemetry/TelemetryConstants"; import { Action } from "Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "Shared/Telemetry/TelemetryProcessor"; import * as TelemetryProcessor from "Shared/Telemetry/TelemetryProcessor";
import { userContext } from "UserContext"; import { userContext } from "UserContext";
import { isServerlessAccount, isVectorSearchEnabled } from "Utils/CapabilityUtils"; import { isFullTextSearchEnabled, isServerlessAccount, isVectorSearchEnabled } from "Utils/CapabilityUtils";
import { ValidCosmosDbIdDescription, ValidCosmosDbIdInputPattern } from "Utils/ValidationUtils"; import { ValidCosmosDbIdDescription, ValidCosmosDbIdInputPattern } from "Utils/ValidationUtils";
export interface AddGlobalSecondaryIndexPanelProps { export interface AddGlobalSecondaryIndexPanelProps {
@@ -75,8 +75,6 @@ export const AddGlobalSecondaryIndexPanel = (props: AddGlobalSecondaryIndexPanel
const [showErrorDetails, setShowErrorDetails] = useState<boolean>(); const [showErrorDetails, setShowErrorDetails] = useState<boolean>();
const [isExecuting, setIsExecuting] = useState<boolean>(); const [isExecuting, setIsExecuting] = useState<boolean>();
const showFullTextSearch: MutableRefObject<boolean> = useRef<boolean>(userContext.apiType === "SQL");
useEffect(() => { useEffect(() => {
const sourceContainerOptions: IDropdownOption[] = []; const sourceContainerOptions: IDropdownOption[] = [];
useDatabases.getState().databases.forEach((database: Database) => { useDatabases.getState().databases.forEach((database: Database) => {
@@ -142,6 +140,10 @@ export const AddGlobalSecondaryIndexPanel = (props: AddGlobalSecondaryIndexPanel
return isVectorSearchEnabled() && (isServerlessAccount() || showCollectionThroughputInput()); return isVectorSearchEnabled() && (isServerlessAccount() || showCollectionThroughputInput());
}; };
const showFullTextSearchParameters = (): boolean => {
return isFullTextSearchEnabled() && (isServerlessAccount() || showCollectionThroughputInput());
};
const getAnalyticalStorageTtl = (): number => { const getAnalyticalStorageTtl = (): number => {
if (!isSynapseLinkEnabled()) { if (!isSynapseLinkEnabled()) {
return undefined; return undefined;
@@ -173,6 +175,11 @@ export const AddGlobalSecondaryIndexPanel = (props: AddGlobalSecondaryIndexPanel
return false; return false;
} }
if (globalSecondaryIndexThroughput > CollectionCreation.MaxRUPerPartition) {
setErrorMessage("Unsharded collections support up to 10,000 RUs");
return false;
}
if (showVectorSearchParameters()) { if (showVectorSearchParameters()) {
if (!vectorPolicyValidated) { if (!vectorPolicyValidated) {
setErrorMessage("Please fix errors in container vector policy"); setErrorMessage("Please fix errors in container vector policy");
@@ -226,7 +233,7 @@ export const AddGlobalSecondaryIndexPanel = (props: AddGlobalSecondaryIndexPanel
}; };
} }
if (showFullTextSearch) { if (showFullTextSearchParameters()) {
indexingPolicy.fullTextIndexes = fullTextIndexes; indexingPolicy.fullTextIndexes = fullTextIndexes;
} }
@@ -381,11 +388,10 @@ export const AddGlobalSecondaryIndexPanel = (props: AddGlobalSecondaryIndexPanel
setVectorIndexingPolicy, setVectorIndexingPolicy,
vectorPolicyValidated, vectorPolicyValidated,
setVectorPolicyValidated, setVectorPolicyValidated,
isGlobalSecondaryIndex: true,
}} }}
/> />
)} )}
{showFullTextSearch && ( {showFullTextSearchParameters() && (
<FullTextSearchComponent <FullTextSearchComponent
{...{ fullTextPolicy, setFullTextPolicy, setFullTextIndexes, setFullTextPolicyValidated }} {...{ fullTextPolicy, setFullTextPolicy, setFullTextIndexes, setFullTextPolicyValidated }}
/> />

View File

@@ -47,7 +47,7 @@ export const ThroughputComponent = (props: ThroughputComponentProps): JSX.Elemen
<ThroughputInput <ThroughputInput
showFreeTierExceedThroughputTooltip={isFreeTierAccount() && !useDatabases.getState().isFirstResourceCreated()} showFreeTierExceedThroughputTooltip={isFreeTierAccount() && !useDatabases.getState().isFirstResourceCreated()}
isDatabase={false} isDatabase={false}
isSharded={true} isSharded={false}
isFreeTier={isFreeTierAccount()} isFreeTier={isFreeTierAccount()}
isQuickstart={false} isQuickstart={false}
isGlobalSecondaryIndex={true} isGlobalSecondaryIndex={true}

View File

@@ -14,7 +14,6 @@ export interface VectorSearchComponentProps {
vectorIndexingPolicy: VectorIndex[]; vectorIndexingPolicy: VectorIndex[];
setVectorIndexingPolicy: React.Dispatch<React.SetStateAction<VectorIndex[]>>; setVectorIndexingPolicy: React.Dispatch<React.SetStateAction<VectorIndex[]>>;
setVectorPolicyValidated: React.Dispatch<React.SetStateAction<boolean>>; setVectorPolicyValidated: React.Dispatch<React.SetStateAction<boolean>>;
isGlobalSecondaryIndex?: boolean;
} }
export const VectorSearchComponent = (props: VectorSearchComponentProps): JSX.Element => { export const VectorSearchComponent = (props: VectorSearchComponentProps): JSX.Element => {
@@ -24,7 +23,6 @@ export const VectorSearchComponent = (props: VectorSearchComponentProps): JSX.El
vectorIndexingPolicy, vectorIndexingPolicy,
setVectorIndexingPolicy, setVectorIndexingPolicy,
setVectorPolicyValidated, setVectorPolicyValidated,
isGlobalSecondaryIndex,
} = props; } = props;
return ( return (
@@ -51,7 +49,6 @@ export const VectorSearchComponent = (props: VectorSearchComponentProps): JSX.El
setVectorIndexingPolicy(vectorIndexingPolicy); setVectorIndexingPolicy(vectorIndexingPolicy);
setVectorPolicyValidated(vectorPolicyValidated); setVectorPolicyValidated(vectorPolicyValidated);
}} }}
isGlobalSecondaryIndex={isGlobalSecondaryIndex}
/> />
</Stack> </Stack>
</Stack> </Stack>

View File

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

View File

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

View File

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

View File

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

View File

@@ -356,7 +356,7 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
value="" value=""
> >
<div <div
className="ms-TextField is-required root-116" className="ms-TextField is-required root-110"
> >
<div <div
className="ms-TextField-wrapper" className="ms-TextField-wrapper"
@@ -647,7 +647,7 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
} }
> >
<label <label
className="ms-Label root-127" className="ms-Label root-121"
htmlFor="TextField0" htmlFor="TextField0"
id="TextFieldLabel2" id="TextFieldLabel2"
> >
@@ -656,13 +656,13 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
</LabelBase> </LabelBase>
</StyledLabelBase> </StyledLabelBase>
<div <div
className="ms-TextField-fieldGroup fieldGroup-117" className="ms-TextField-fieldGroup fieldGroup-111"
> >
<input <input
aria-invalid={false} aria-invalid={false}
aria-labelledby="TextFieldLabel2" aria-labelledby="TextFieldLabel2"
autoFocus={true} autoFocus={true}
className="ms-TextField-field field-118" className="ms-TextField-field field-112"
id="TextField0" id="TextField0"
name="collectionIdConfirmation" name="collectionIdConfirmation"
onBlur={[Function]} onBlur={[Function]}
@@ -2464,7 +2464,7 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
> >
<button <button
aria-label="Create" aria-label="Create"
className="ms-Button ms-Button--primary root-128" className="ms-Button ms-Button--primary root-122"
data-is-focusable={true} data-is-focusable={true}
data-test="Panel/OkButton" data-test="Panel/OkButton"
id="sidePanelOkButton" id="sidePanelOkButton"
@@ -2477,14 +2477,14 @@ exports[`StringInput Pane should render Create new directory properly 1`] = `
type="submit" type="submit"
> >
<span <span
className="ms-Button-flexContainer flexContainer-129" className="ms-Button-flexContainer flexContainer-123"
data-automationid="splitbuttonprimary" data-automationid="splitbuttonprimary"
> >
<span <span
className="ms-Button-textContainer textContainer-130" className="ms-Button-textContainer textContainer-124"
> >
<span <span
className="ms-Button-label label-132" className="ms-Button-label label-126"
id="id__5" id="id__5"
key="id__5" key="id__5"
> >

View File

@@ -2,13 +2,9 @@ import {
DetailsList, DetailsList,
DetailsListLayoutMode, DetailsListLayoutMode,
DirectionalHint, DirectionalHint,
FontIcon,
IColumn, IColumn,
SelectionMode, SelectionMode,
TooltipHost, TooltipHost,
getTheme,
mergeStyles,
mergeStyleSets,
} from "@fluentui/react"; } from "@fluentui/react";
import { Upload } from "Common/Upload/Upload"; import { Upload } from "Common/Upload/Upload";
import { UploadDetailsRecord } from "Contracts/ViewModels"; import { UploadDetailsRecord } from "Contracts/ViewModels";
@@ -18,41 +14,7 @@ import { getErrorMessage } from "../../Tables/Utilities";
import { useSelectedNode } from "../../useSelectedNode"; import { useSelectedNode } from "../../useSelectedNode";
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm"; import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
const theme = getTheme(); export const UploadItemsPane: FunctionComponent = () => {
const iconClass = mergeStyles({
verticalAlign: "middle",
maxHeight: "16px",
maxWidth: "16px",
});
const classNames = mergeStyleSets({
fileIconHeaderIcon: {
padding: 0,
fontSize: "16px",
},
fileIconCell: {
textAlign: "center",
selectors: {
"&:before": {
content: ".",
display: "inline-block",
verticalAlign: "middle",
height: "100%",
width: "0px",
visibility: "hidden",
},
},
},
error: [{ color: theme.semanticColors.errorIcon }, iconClass],
accept: [{ color: theme.semanticColors.successIcon }, iconClass],
warning: [{ color: theme.semanticColors.warningIcon }, iconClass],
});
export type UploadItemsPaneProps = {
onUpload?: (data: UploadDetailsRecord[]) => void;
};
export const UploadItemsPane: FunctionComponent<UploadItemsPaneProps> = ({ onUpload }) => {
const [files, setFiles] = useState<FileList>(); const [files, setFiles] = useState<FileList>();
const [uploadFileData, setUploadFileData] = useState<UploadDetailsRecord[]>([]); const [uploadFileData, setUploadFileData] = useState<UploadDetailsRecord[]>([]);
const [formError, setFormError] = useState<string>(""); const [formError, setFormError] = useState<string>("");
@@ -75,8 +37,6 @@ export const UploadItemsPane: FunctionComponent<UploadItemsPaneProps> = ({ onUpl
(uploadDetails) => { (uploadDetails) => {
setUploadFileData(uploadDetails.data); setUploadFileData(uploadDetails.data);
setFiles(undefined); setFiles(undefined);
// Emit the upload details to the parent component
onUpload && onUpload(uploadDetails.data);
}, },
(error: Error) => { (error: Error) => {
const errorMessage = getErrorMessage(error); const errorMessage = getErrorMessage(error);
@@ -100,94 +60,44 @@ export const UploadItemsPane: FunctionComponent<UploadItemsPaneProps> = ({ onUpl
}; };
const columns: IColumn[] = [ const columns: IColumn[] = [
{
key: "icons",
name: "",
fieldName: "",
className: classNames.fileIconCell,
iconClassName: classNames.fileIconHeaderIcon,
isIconOnly: true,
minWidth: 16,
maxWidth: 16,
onRender: (item: UploadDetailsRecord, index: number, column: IColumn) => {
if (item.numFailed) {
const errorList = (
<ul
aria-label={"error list"}
style={{
margin: "5px 0",
paddingLeft: "20px",
listStyleType: "disc", // Explicitly set to use bullets (dots)
}}
>
{item.errors.map((error, i) => (
<li key={i} style={{ display: "list-item" }}>
{error}
</li>
))}
</ul>
);
return (
<TooltipHost
content={errorList}
id={`tooltip-${index}-${column.key}`}
directionalHint={DirectionalHint.bottomAutoEdge}
>
<FontIcon iconName="Error" className={classNames.error} aria-label="error" />
</TooltipHost>
);
} else if (item.numThrottled) {
return <FontIcon iconName="Warning" className={classNames.warning} aria-label="warning" />;
} else {
return <FontIcon iconName="Accept" className={classNames.accept} aria-label="accept" />;
}
},
},
{ {
key: "fileName", key: "fileName",
name: "FILE NAME", name: "FILE NAME",
fieldName: "fileName", fieldName: "fileName",
minWidth: 120, minWidth: 140,
maxWidth: 140, maxWidth: 140,
onRender: (item: UploadDetailsRecord, index: number, column: IColumn) => {
const fieldContent = item.fileName;
return (
<TooltipHost
content={fieldContent}
id={`tooltip-${index}-${column.key}`}
directionalHint={DirectionalHint.bottomAutoEdge}
>
{fieldContent}
</TooltipHost>
);
},
}, },
{ {
key: "status", key: "status",
name: "STATUS", name: "STATUS",
fieldName: "numSucceeded", fieldName: "numSucceeded",
minWidth: 120, minWidth: 140,
maxWidth: 140, maxWidth: 140,
isRowHeader: true, isRowHeader: true,
isResizable: true, isResizable: true,
data: "string", data: "string",
isPadded: true, isPadded: true,
onRender: (item: UploadDetailsRecord, index: number, column: IColumn) => {
const fieldContent = `${item.numSucceeded} created, ${item.numThrottled} throttled, ${item.numFailed} errors`;
return (
<TooltipHost
content={fieldContent}
id={`tooltip-${index}-${column.key}`}
directionalHint={DirectionalHint.bottomAutoEdge}
>
{fieldContent}
</TooltipHost>
);
},
}, },
]; ];
const _renderItemColumn = (item: UploadDetailsRecord, index: number, column: IColumn) => {
let fieldContent: string;
const tooltipId = `tooltip-${index}-${column.key}`;
switch (column.key) {
case "status":
fieldContent = `${item.numSucceeded} created, ${item.numThrottled} throttled, ${item.numFailed} errors`;
break;
default:
fieldContent = item.fileName;
}
return (
<TooltipHost content={fieldContent} id={tooltipId} directionalHint={DirectionalHint.rightCenter}>
{fieldContent}
</TooltipHost>
);
};
return ( return (
<RightPaneForm {...props}> <RightPaneForm {...props}>
<div className="paneMainContent"> <div className="paneMainContent">
@@ -205,6 +115,7 @@ export const UploadItemsPane: FunctionComponent<UploadItemsPaneProps> = ({ onUpl
<DetailsList <DetailsList
items={uploadFileData} items={uploadFileData}
columns={columns} columns={columns}
onRenderItemColumn={_renderItemColumn}
selectionMode={SelectionMode.none} selectionMode={SelectionMode.none}
layoutMode={DetailsListLayoutMode.justified} layoutMode={DetailsListLayoutMode.justified}
isHeaderVisible={true} isHeaderVisible={true}

View File

@@ -30,10 +30,8 @@ import { KeyboardAction, KeyboardActionGroup, KeyboardActionHandler, useKeyboard
import { isFabric, isFabricMirrored, isFabricNative, isFabricNativeReadOnly } from "Platform/Fabric/FabricUtil"; import { isFabric, isFabricMirrored, isFabricNative, isFabricNativeReadOnly } from "Platform/Fabric/FabricUtil";
import { userContext } from "UserContext"; import { userContext } from "UserContext";
import { getCollectionName, getDatabaseName } from "Utils/APITypeUtils"; import { getCollectionName, getDatabaseName } from "Utils/APITypeUtils";
import { conditionalClass } from "Utils/StyleUtils";
import { Allotment, AllotmentHandle } from "allotment"; import { Allotment, AllotmentHandle } from "allotment";
import { useSidePanel } from "hooks/useSidePanel"; import { useSidePanel } from "hooks/useSidePanel";
import useZoomLevel from "hooks/useZoomLevel";
import { debounce } from "lodash"; import { debounce } from "lodash";
import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react"; import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
@@ -106,23 +104,6 @@ const useSidebarStyles = makeStyles({
display: "flex", display: "flex",
}, },
}, },
accessibleContent: {
"@media (max-width: 420px)": {
overflow: "scroll",
},
},
minHeightResponsive: {
"@media (max-width: 420px)": {
minHeight: "400px",
},
},
accessibleContentZoom: {
overflow: "scroll",
},
minHeightZoom: {
minHeight: "400px",
},
}); });
interface GlobalCommandsProps { interface GlobalCommandsProps {
@@ -294,7 +275,6 @@ export const SidebarContainer: React.FC<SidebarProps> = ({ explorer }) => {
const [expandedSize, setExpandedSize] = React.useState(300); const [expandedSize, setExpandedSize] = React.useState(300);
const hasSidebar = userContext.apiType !== "Postgres" && userContext.apiType !== "VCoreMongo"; const hasSidebar = userContext.apiType !== "Postgres" && userContext.apiType !== "VCoreMongo";
const allotment = useRef<AllotmentHandle>(null); const allotment = useRef<AllotmentHandle>(null);
const isZoomed = useZoomLevel();
const expand = useCallback(() => { const expand = useCallback(() => {
if (!expanded) { if (!expanded) {
@@ -345,23 +325,11 @@ export const SidebarContainer: React.FC<SidebarProps> = ({ explorer }) => {
return ( return (
<div className="sidebarContainer"> <div className="sidebarContainer">
<Allotment <Allotment ref={allotment} onChange={onChange} onDragEnd={onDragEnd} className="resourceTreeAndTabs">
ref={allotment}
onChange={onChange}
onDragEnd={onDragEnd}
className={`resourceTreeAndTabs ${styles.accessibleContent} ${conditionalClass(
isZoomed,
styles.accessibleContentZoom,
)}`}
>
{/* Collections Tree - Start */} {/* Collections Tree - Start */}
{hasSidebar && ( {hasSidebar && (
// When collapsed, we force the pane to 24 pixels wide and make it non-resizable. // When collapsed, we force the pane to 24 pixels wide and make it non-resizable.
<Allotment.Pane <Allotment.Pane minSize={24} preferredSize={250}>
className={`${styles.minHeightResponsive} ${conditionalClass(isZoomed, styles.minHeightZoom)}`}
minSize={24}
preferredSize={250}
>
<CosmosFluentProvider className={mergeClasses(styles.sidebar)}> <CosmosFluentProvider className={mergeClasses(styles.sidebar)}>
<div className={styles.sidebarContainer}> <div className={styles.sidebarContainer}>
{loading && ( {loading && (
@@ -417,10 +385,7 @@ export const SidebarContainer: React.FC<SidebarProps> = ({ explorer }) => {
</CosmosFluentProvider> </CosmosFluentProvider>
</Allotment.Pane> </Allotment.Pane>
)} )}
<Allotment.Pane <Allotment.Pane minSize={200}>
className={`${styles.minHeightResponsive} ${conditionalClass(isZoomed, styles.minHeightZoom)}`}
minSize={200}
>
<Tabs explorer={explorer} /> <Tabs explorer={explorer} />
</Allotment.Pane> </Allotment.Pane>
</Allotment> </Allotment>

View File

@@ -1,14 +1,15 @@
/** /**
* Accordion top class * Accordion top class
*/ */
import { makeStyles, tokens } from "@fluentui/react-components"; import { Link, makeStyles, tokens } from "@fluentui/react-components";
import { DocumentAddRegular, LinkMultipleRegular } from "@fluentui/react-icons"; import { DocumentAddRegular, LinkMultipleRegular } from "@fluentui/react-icons";
import { SampleDataImportDialog } from "Explorer/SplashScreen/SampleDataImportDialog"; import { SampleDataImportDialog } from "Explorer/SplashScreen/SampleDataImportDialog";
import { CosmosFluentProvider } from "Explorer/Theme/ThemeUtil"; import { CosmosFluentProvider } from "Explorer/Theme/ThemeUtil";
import { isFabricNative, isFabricNativeReadOnly } from "Platform/Fabric/FabricUtil"; import { isFabricNative, isFabricNativeReadOnly, openSettingsConnectionTab } from "Platform/Fabric/FabricUtil";
import * as React from "react"; import * as React from "react";
import { userContext } from "UserContext"; import { userContext } from "UserContext";
import CosmosDbBlackIcon from "../../../images/CosmosDB_black.svg"; import CosmosDbBlackIcon from "../../../images/CosmosDB_black.svg";
import LinkIcon from "../../../images/Link_blue.svg";
import Explorer from "../Explorer"; import Explorer from "../Explorer";
export interface SplashScreenProps { export interface SplashScreenProps {
@@ -154,7 +155,7 @@ export const FabricHomeScreen: React.FC<SplashScreenProps> = (props: SplashScree
title: "App development", title: "App development",
description: "Start here to use an SDK to build your apps", description: "Start here to use an SDK to build your apps",
icon: <LinkMultipleRegular />, icon: <LinkMultipleRegular />,
onClick: () => window.open("https://aka.ms/cosmosdbfabricsdk", "_blank"), onClick: () => openSettingsConnectionTab(),
}, },
]; ];
@@ -185,12 +186,12 @@ export const FabricHomeScreen: React.FC<SplashScreenProps> = (props: SplashScree
{title} {title}
</div> </div>
{getSplashScreenButtons()} {getSplashScreenButtons()}
{/* <div className={styles.footer}> <div className={styles.footer}>
Need help?{" "} Need help?{" "}
<Link href="https://aka.ms/cosmosdbfabricdocs" target="_blank"> <Link href="https://aka.ms/cosmosdbfabricdocs" target="_blank">
Learn more <img src={LinkIcon} alt="Learn more" /> Learn more <img src={LinkIcon} alt="Learn more" />
</Link> </Link>
</div> */} </div>
</CosmosFluentProvider> </CosmosFluentProvider>
</> </>
); );

View File

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

View File

@@ -28,7 +28,6 @@ import LinkIcon from "../../../images/Link_blue.svg";
import PowerShellIcon from "../../../images/PowerShell.svg"; import PowerShellIcon from "../../../images/PowerShell.svg";
import CopilotIcon from "../../../images/QueryCopilotNewLogo.svg"; import CopilotIcon from "../../../images/QueryCopilotNewLogo.svg";
import QuickStartIcon from "../../../images/Quickstart_Lightning.svg"; import QuickStartIcon from "../../../images/Quickstart_Lightning.svg";
import VisualStudioIcon from "../../../images/VisualStudio.svg";
import NotebookIcon from "../../../images/notebook/Notebook-resource.svg"; import NotebookIcon from "../../../images/notebook/Notebook-resource.svg";
import CollectionIcon from "../../../images/tree-collection.svg"; import CollectionIcon from "../../../images/tree-collection.svg";
import * as Constants from "../../Common/Constants"; import * as Constants from "../../Common/Constants";
@@ -126,12 +125,8 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
useDatabases.getState().sampleDataResourceTokenCollection useDatabases.getState().sampleDataResourceTokenCollection
) { ) {
return ( return (
<Stack <Stack style={{ width: "66%", cursor: "pointer", margin: "40px auto" }} tokens={{ childrenGap: 16 }}>
className="splashStackContainer" <Stack horizontal tokens={{ childrenGap: 16 }}>
style={{ width: "66%", cursor: "pointer", margin: "40px auto" }}
tokens={{ childrenGap: 16 }}
>
<Stack className="splashStackRow" horizontal>
<SplashScreenButton <SplashScreenButton
imgSrc={QuickStartIcon} imgSrc={QuickStartIcon}
title={"Launch quick start"} title={"Launch quick start"}
@@ -151,7 +146,7 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
}} }}
/> />
</Stack> </Stack>
<Stack className="splashStackRow" horizontal> <Stack horizontal tokens={{ childrenGap: 16 }}>
{useQueryCopilot.getState().copilotEnabled && ( {useQueryCopilot.getState().copilotEnabled && (
<SplashScreenButton <SplashScreenButton
imgSrc={CopilotIcon} imgSrc={CopilotIcon}
@@ -295,10 +290,10 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
<form className="connectExplorerFormContainer"> <form className="connectExplorerFormContainer">
<div className="splashScreenContainer"> <div className="splashScreenContainer">
<div className="splashScreen"> <div className="splashScreen">
<h2 className="title" role="heading" aria-label={title}> <h1 className="title" role="heading" aria-label={title}>
{title} {title}
<FeaturePanelLauncher /> <FeaturePanelLauncher />
</h2> </h1>
<div className="subtitle">{subtitle}</div> <div className="subtitle">{subtitle}</div>
{this.getSplashScreenButtons()} {this.getSplashScreenButtons()}
{useCarousel.getState().showCoachMark && ( {useCarousel.getState().showCoachMark && (
@@ -463,10 +458,10 @@ export class SplashScreen extends React.Component<SplashScreenProps> {
} }
if (userContext.apiType === "VCoreMongo") { if (userContext.apiType === "VCoreMongo") {
icon = VisualStudioIcon; icon = ContainersIcon;
title = "Connect with VS Code"; title = "Connect with Studio 3T";
description = "Query and Manage your MongoDB cluster in Visual Studio Code"; description = "Prefer Studio 3T? Find your connection strings here";
onClick = () => this.container.openInVsCode(); onClick = () => useTabs.getState().openAndActivateReactTab(ReactTabKind.Connect);
} }
return { return {

View File

@@ -43,52 +43,32 @@ export const startCloudShellTerminal = async (terminal: Terminal, shellType: Ter
await ensureCloudShellProviderRegistered(); await ensureCloudShellProviderRegistered();
resolvedRegion = determineCloudShellRegion(); resolvedRegion = determineCloudShellRegion();
resolvedRegion = determineCloudShellRegion();
terminal.writeln(formatWarningMessage("⚠️ IMPORTANT: Azure Cloud Shell Region Notice ⚠️"));
terminal.writeln(
formatInfoMessage(
"The Cloud Shell environment will operate in a region that may differ from your database's region.",
),
);
terminal.writeln(formatInfoMessage("This has two potential implications:"));
terminal.writeln(formatInfoMessage("1. Performance Impact:"));
terminal.writeln(
formatInfoMessage(" Commands may experience higher latency due to geographic distance between regions."),
);
terminal.writeln(formatInfoMessage("2. Data Compliance Considerations:"));
terminal.writeln(
formatInfoMessage(
" Data processed through this shell could temporarily reside in a different geographic region,",
),
);
terminal.writeln(
formatInfoMessage(" which may affect compliance with data residency requirements or regulations specific"),
);
terminal.writeln(formatInfoMessage(" to your organization."));
terminal.writeln("");
terminal.writeln("\x1b[94mFor more information on Azure Cosmos DB data governance and compliance, please visit:");
terminal.writeln("\x1b[94mhttps://learn.microsoft.com/en-us/azure/cosmos-db/data-residency\x1b[0m");
// Ask for user consent for region // Ask for user consent for region
const consentGranted = await askConfirmation(terminal, formatWarningMessage("Do you wish to proceed?")); const consentGranted = await askConfirmation(
terminal,
formatWarningMessage(
"The shell environment may be operating in a region different from that of the database, which could impact performance or data compliance. Do you wish to proceed?",
),
);
// Track user decision // Track user decision
TelemetryProcessor.trace( TelemetryProcessor.trace(
Action.CloudShellUserConsent, Action.CloudShellUserConsent,
consentGranted ? ActionModifiers.Success : ActionModifiers.Cancel, consentGranted ? ActionModifiers.Success : ActionModifiers.Cancel,
{ { dataExplorerArea: Areas.CloudShell },
dataExplorerArea: Areas.CloudShell,
shellType: TerminalKind[shellType],
isConsent: consentGranted,
region: resolvedRegion,
},
startKey,
); );
if (!consentGranted) { if (!consentGranted) {
TelemetryProcessor.traceCancel(
Action.CloudShellTerminalSession,
{
shellType: TerminalKind[shellType],
dataExplorerArea: Areas.CloudShell,
region: resolvedRegion,
isConsent: false,
},
startKey,
);
terminal.writeln( terminal.writeln(
formatErrorMessage("Session ended. Please close this tab and initiate a new shell session if needed."), formatErrorMessage("Session ended. Please close this tab and initiate a new shell session if needed."),
); );
@@ -282,27 +262,28 @@ export const configureSocketConnection = async (
}; };
export const sendTerminalStartupCommands = (socket: WebSocket, initCommands: string): void => { export const sendTerminalStartupCommands = (socket: WebSocket, initCommands: string): void => {
// ensures connections don't remain open indefinitely by implementing an automatic timeout after 120 minutes.
const keepSocketAlive = (socket: WebSocket) => {
if (socket.readyState === WebSocket.OPEN) {
if (pingCount >= MAX_PING_COUNT) {
socket.close();
} else {
pingCount++;
// The code uses a recursive setTimeout pattern rather than setInterval,
// which ensures each new ping only happens after the previous one completes
// and naturally stops if the socket closes.
keepAliveID = setTimeout(() => keepSocketAlive(socket), 1000);
}
}
};
if (socket && socket.readyState === WebSocket.OPEN) { if (socket && socket.readyState === WebSocket.OPEN) {
socket.send(initCommands); socket.send(initCommands);
keepSocketAlive(socket);
} else { } else {
socket.onopen = () => { socket.onopen = () => {
socket.send(initCommands); socket.send(initCommands);
// ensures connections don't remain open indefinitely by implementing an automatic timeout after 20 minutes.
const keepSocketAlive = (socket: WebSocket) => {
if (socket.readyState === WebSocket.OPEN) {
if (pingCount >= MAX_PING_COUNT) {
socket.close();
} else {
socket.send("");
pingCount++;
// The code uses a recursive setTimeout pattern rather than setInterval,
// which ensures each new ping only happens after the previous one completes
// and naturally stops if the socket closes.
keepAliveID = setTimeout(() => keepSocketAlive(socket), 1000);
}
}
};
keepSocketAlive(socket); keepSocketAlive(socket);
}; };
} }

View File

@@ -22,12 +22,6 @@ export const EXIT_COMMAND = ` printf "\\033[1;31mSession ended. Please close thi
* the required methods. * the required methods.
*/ */
export abstract class AbstractShellHandler { export abstract class AbstractShellHandler {
/**
* The name of the application using this shell handler.
* This is used for telemetry and logging purposes.
*/
protected APP_NAME = "CosmosExplorerTerminal";
abstract getShellName(): string; abstract getShellName(): string;
abstract getSetUpCommands(): string[]; abstract getSetUpCommands(): string[];
abstract getConnectionCommand(): string; abstract getConnectionCommand(): string;
@@ -62,30 +56,4 @@ export abstract class AbstractShellHandler {
return allCommands.join("\n").concat("\n"); return allCommands.join("\n").concat("\n");
} }
/**
* Setup commands for MongoDB shell:
*
* 1. Check if mongosh is already installed
* 2. Download mongosh package if not installed
* 3. Extract the package to access mongosh binaries
* 4. Move extracted files to ~/mongosh directory
* 5. Add mongosh binary path to system PATH
* 6. Apply PATH changes by sourcing .bashrc
*
* Each command runs conditionally only if mongosh
* is not already present in the environment.
*/
protected mongoShellSetupCommands(): string[] {
const PACKAGE_VERSION: string = "2.5.0";
return [
"if ! command -v mongosh &> /dev/null; then echo '⚠️ mongosh not found. Installing...'; fi",
`if ! command -v mongosh &> /dev/null; then curl -LO https://downloads.mongodb.com/compass/mongosh-${PACKAGE_VERSION}-linux-x64.tgz; fi`,
`if ! command -v mongosh &> /dev/null; then tar -xvzf mongosh-${PACKAGE_VERSION}-linux-x64.tgz; fi`,
`if ! command -v mongosh &> /dev/null; then mkdir -p ~/mongosh/bin && mv mongosh-${PACKAGE_VERSION}-linux-x64/bin/mongosh ~/mongosh/bin/ && chmod +x ~/mongosh/bin/mongosh; fi`,
`if ! command -v mongosh &> /dev/null; then rm -rf mongosh-${PACKAGE_VERSION}-linux-x64 mongosh-${PACKAGE_VERSION}-linux-x64.tgz; fi`,
"if ! command -v mongosh &> /dev/null; then echo 'export PATH=$HOME/mongosh/bin:$PATH' >> ~/.bashrc; fi",
"source ~/.bashrc",
];
}
} }

View File

@@ -87,7 +87,7 @@ describe("CassandraShellHandler", () => {
}); });
test("should return correct connection command", () => { test("should return correct connection command", () => {
const expectedCommand = `cqlsh test-endpoint.cassandra.cosmos.azure.com 10350 -u test-account -p test-key --ssl`; const expectedCommand = "cqlsh test-endpoint.cassandra.cosmos.azure.com 10350 -u test-account -p test-key --ssl";
expect(handler.getConnectionCommand()).toBe(expectedCommand); expect(handler.getConnectionCommand()).toBe(expectedCommand);
expect(CommonUtils.getHostFromUrl).toHaveBeenCalledWith("https://test-endpoint.cassandra.cosmos.azure.com:443/"); expect(CommonUtils.getHostFromUrl).toHaveBeenCalledWith("https://test-endpoint.cassandra.cosmos.azure.com:443/");

View File

@@ -68,7 +68,7 @@ describe("MongoShellHandler", () => {
const commands = mongoShellHandler.getSetUpCommands(); const commands = mongoShellHandler.getSetUpCommands();
expect(Array.isArray(commands)).toBe(true); expect(Array.isArray(commands)).toBe(true);
expect(commands.length).toBe(7); expect(commands.length).toBe(6);
expect(commands[1]).toContain("mongosh-2.5.0-linux-x64.tgz"); expect(commands[1]).toContain("mongosh-2.5.0-linux-x64.tgz");
}); });
}); });
@@ -91,7 +91,7 @@ describe("MongoShellHandler", () => {
const command = mongoShellHandler.getConnectionCommand(); const command = mongoShellHandler.getConnectionCommand();
expect(command).toBe( expect(command).toBe(
"mongosh mongodb://test-mongo.documents.azure.com:10255?appName=CosmosExplorerTerminal --username test-account --password test-key --tls --tlsAllowInvalidCertificates", "mongosh --host test-mongo.documents.azure.com --port 10255 --username test-account --password test-key --tls --tlsAllowInvalidCertificates",
); );
expect(CommonUtils.getHostFromUrl).toHaveBeenCalledWith("https://test-mongo.documents.azure.com:443/"); expect(CommonUtils.getHostFromUrl).toHaveBeenCalledWith("https://test-mongo.documents.azure.com:443/");

View File

@@ -2,6 +2,8 @@ import { userContext } from "../../../../UserContext";
import { getHostFromUrl } from "../Utils/CommonUtils"; import { getHostFromUrl } from "../Utils/CommonUtils";
import { AbstractShellHandler } from "./AbstractShellHandler"; import { AbstractShellHandler } from "./AbstractShellHandler";
const PACKAGE_VERSION: string = "2.5.0";
export class MongoShellHandler extends AbstractShellHandler { export class MongoShellHandler extends AbstractShellHandler {
private _key: string; private _key: string;
private _endpoint: string | undefined; private _endpoint: string | undefined;
@@ -16,7 +18,14 @@ export class MongoShellHandler extends AbstractShellHandler {
} }
public getSetUpCommands(): string[] { public getSetUpCommands(): string[] {
return this.mongoShellSetupCommands(); return [
"if ! command -v mongosh &> /dev/null; then echo '⚠️ mongosh not found. Installing...'; fi",
`if ! command -v mongosh &> /dev/null; then curl -LO https://downloads.mongodb.com/compass/mongosh-${PACKAGE_VERSION}-linux-x64.tgz; fi`,
`if ! command -v mongosh &> /dev/null; then tar -xvzf mongosh-${PACKAGE_VERSION}-linux-x64.tgz; fi`,
`if ! command -v mongosh &> /dev/null; then mkdir -p ~/mongosh && mv mongosh-${PACKAGE_VERSION}-linux-x64/* ~/mongosh/; fi`,
"if ! command -v mongosh &> /dev/null; then echo 'export PATH=$HOME/mongosh/bin:$PATH' >> ~/.bashrc; fi",
"source ~/.bashrc",
];
} }
public getConnectionCommand(): string { public getConnectionCommand(): string {
@@ -28,17 +37,9 @@ export class MongoShellHandler extends AbstractShellHandler {
if (!dbName) { if (!dbName) {
return "echo 'Database name not found.'"; return "echo 'Database name not found.'";
} }
return ( return `mongosh --host ${getHostFromUrl(this._endpoint)} --port 10255 --username ${dbName} --password ${
"mongosh mongodb://" + this._key
getHostFromUrl(this._endpoint) + } --tls --tlsAllowInvalidCertificates`;
":10255?appName=" +
this.APP_NAME +
" --username " +
dbName +
" --password " +
this._key +
" --tls --tlsAllowInvalidCertificates"
);
} }
public getTerminalSuppressedData(): string { public getTerminalSuppressedData(): string {

View File

@@ -54,7 +54,7 @@ export class PostgresShellHandler extends AbstractShellHandler {
// All Azure Cosmos DB PostgreSQL deployments follow this convention. // All Azure Cosmos DB PostgreSQL deployments follow this convention.
// Ref. https://learn.microsoft.com/en-us/azure/cosmos-db/postgresql/reference-limits#database-creation // Ref. https://learn.microsoft.com/en-us/azure/cosmos-db/postgresql/reference-limits#database-creation
const loginName = userContext.postgresConnectionStrParams.adminLogin; const loginName = userContext.postgresConnectionStrParams.adminLogin;
return `psql -h "${this._endpoint}" -p 5432 -d "citus" -U "${loginName}" --set=sslmode=require --set=application_name=${this.APP_NAME}`; return `psql -h "${this._endpoint}" -p 5432 -d "citus" -U "${loginName}" --set=sslmode=require`;
} }
public getTerminalSuppressedData(): string { public getTerminalSuppressedData(): string {

View File

@@ -44,7 +44,7 @@ describe("VCoreMongoShellHandler", () => {
const commands = vcoreMongoShellHandler.getSetUpCommands(); const commands = vcoreMongoShellHandler.getSetUpCommands();
expect(Array.isArray(commands)).toBe(true); expect(Array.isArray(commands)).toBe(true);
expect(commands.length).toBe(7); expect(commands.length).toBe(6);
expect(commands[1]).toContain("mongosh-2.5.0-linux-x64.tgz"); expect(commands[1]).toContain("mongosh-2.5.0-linux-x64.tgz");
expect(commands[0]).toContain("mongosh not found"); expect(commands[0]).toContain("mongosh not found");
}); });

View File

@@ -1,6 +1,8 @@
import { userContext } from "../../../../UserContext"; import { userContext } from "../../../../UserContext";
import { AbstractShellHandler } from "./AbstractShellHandler"; import { AbstractShellHandler } from "./AbstractShellHandler";
const PACKAGE_VERSION: string = "2.5.0";
export class VCoreMongoShellHandler extends AbstractShellHandler { export class VCoreMongoShellHandler extends AbstractShellHandler {
private _endpoint: string | undefined; private _endpoint: string | undefined;
@@ -13,8 +15,28 @@ export class VCoreMongoShellHandler extends AbstractShellHandler {
return "MongoDB VCore"; return "MongoDB VCore";
} }
/**
* Setup commands for MongoDB VCore shell:
*
* 1. Check if mongosh is already installed
* 2. Download mongosh package if not installed
* 3. Extract the package to access mongosh binaries
* 4. Move extracted files to ~/mongosh directory
* 5. Add mongosh binary path to system PATH
* 6. Apply PATH changes by sourcing .bashrc
*
* Each command runs conditionally only if mongosh
* is not already present in the environment.
*/
public getSetUpCommands(): string[] { public getSetUpCommands(): string[] {
return this.mongoShellSetupCommands(); return [
"if ! command -v mongosh &> /dev/null; then echo '⚠️ mongosh not found. Installing...'; fi",
`if ! command -v mongosh &> /dev/null; then curl -LO https://downloads.mongodb.com/compass/mongosh-${PACKAGE_VERSION}-linux-x64.tgz; fi`,
`if ! command -v mongosh &> /dev/null; then tar -xvzf mongosh-${PACKAGE_VERSION}-linux-x64.tgz; fi`,
`if ! command -v mongosh &> /dev/null; then mkdir -p ~/mongosh && mv mongosh-${PACKAGE_VERSION}-linux-x64/* ~/mongosh/; fi`,
"if ! command -v mongosh &> /dev/null; then echo 'export PATH=$HOME/mongosh/bin:$PATH' >> ~/.bashrc; fi",
"source ~/.bashrc",
];
} }
public getConnectionCommand(): string { public getConnectionCommand(): string {
@@ -23,7 +45,7 @@ export class VCoreMongoShellHandler extends AbstractShellHandler {
} }
const userName = userContext.vcoreMongoConnectionParams.adminLogin; const userName = userContext.vcoreMongoConnectionParams.adminLogin;
return `mongosh "mongodb+srv://${userName}:@${this._endpoint}/?authMechanism=SCRAM-SHA-256&retrywrites=false&maxIdleTimeMS=120000&appName=${this.APP_NAME}"`; return `mongosh "mongodb+srv://${userName}:@${this._endpoint}/?authMechanism=SCRAM-SHA-256&retrywrites=false&maxIdleTimeMS=120000"`;
} }
public getTerminalSuppressedData(): string { public getTerminalSuppressedData(): string {

View File

@@ -1,6 +1,5 @@
import { AbstractShellHandler } from "Explorer/Tabs/CloudShellTab/ShellTypes/AbstractShellHandler";
import { IDisposable, ITerminalAddon, Terminal } from "@xterm/xterm"; import { IDisposable, ITerminalAddon, Terminal } from "@xterm/xterm";
import { AbstractShellHandler } from "../ShellTypes/AbstractShellHandler";
import { formatErrorMessage } from "./TerminalLogFormats";
interface IAttachOptions { interface IAttachOptions {
bidirectional?: boolean; bidirectional?: boolean;
@@ -57,27 +56,8 @@ export class AttachAddon implements ITerminalAddon {
this._disposables.push(terminal.onBinary((data) => this._sendBinary(data))); this._disposables.push(terminal.onBinary((data) => this._sendBinary(data)));
} }
this._disposables.push(addSocketListener(this._socket, "close", () => this._handleSocketClose(terminal))); this._disposables.push(addSocketListener(this._socket, "close", () => this.dispose()));
this._disposables.push(addSocketListener(this._socket, "error", () => this._handleSocketClose(terminal))); this._disposables.push(addSocketListener(this._socket, "error", () => this.dispose()));
}
/**
* Handles socket close events by terminating processes and showing a message
*/
private _handleSocketClose(terminal: Terminal): void {
if (terminal) {
terminal.writeln(
formatErrorMessage("Session ended. Please close this tab and initiate a new shell session if needed."),
);
// Send exit command to terminal
if (this._bidirectional) {
terminal.write(formatErrorMessage("exit\r\n"));
}
}
// Clean up resources
this.dispose();
} }
/** /**

View File

@@ -26,6 +26,7 @@ import { useDialog } from "Explorer/Controls/Dialog";
import { EditorReact } from "Explorer/Controls/Editor/EditorReact"; import { EditorReact } from "Explorer/Controls/Editor/EditorReact";
import { InputDataList, InputDatalistDropdownOptionSection } from "Explorer/Controls/InputDataList/InputDataList"; import { InputDataList, InputDatalistDropdownOptionSection } from "Explorer/Controls/InputDataList/InputDataList";
import { ProgressModalDialog } from "Explorer/Controls/ProgressModalDialog"; import { ProgressModalDialog } from "Explorer/Controls/ProgressModalDialog";
import Explorer from "Explorer/Explorer";
import { useCommandBar } from "Explorer/Menus/CommandBar/CommandBarComponentAdapter"; import { useCommandBar } from "Explorer/Menus/CommandBar/CommandBarComponentAdapter";
import { querySampleDocuments, readSampleDocument } from "Explorer/QueryCopilot/QueryCopilotUtilities"; import { querySampleDocuments, readSampleDocument } from "Explorer/QueryCopilot/QueryCopilotUtilities";
import { import {
@@ -63,7 +64,7 @@ import * as Logger from "../../../Common/Logger";
import * as MongoProxyClient from "../../../Common/MongoProxyClient"; import * as MongoProxyClient from "../../../Common/MongoProxyClient";
import * as DataModels from "../../../Contracts/DataModels"; import * as DataModels from "../../../Contracts/DataModels";
import * as ViewModels from "../../../Contracts/ViewModels"; import * as ViewModels from "../../../Contracts/ViewModels";
import { CollectionBase, UploadDetailsRecord } from "../../../Contracts/ViewModels"; import { CollectionBase } from "../../../Contracts/ViewModels";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor"; import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import * as QueryUtils from "../../../Utils/QueryUtils"; import * as QueryUtils from "../../../Utils/QueryUtils";
import { defaultQueryFields, extractPartitionKeyValues } from "../../../Utils/QueryUtils"; import { defaultQueryFields, extractPartitionKeyValues } from "../../../Utils/QueryUtils";
@@ -143,13 +144,6 @@ export const useDocumentsTabStyles = makeStyles({
deleteProgressContent: { deleteProgressContent: {
paddingTop: tokens.spacingVerticalL, paddingTop: tokens.spacingVerticalL,
}, },
smallScreenContent: {
"@media (max-width: 420px)": {
flexWrap: "wrap",
minHeight: "max-content",
padding: "4px",
},
},
}); });
export class DocumentsTabV2 extends TabsBase { export class DocumentsTabV2 extends TabsBase {
@@ -308,6 +302,7 @@ type UiKeyboardEvent = (e: KeyboardEvent | React.SyntheticEvent<Element, Event>)
// Export to expose to unit tests // Export to expose to unit tests
export type ButtonsDependencies = { export type ButtonsDependencies = {
_collection: ViewModels.CollectionBase;
selectedRows: Set<TableRowId>; selectedRows: Set<TableRowId>;
editorState: ViewModels.DocumentExplorerState; editorState: ViewModels.DocumentExplorerState;
isPreferredApiMongoDB: boolean; isPreferredApiMongoDB: boolean;
@@ -318,7 +313,26 @@ export type ButtonsDependencies = {
onSaveExistingDocumentClick: UiKeyboardEvent; onSaveExistingDocumentClick: UiKeyboardEvent;
onRevertExistingDocumentClick: UiKeyboardEvent; onRevertExistingDocumentClick: UiKeyboardEvent;
onDeleteExistingDocumentsClick: UiKeyboardEvent; onDeleteExistingDocumentsClick: UiKeyboardEvent;
onUploadDocumentsClick: UiKeyboardEvent; };
const createUploadButton = (container: Explorer): CommandButtonComponentProps => {
const label = "Upload Item";
return {
id: UPLOAD_BUTTON_ID,
iconSrc: UploadIcon,
iconAlt: label,
onCommandClick: () => {
const selectedCollection: ViewModels.Collection = useSelectedNode.getState().findSelectedCollection();
selectedCollection && container.openUploadItemsPane();
},
commandButtonLabel: label,
ariaLabel: label,
hasPopup: true,
disabled:
useSelectedNode.getState().isDatabaseNodeOrNoneSelected() ||
!useClientWriteEnabled.getState().clientWriteEnabled ||
useSelectedNode.getState().isQueryCopilotCollectionSelected(),
};
}; };
// Export to expose to unit tests // Export to expose to unit tests
@@ -331,6 +345,7 @@ export const UPLOAD_BUTTON_ID = "uploadItemBtn";
// Export to expose in unit tests // Export to expose in unit tests
export const getTabsButtons = ({ export const getTabsButtons = ({
_collection,
selectedRows, selectedRows,
editorState, editorState,
isPreferredApiMongoDB, isPreferredApiMongoDB,
@@ -341,7 +356,6 @@ export const getTabsButtons = ({
onSaveExistingDocumentClick, onSaveExistingDocumentClick,
onRevertExistingDocumentClick, onRevertExistingDocumentClick,
onDeleteExistingDocumentsClick, onDeleteExistingDocumentsClick,
onUploadDocumentsClick,
}: ButtonsDependencies): CommandButtonComponentProps[] => { }: ButtonsDependencies): CommandButtonComponentProps[] => {
if (isFabric() && userContext.fabricContext?.isReadOnly) { if (isFabric() && userContext.fabricContext?.isReadOnly) {
// All the following buttons require write access // All the following buttons require write access
@@ -453,20 +467,7 @@ export const getTabsButtons = ({
} }
if (!isPreferredApiMongoDB) { if (!isPreferredApiMongoDB) {
const label = "Upload Item"; buttons.push(createUploadButton(_collection.container));
buttons.push({
id: UPLOAD_BUTTON_ID,
iconSrc: UploadIcon,
iconAlt: label,
onCommandClick: onUploadDocumentsClick,
commandButtonLabel: label,
ariaLabel: label,
hasPopup: true,
disabled:
useSelectedNode.getState().isDatabaseNodeOrNoneSelected() ||
!useClientWriteEnabled.getState().clientWriteEnabled ||
useSelectedNode.getState().isQueryCopilotCollectionSelected(),
});
} }
return buttons; return buttons;
@@ -671,7 +672,6 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
collection: CollectionBase; collection: CollectionBase;
}>(undefined); }>(undefined);
const [bulkDeleteMode, setBulkDeleteMode] = useState<"inProgress" | "completed" | "aborting" | "aborted">(undefined); const [bulkDeleteMode, setBulkDeleteMode] = useState<"inProgress" | "completed" | "aborting" | "aborted">(undefined);
const [abortController, setAbortController] = useState<AbortController | undefined>(undefined);
const setKeyboardActions = useKeyboardActionGroup(KeyboardActionGroup.ACTIVE_TAB); const setKeyboardActions = useKeyboardActionGroup(KeyboardActionGroup.ACTIVE_TAB);
@@ -699,19 +699,13 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
return; return;
} }
if (bulkDeleteProcess.pendingIds.length === 0 && bulkDeleteProcess.throttledIds.length === 0) { if (
// Successfully deleted all documents (bulkDeleteProcess.pendingIds.length === 0 && bulkDeleteProcess.throttledIds.length === 0) ||
bulkDeleteMode === "aborting"
) {
// Successfully deleted all documents or operation was aborted
bulkDeleteOperation.onCompleted(bulkDeleteProcess.successfulIds); bulkDeleteOperation.onCompleted(bulkDeleteProcess.successfulIds);
setBulkDeleteMode("completed"); setBulkDeleteMode(bulkDeleteMode === "aborting" ? "aborted" : "completed");
return;
}
if (bulkDeleteMode === "aborting") {
// Operation was aborted
abortController?.abort();
bulkDeleteOperation.onCompleted(bulkDeleteProcess.successfulIds);
setBulkDeleteMode("aborted");
setAbortController(undefined);
return; return;
} }
@@ -719,10 +713,8 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
const newPendingIds = bulkDeleteProcess.pendingIds.concat(bulkDeleteProcess.throttledIds); const newPendingIds = bulkDeleteProcess.pendingIds.concat(bulkDeleteProcess.throttledIds);
const timeout = bulkDeleteProcess.beforeExecuteMs || 0; const timeout = bulkDeleteProcess.beforeExecuteMs || 0;
const ac = new AbortController();
setAbortController(ac);
setTimeout(() => { setTimeout(() => {
deleteNoSqlDocuments(bulkDeleteOperation.collection, [...newPendingIds], ac.signal) deleteNoSqlDocuments(bulkDeleteOperation.collection, [...newPendingIds])
.then((deleteResult) => { .then((deleteResult) => {
let retryAfterMilliseconds = 0; let retryAfterMilliseconds = 0;
const newSuccessful: DocumentId[] = []; const newSuccessful: DocumentId[] = [];
@@ -737,7 +729,7 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
} else if (result.statusCode >= 400) { } else if (result.statusCode >= 400) {
newFailed.push(result.documentId); newFailed.push(result.documentId);
logConsoleError( logConsoleError(
`Failed to delete document ${result.documentId.id()} with status code ${result.statusCode}`, `Failed to delete document ${result.documentId.id} with status code ${result.statusCode}`,
); );
} }
}); });
@@ -878,6 +870,7 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
} }
updateNavbarWithTabsButtons(isTabActive, { updateNavbarWithTabsButtons(isTabActive, {
_collection,
selectedRows, selectedRows,
editorState, editorState,
isPreferredApiMongoDB, isPreferredApiMongoDB,
@@ -888,7 +881,6 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
onSaveExistingDocumentClick, onSaveExistingDocumentClick,
onRevertExistingDocumentClick, onRevertExistingDocumentClick,
onDeleteExistingDocumentsClick, onDeleteExistingDocumentsClick,
onUploadDocumentsClick,
}); });
}, []); }, []);
@@ -1294,47 +1286,11 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
); );
}, [deleteDocuments, documentIds, isPreferredApiMongoDB, selectedRows]); }, [deleteDocuments, documentIds, isPreferredApiMongoDB, selectedRows]);
const onUploadDocumentsClick = useCallback((): void => {
if (!isPreferredApiMongoDB) {
const onSuccessUpload = (data: UploadDetailsRecord[]) => {
const addedIdsSet = new Set(
data
.reduce(
(result: ItemDefinition[], record) =>
result.concat(record.resources && record.resources.length ? record.resources : []),
[],
)
.map((document) => {
const partitionKeyValueArray: PartitionKey[] = extractPartitionKeyValues(
document,
partitionKey as PartitionKeyDefinition,
);
return newDocumentId(
document as ItemDefinition & Resource,
partitionKeyProperties,
partitionKeyValueArray as string[],
);
}),
);
const documents = new Set(documentIds);
addedIdsSet.forEach((item) => documents.add(item));
setDocumentIds(Array.from(documents));
setSelectedDocumentContent(undefined);
setClickedRowIndex(undefined);
setSelectedRows(new Set());
setEditorState(ViewModels.DocumentExplorerState.noDocumentSelected);
};
_collection.container.openUploadItemsPane(onSuccessUpload);
}
}, [_collection.container, documentIds, isPreferredApiMongoDB, newDocumentId, partitionKey, partitionKeyProperties]);
// If editor state changes, update the nav // If editor state changes, update the nav
useEffect( useEffect(
() => () =>
updateNavbarWithTabsButtons(isTabActive, { updateNavbarWithTabsButtons(isTabActive, {
_collection,
selectedRows, selectedRows,
editorState, editorState,
isPreferredApiMongoDB, isPreferredApiMongoDB,
@@ -1343,11 +1299,11 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
onSaveNewDocumentClick, onSaveNewDocumentClick,
onRevertNewDocumentClick, onRevertNewDocumentClick,
onSaveExistingDocumentClick, onSaveExistingDocumentClick,
onRevertExistingDocumentClick, onRevertExistingDocumentClick: onRevertExistingDocumentClick,
onDeleteExistingDocumentsClick, onDeleteExistingDocumentsClick: onDeleteExistingDocumentsClick,
onUploadDocumentsClick,
}), }),
[ [
_collection,
selectedRows, selectedRows,
editorState, editorState,
isPreferredApiMongoDB, isPreferredApiMongoDB,
@@ -1358,7 +1314,6 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
onSaveExistingDocumentClick, onSaveExistingDocumentClick,
onRevertExistingDocumentClick, onRevertExistingDocumentClick,
onDeleteExistingDocumentsClick, onDeleteExistingDocumentsClick,
onUploadDocumentsClick,
isTabActive, isTabActive,
], ],
); );
@@ -2147,7 +2102,7 @@ export const DocumentsTabComponent: React.FunctionComponent<IDocumentsTabCompone
return ( return (
<CosmosFluentProvider className={styles.container}> <CosmosFluentProvider className={styles.container}>
<div data-test={"DocumentsTab"} className="tab-pane active" role="tabpanel" style={{ display: "flex" }}> <div data-test={"DocumentsTab"} className="tab-pane active" role="tabpanel" style={{ display: "flex" }}>
<div data-test={"DocumentsTab/Filter"} className={`${styles.filterRow} ${styles.smallScreenContent}`}> <div data-test={"DocumentsTab/Filter"} className={styles.filterRow}>
{!isPreferredApiMongoDB && <span> SELECT * FROM c </span>} {!isPreferredApiMongoDB && <span> SELECT * FROM c </span>}
<InputDataList <InputDataList
dropdownOptions={getFilterChoices()} dropdownOptions={getFilterChoices()}

View File

@@ -15,7 +15,7 @@ exports[`Documents tab (noSql API) when rendered should render the page 1`] = `
} }
> >
<div <div
className="___11ktxfv_0000000 f1o614cb fy9rknc f22iagw fsnqrgy f1f5gg8d fjodcmx f122n59 f1f09k3d fg706s2 frpde29 ___1ngl8o6_0000000 fz7mnu6 fl3egqs flhmrkm" className="___11ktxfv_0000000 f1o614cb fy9rknc f22iagw fsnqrgy f1f5gg8d fjodcmx f122n59 f1f09k3d fg706s2 frpde29"
data-test="DocumentsTab/Filter" data-test="DocumentsTab/Filter"
> >
<span> <span>

View File

@@ -8,8 +8,6 @@ import RunQuery from "../../../../images/RunQuery.png";
import { QueryResults } from "../../../Contracts/ViewModels"; import { QueryResults } from "../../../Contracts/ViewModels";
import { ErrorList } from "./ErrorList"; import { ErrorList } from "./ErrorList";
import { ResultsView } from "./ResultsView"; import { ResultsView } from "./ResultsView";
import useZoomLevel from "hooks/useZoomLevel";
import { conditionalClass } from "Utils/StyleUtils";
export interface ResultsViewProps { export interface ResultsViewProps {
isMongoDB: boolean; isMongoDB: boolean;
@@ -25,16 +23,11 @@ interface QueryResultProps extends ResultsViewProps {
const ExecuteQueryCallToAction: React.FC = () => { const ExecuteQueryCallToAction: React.FC = () => {
const styles = useQueryTabStyles(); const styles = useQueryTabStyles();
const isZoomed = useZoomLevel();
return ( return (
<div data-test="QueryTab/ResultsPane/ExecuteCTA" className={styles.executeCallToAction}> <div data-test="QueryTab/ResultsPane/ExecuteCTA" className={styles.executeCallToAction}>
<div> <div>
<p> <p>
<img <img src={RunQuery} aria-hidden="true" />
className={`${styles.responsiveImg} ${conditionalClass(isZoomed, styles.zoomedImageSize)}`}
src={RunQuery}
aria-hidden="true"
/>
</p> </p>
<p>Execute a query to see the results</p> <p>Execute a query to see the results</p>
</div> </div>

View File

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

View File

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

View File

@@ -25,9 +25,6 @@ export const useQueryTabStyles = makeStyles({
height: "100%", height: "100%",
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "column",
"@media (max-width: 420px)": {
overflow: "scroll",
},
}, },
queryResultsMessage: { queryResultsMessage: {
...shorthands.margin("5px"), ...shorthands.margin("5px"),
@@ -41,9 +38,6 @@ export const useQueryTabStyles = makeStyles({
display: "flex", display: "flex",
rowGap: "12px", rowGap: "12px",
flexDirection: "column", flexDirection: "column",
"@media (max-width: 420px)": {
height: "auto",
},
}, },
queryResultsTabContentContainer: { queryResultsTabContentContainer: {
display: "flex", display: "flex",
@@ -99,12 +93,4 @@ export const useQueryTabStyles = makeStyles({
display: "flex", display: "flex",
flexDirection: "row", flexDirection: "row",
}, },
responsiveImg: {
"@media (max-width: 420px)": {
width: "50px",
},
},
zoomedImageSize: {
width: "60px",
},
}); });

View File

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

View File

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

View File

@@ -21,7 +21,7 @@ import { readTriggers } from "../../Common/dataAccess/readTriggers";
import { readUserDefinedFunctions } from "../../Common/dataAccess/readUserDefinedFunctions"; import { readUserDefinedFunctions } from "../../Common/dataAccess/readUserDefinedFunctions";
import * as DataModels from "../../Contracts/DataModels"; import * as DataModels from "../../Contracts/DataModels";
import * as ViewModels from "../../Contracts/ViewModels"; import * as ViewModels from "../../Contracts/ViewModels";
import { BulkInsertResult, UploadDetailsRecord } from "../../Contracts/ViewModels"; import { UploadDetailsRecord } from "../../Contracts/ViewModels";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants"; import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../../UserContext"; import { userContext } from "../../UserContext";
@@ -1092,13 +1092,17 @@ export default class Collection implements ViewModels.Collection {
}); });
} }
public async bulkInsertDocuments(documents: JSONObject[]): Promise<BulkInsertResult> { public async bulkInsertDocuments(documents: JSONObject[]): Promise<{
const stats: BulkInsertResult = { numSucceeded: number;
numFailed: number;
numThrottled: number;
errors: string[];
}> {
const stats = {
numSucceeded: 0, numSucceeded: 0,
numFailed: 0, numFailed: 0,
numThrottled: 0, numThrottled: 0,
errors: [] as string[], errors: [] as string[],
resources: [],
}; };
const chunkSize = 100; // 100 is the max # of bulk operations the SDK currently accepts const chunkSize = 100; // 100 is the max # of bulk operations the SDK currently accepts
@@ -1116,12 +1120,8 @@ export default class Collection implements ViewModels.Collection {
responses.forEach((response, index) => { responses.forEach((response, index) => {
if (response.statusCode === 201) { if (response.statusCode === 201) {
stats.numSucceeded++; stats.numSucceeded++;
stats.resources.push(response.resourceBody);
} else if (response.statusCode === 429) { } else if (response.statusCode === 429) {
documentsToAttempt.push(attemptedDocuments[index]); documentsToAttempt.push(attemptedDocuments[index]);
} else if (response.statusCode === 409) {
stats.numFailed++;
stats.errors.push(`Document with id ${attemptedDocuments[index].id} already exists.`);
} else { } else {
stats.numFailed++; stats.numFailed++;
stats.errors.push(JSON.stringify(response.resourceBody)); stats.errors.push(JSON.stringify(response.resourceBody));
@@ -1149,22 +1149,18 @@ export default class Collection implements ViewModels.Collection {
numFailed: 0, numFailed: 0,
numThrottled: 0, numThrottled: 0,
errors: [], errors: [],
resources: [],
}; };
try { try {
const parsedContent = JSON.parse(documentContent); const parsedContent = JSON.parse(documentContent);
if (Array.isArray(parsedContent)) { if (Array.isArray(parsedContent)) {
const { numSucceeded, numFailed, numThrottled, errors, resources } = const { numSucceeded, numFailed, numThrottled, errors } = await this.bulkInsertDocuments(parsedContent);
await this.bulkInsertDocuments(parsedContent);
record.numSucceeded = numSucceeded; record.numSucceeded = numSucceeded;
record.numFailed = numFailed; record.numFailed = numFailed;
record.numThrottled = numThrottled; record.numThrottled = numThrottled;
record.errors = errors; record.errors = errors;
record.resources = record.resources.concat(resources);
} else { } else {
const resource = await createDocument(this, parsedContent); await createDocument(this, parsedContent);
record.resources.push(resource);
record.numSucceeded++; record.numSucceeded++;
} }

View File

@@ -128,6 +128,15 @@ export const checkDatabaseResourceTokensValidity = (tokenTimestamp: number): voi
} }
}; };
/**
* Open the connection tab in the settings page of the Fabric UX extension
*/
export const openSettingsConnectionTab = (): void => {
if (configContext.platform === Platform.Fabric) {
sendCachedDataMessage(FabricMessageTypes.OpenSettings, [{ settingsId: "Connection" }]);
}
};
export const isFabric = (): boolean => configContext.platform === Platform.Fabric; export const isFabric = (): boolean => configContext.platform === Platform.Fabric;
export const isFabricMirroredKey = (): boolean => export const isFabricMirroredKey = (): boolean =>
isFabric() && userContext.fabricContext?.artifactType === CosmosDbArtifactType.MIRRORED_KEY; isFabric() && userContext.fabricContext?.artifactType === CosmosDbArtifactType.MIRRORED_KEY;

View File

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

View File

@@ -149,7 +149,6 @@ export enum Action {
UploadDocuments, // Used in Fabric. Please do not rename. UploadDocuments, // Used in Fabric. Please do not rename.
CloudShellUserConsent, CloudShellUserConsent,
CloudShellTerminalSession, CloudShellTerminalSession,
OpenVSCode,
} }
export const ActionModifiers = { export const ActionModifiers = {

View File

@@ -117,7 +117,6 @@ export interface UserContext {
readonly feedbackPolicies?: AdminFeedbackPolicySettings; readonly feedbackPolicies?: AdminFeedbackPolicySettings;
readonly dataPlaneRbacEnabled?: boolean; readonly dataPlaneRbacEnabled?: boolean;
readonly refreshCosmosClient?: boolean; readonly refreshCosmosClient?: boolean;
throughputBucketsEnabled?: boolean;
} }
export type ApiType = "SQL" | "Mongo" | "Gremlin" | "Tables" | "Cassandra" | "Postgres" | "VCoreMongo"; export type ApiType = "SQL" | "Mongo" | "Gremlin" | "Tables" | "Cassandra" | "Postgres" | "VCoreMongo";

View File

@@ -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);
};

View File

@@ -1,48 +0,0 @@
import { configContext } from "ConfigContext";
import { FeatureRegistration } from "Contracts/DataModels";
import { AuthorizationTokenHeaderMetadata } from "Contracts/ViewModels";
import { getAuthorizationHeader } from "Utils/AuthorizationUtils";
export const featureRegistered = async (subscriptionId: string, feature: string) => {
const api_version = "2021-07-01";
const url = `${configContext.ARM_ENDPOINT}/subscriptions/${subscriptionId}/providers/Microsoft.Features/featureProviders/Microsoft.DocumentDB/subscriptionFeatureRegistrations/${feature}?api-version=${api_version}`;
const authorizationHeader: AuthorizationTokenHeaderMetadata = getAuthorizationHeader();
const headers = { [authorizationHeader.header]: authorizationHeader.token };
let response;
try {
response = await _fetchWithTimeout(url, headers);
} catch (error) {
return false;
}
if (!response?.ok) {
return false;
}
const featureRegistration = (await response?.json()) as FeatureRegistration;
return featureRegistration?.properties?.state === "Registered";
};
async function _fetchWithTimeout(
url: string,
headers: {
[x: string]: string;
},
) {
const timeout = 10000;
const options = { timeout };
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), timeout);
const response = await window.fetch(url, {
headers,
...options,
signal: controller.signal,
});
clearTimeout(id);
return response;
}

View File

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

View File

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

View File

@@ -1,7 +1,4 @@
import { PartitionKey, PartitionKeyDefinition } from "@azure/cosmos"; import { PartitionKey, PartitionKeyDefinition } from "@azure/cosmos";
import { getRUThreshold, ruThresholdEnabled } from "Shared/StorageUtility";
import { userContext } from "UserContext";
import { logConsoleWarning } from "Utils/NotificationConsoleUtils";
import * as DataModels from "../Contracts/DataModels"; import * as DataModels from "../Contracts/DataModels";
import * as ViewModels from "../Contracts/ViewModels"; import * as ViewModels from "../Contracts/ViewModels";
@@ -89,18 +86,6 @@ export const queryPagesUntilContentPresent = async (
results.roundTrips = roundTrips; results.roundTrips = roundTrips;
results.requestCharge = Number(results.requestCharge) + netRequestCharge; results.requestCharge = Number(results.requestCharge) + netRequestCharge;
netRequestCharge = Number(results.requestCharge); netRequestCharge = Number(results.requestCharge);
if (results.hasMoreResults && userContext.apiType === "SQL" && ruThresholdEnabled()) {
const ruThreshold: number = getRUThreshold();
if (netRequestCharge > ruThreshold) {
logConsoleWarning(
`Warning: Query has exceeded the Request Unit threshold of ${ruThreshold} RUs. Query results show only those documents returned before the threshold was exceeded`,
);
results.ruThresholdExceeded = true;
return results;
}
}
const resultsMetadata = { const resultsMetadata = {
hasMoreResults: results.hasMoreResults, hasMoreResults: results.hasMoreResults,
itemCount: results.itemCount, itemCount: results.itemCount,
@@ -139,7 +124,7 @@ export const extractPartitionKeyValues = (
documentContent: any, documentContent: any,
partitionKeyDefinition: PartitionKeyDefinition, partitionKeyDefinition: PartitionKeyDefinition,
): PartitionKey[] => { ): PartitionKey[] => {
if (!partitionKeyDefinition.paths || partitionKeyDefinition.paths.length === 0) { if (!partitionKeyDefinition.paths || partitionKeyDefinition.paths.length === 0 || partitionKeyDefinition.systemKey) {
return undefined; return undefined;
} }
@@ -151,7 +136,7 @@ export const extractPartitionKeyValues = (
if (value !== undefined) { if (value !== undefined) {
partitionKeyValues.push(value); partitionKeyValues.push(value);
} else if (!partitionKeyDefinition.systemKey) { } else {
partitionKeyValues.push({}); partitionKeyValues.push({});
} }
}); });

View File

@@ -21,11 +21,3 @@ export function copyStyles(sourceDoc: Document, targetDoc: Document): void {
} }
}); });
} }
/**
* Conditionally returns a class name based on a boolean condition.
* If the condition is true, returns the `trueValue` class; otherwise, returns `falseValue` (or an empty string if not provided).
*/
export function conditionalClass(condition: boolean, trueValue: string, falseValue?: string): string {
return condition ? trueValue : falseValue || "";
}

View File

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

View File

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

View File

@@ -3,13 +3,13 @@
Run "npm run generateARMClients" to regenerate Run "npm run generateARMClients" to regenerate
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2025-05-01-preview/cosmos-db.json Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-12-01-preview/cosmos-db.json
*/ */
import { configContext } from "../../../../ConfigContext";
import { armRequest } from "../../request"; import { armRequest } from "../../request";
import * as Types from "./types"; import * as Types from "./types";
import { configContext } from "../../../../ConfigContext"; const apiVersion = "2024-12-01-preview";
const apiVersion = "2025-05-01-preview";
/* Retrieves the metrics determined by the given filter for the given collection, split by partition. */ /* Retrieves the metrics determined by the given filter for the given collection, split by partition. */
export async function listMetrics( export async function listMetrics(

View File

@@ -3,13 +3,13 @@
Run "npm run generateARMClients" to regenerate Run "npm run generateARMClients" to regenerate
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2025-05-01-preview/cosmos-db.json Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-12-01-preview/cosmos-db.json
*/ */
import { configContext } from "../../../../ConfigContext";
import { armRequest } from "../../request"; import { armRequest } from "../../request";
import * as Types from "./types"; import * as Types from "./types";
import { configContext } from "../../../../ConfigContext"; const apiVersion = "2024-12-01-preview";
const apiVersion = "2025-05-01-preview";
/* Retrieves the metrics determined by the given filter for the given collection and region, split by partition. */ /* Retrieves the metrics determined by the given filter for the given collection and region, split by partition. */
export async function listMetrics( export async function listMetrics(

View File

@@ -3,13 +3,13 @@
Run "npm run generateARMClients" to regenerate Run "npm run generateARMClients" to regenerate
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2025-05-01-preview/cosmos-db.json Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-12-01-preview/cosmos-db.json
*/ */
import { configContext } from "../../../../ConfigContext";
import { armRequest } from "../../request"; import { armRequest } from "../../request";
import * as Types from "./types"; import * as Types from "./types";
import { configContext } from "../../../../ConfigContext"; const apiVersion = "2024-12-01-preview";
const apiVersion = "2025-05-01-preview";
/* Retrieves the metrics determined by the given filter for the given database account, collection and region. */ /* Retrieves the metrics determined by the given filter for the given database account, collection and region. */
export async function listMetrics( export async function listMetrics(

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,13 +3,13 @@
Run "npm run generateARMClients" to regenerate Run "npm run generateARMClients" to regenerate
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2025-05-01-preview/cosmos-db.json Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-12-01-preview/cosmos-db.json
*/ */
import { configContext } from "../../../../ConfigContext";
import { armRequest } from "../../request"; import { armRequest } from "../../request";
import * as Types from "./types"; import * as Types from "./types";
import { configContext } from "../../../../ConfigContext"; const apiVersion = "2024-12-01-preview";
const apiVersion = "2025-05-01-preview";
/* List Cosmos DB locations and their properties */ /* List Cosmos DB locations and their properties */
export async function list(subscriptionId: string): Promise<Types.LocationListResult | Types.CloudError> { export async function list(subscriptionId: string): Promise<Types.LocationListResult | Types.CloudError> {

View File

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

View File

@@ -3,13 +3,13 @@
Run "npm run generateARMClients" to regenerate Run "npm run generateARMClients" to regenerate
Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs Edting this file directly should be done with extreme caution as not to diverge from ARM REST specs
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2025-05-01-preview/cosmos-db.json Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/main/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/preview/2024-12-01-preview/cosmos-db.json
*/ */
import { configContext } from "../../../../ConfigContext";
import { armRequest } from "../../request"; import { armRequest } from "../../request";
import * as Types from "./types"; import * as Types from "./types";
import { configContext } from "../../../../ConfigContext"; const apiVersion = "2024-12-01-preview";
const apiVersion = "2025-05-01-preview";
/* Lists all of the available Cosmos DB Resource Provider operations. */ /* Lists all of the available Cosmos DB Resource Provider operations. */
export async function list(): Promise<Types.OperationListResult> { export async function list(): Promise<Types.OperationListResult> {

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